From 8f48998279c7cddd539405eb1902ba27d3d97197 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Oct 2021 23:26:04 +0200 Subject: [PATCH 01/91] Fixes --- Cargo.lock | 449 ++++++++++---------- Cargo.toml | 90 ++++ pallets/dmp-queue/src/lib.rs | 2 +- pallets/parachain-system/src/lib.rs | 2 +- pallets/xcmp-queue/src/mock.rs | 1 + parachain-template/runtime/src/lib.rs | 3 + polkadot-parachains/pallets/ping/src/lib.rs | 4 +- polkadot-parachains/rococo/src/lib.rs | 3 + polkadot-parachains/shell/src/lib.rs | 3 + polkadot-parachains/statemine/src/lib.rs | 3 + polkadot-parachains/statemint/src/lib.rs | 3 + polkadot-parachains/westmint/src/lib.rs | 3 + 12 files changed, 333 insertions(+), 233 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 273249468f3..decd42d333d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,7 +462,7 @@ dependencies = [ [[package]] name = "beefy-gadget" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "beefy-primitives", "fnv", @@ -490,7 +490,7 @@ dependencies = [ [[package]] name = "beefy-gadget-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "beefy-gadget", "beefy-primitives", @@ -510,12 +510,12 @@ dependencies = [ [[package]] name = "beefy-merkle-tree" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" [[package]] name = "beefy-primitives" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "scale-info", @@ -706,7 +706,6 @@ dependencies = [ [[package]] name = "bp-header-chain" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "finality-grandpa", "frame-support", @@ -722,7 +721,6 @@ dependencies = [ [[package]] name = "bp-message-dispatch" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-runtime", "frame-support", @@ -734,7 +732,6 @@ dependencies = [ [[package]] name = "bp-messages" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitvec 0.20.1", "bp-runtime", @@ -750,7 +747,6 @@ dependencies = [ [[package]] name = "bp-polkadot-core" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-messages", "bp-runtime", @@ -768,7 +764,6 @@ dependencies = [ [[package]] name = "bp-rialto" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-messages", "bp-runtime", @@ -783,7 +778,6 @@ dependencies = [ [[package]] name = "bp-rococo" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -800,7 +794,6 @@ dependencies = [ [[package]] name = "bp-runtime" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "frame-support", "hash-db", @@ -818,7 +811,6 @@ dependencies = [ [[package]] name = "bp-test-utils" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-header-chain", "ed25519-dalek", @@ -833,7 +825,6 @@ dependencies = [ [[package]] name = "bp-wococo" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -848,7 +839,6 @@ dependencies = [ [[package]] name = "bridge-runtime-common" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-message-dispatch", "bp-messages", @@ -2444,7 +2434,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", ] @@ -2462,7 +2452,7 @@ dependencies = [ [[package]] name = "frame-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -2508,7 +2498,7 @@ dependencies = [ [[package]] name = "frame-election-provider-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -2522,7 +2512,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -2550,7 +2540,7 @@ dependencies = [ [[package]] name = "frame-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "bitflags", "frame-metadata", @@ -2577,7 +2567,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "Inflector", "frame-support-procedural-tools", @@ -2589,7 +2579,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate 1.1.0", @@ -2601,7 +2591,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "proc-macro2", "quote", @@ -2611,7 +2601,7 @@ dependencies = [ [[package]] name = "frame-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "log", @@ -2628,7 +2618,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -2643,7 +2633,7 @@ dependencies = [ [[package]] name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "sp-api", @@ -2652,7 +2642,7 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "sp-api", @@ -3604,7 +3594,6 @@ dependencies = [ [[package]] name = "kusama-runtime" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -4574,7 +4563,6 @@ dependencies = [ [[package]] name = "metered-channel" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "derive_more", "futures 0.3.17", @@ -5058,7 +5046,7 @@ dependencies = [ [[package]] name = "pallet-assets" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#66296a0af0aede07c27104e6174a3534b15f14aa" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5072,7 +5060,7 @@ dependencies = [ [[package]] name = "pallet-aura" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#66296a0af0aede07c27104e6174a3534b15f14aa" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5088,7 +5076,7 @@ dependencies = [ [[package]] name = "pallet-authority-discovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5104,7 +5092,7 @@ dependencies = [ [[package]] name = "pallet-authorship" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5119,7 +5107,7 @@ dependencies = [ [[package]] name = "pallet-babe" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5143,7 +5131,7 @@ dependencies = [ [[package]] name = "pallet-bags-list" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5163,7 +5151,7 @@ dependencies = [ [[package]] name = "pallet-balances" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5178,7 +5166,7 @@ dependencies = [ [[package]] name = "pallet-beefy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "beefy-primitives", "frame-support", @@ -5194,7 +5182,7 @@ dependencies = [ [[package]] name = "pallet-beefy-mmr" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "beefy-merkle-tree", "beefy-primitives", @@ -5219,7 +5207,7 @@ dependencies = [ [[package]] name = "pallet-bounties" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5237,7 +5225,6 @@ dependencies = [ [[package]] name = "pallet-bridge-dispatch" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-message-dispatch", "bp-runtime", @@ -5254,7 +5241,6 @@ dependencies = [ [[package]] name = "pallet-bridge-grandpa" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bp-header-chain", "bp-runtime", @@ -5276,7 +5262,6 @@ dependencies = [ [[package]] name = "pallet-bridge-messages" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitvec 0.20.1", "bp-message-dispatch", @@ -5324,7 +5309,7 @@ dependencies = [ [[package]] name = "pallet-collective" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5341,7 +5326,7 @@ dependencies = [ [[package]] name = "pallet-democracy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5357,7 +5342,7 @@ dependencies = [ [[package]] name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5381,7 +5366,7 @@ dependencies = [ [[package]] name = "pallet-elections-phragmen" version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5399,7 +5384,7 @@ dependencies = [ [[package]] name = "pallet-gilt" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5414,7 +5399,7 @@ dependencies = [ [[package]] name = "pallet-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5437,7 +5422,7 @@ dependencies = [ [[package]] name = "pallet-identity" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "enumflags2", "frame-benchmarking", @@ -5453,7 +5438,7 @@ dependencies = [ [[package]] name = "pallet-im-online" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5473,7 +5458,7 @@ dependencies = [ [[package]] name = "pallet-indices" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5490,7 +5475,7 @@ dependencies = [ [[package]] name = "pallet-membership" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5507,7 +5492,7 @@ dependencies = [ [[package]] name = "pallet-mmr" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "ckb-merkle-mountain-range", "frame-benchmarking", @@ -5525,7 +5510,7 @@ dependencies = [ [[package]] name = "pallet-mmr-primitives" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5541,7 +5526,7 @@ dependencies = [ [[package]] name = "pallet-mmr-rpc" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -5558,7 +5543,7 @@ dependencies = [ [[package]] name = "pallet-multisig" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5573,7 +5558,7 @@ dependencies = [ [[package]] name = "pallet-nicks" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5587,7 +5572,7 @@ dependencies = [ [[package]] name = "pallet-offences" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5604,7 +5589,7 @@ dependencies = [ [[package]] name = "pallet-offences-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5627,7 +5612,7 @@ dependencies = [ [[package]] name = "pallet-proxy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5642,7 +5627,7 @@ dependencies = [ [[package]] name = "pallet-randomness-collective-flip" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#66296a0af0aede07c27104e6174a3534b15f14aa" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5656,7 +5641,7 @@ dependencies = [ [[package]] name = "pallet-recovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5670,7 +5655,7 @@ dependencies = [ [[package]] name = "pallet-scheduler" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5686,7 +5671,7 @@ dependencies = [ [[package]] name = "pallet-session" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5707,7 +5692,7 @@ dependencies = [ [[package]] name = "pallet-session-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5723,7 +5708,7 @@ dependencies = [ [[package]] name = "pallet-society" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5737,7 +5722,7 @@ dependencies = [ [[package]] name = "pallet-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5760,7 +5745,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-curve" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", @@ -5771,7 +5756,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-fn" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "log", "sp-arithmetic", @@ -5780,7 +5765,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5809,7 +5794,7 @@ dependencies = [ [[package]] name = "pallet-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5827,7 +5812,7 @@ dependencies = [ [[package]] name = "pallet-tips" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5846,7 +5831,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-support", "frame-system", @@ -5863,7 +5848,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -5880,7 +5865,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -5891,7 +5876,7 @@ dependencies = [ [[package]] name = "pallet-treasury" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5922,7 +5907,7 @@ dependencies = [ [[package]] name = "pallet-utility" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5938,7 +5923,7 @@ dependencies = [ [[package]] name = "pallet-vesting" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-benchmarking", "frame-support", @@ -5953,7 +5938,6 @@ dependencies = [ [[package]] name = "pallet-xcm" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "frame-support", "frame-system", @@ -5971,7 +5955,6 @@ dependencies = [ [[package]] name = "pallet-xcm-benchmarks" version = "0.9.8" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "frame-benchmarking", "frame-support", @@ -6491,7 +6474,6 @@ checksum = "989d43012e2ca1c4a02507c67282691a0a3207f9dc67cec596b43fe925b3d325" [[package]] name = "polkadot-approval-distribution" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "polkadot-node-network-protocol", @@ -6505,7 +6487,6 @@ dependencies = [ [[package]] name = "polkadot-availability-bitfield-distribution" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "polkadot-node-network-protocol", @@ -6518,7 +6499,6 @@ dependencies = [ [[package]] name = "polkadot-availability-distribution" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "derive_more", "futures 0.3.17", @@ -6540,7 +6520,6 @@ dependencies = [ [[package]] name = "polkadot-availability-recovery" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "lru 0.7.0", @@ -6560,7 +6539,6 @@ dependencies = [ [[package]] name = "polkadot-cli" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "frame-benchmarking-cli", "futures 0.3.17", @@ -6580,7 +6558,6 @@ dependencies = [ [[package]] name = "polkadot-client" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "beefy-primitives", "frame-benchmarking", @@ -6678,7 +6655,6 @@ dependencies = [ [[package]] name = "polkadot-collator-protocol" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "always-assert", "derive_more", @@ -6699,7 +6675,6 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "parity-scale-codec", "parity-util-mem", @@ -6712,7 +6687,6 @@ dependencies = [ [[package]] name = "polkadot-dispute-distribution" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "derive_more", "futures 0.3.17", @@ -6734,7 +6708,6 @@ dependencies = [ [[package]] name = "polkadot-erasure-coding" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "parity-scale-codec", "polkadot-node-primitives", @@ -6748,7 +6721,6 @@ dependencies = [ [[package]] name = "polkadot-gossip-support" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -6768,7 +6740,6 @@ dependencies = [ [[package]] name = "polkadot-network-bridge" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-trait", "futures 0.3.17", @@ -6787,7 +6758,6 @@ dependencies = [ [[package]] name = "polkadot-node-collation-generation" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "parity-scale-codec", @@ -6805,7 +6775,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-approval-voting" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitvec 0.20.1", "derive_more", @@ -6833,7 +6802,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-av-store" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitvec 0.20.1", "futures 0.3.17", @@ -6853,7 +6821,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-backing" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitvec 0.20.1", "futures 0.3.17", @@ -6871,7 +6838,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-bitfield-signing" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "polkadot-node-subsystem", @@ -6886,7 +6852,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-candidate-validation" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-trait", "futures 0.3.17", @@ -6904,7 +6869,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-api" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "polkadot-node-subsystem", @@ -6919,7 +6883,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-selection" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -6936,7 +6899,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-coordinator" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitvec 0.20.1", "derive_more", @@ -6955,7 +6917,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-participation" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "polkadot-node-primitives", @@ -6968,7 +6929,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-parachains-inherent" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-trait", "futures 0.3.17", @@ -6985,7 +6945,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-provisioner" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitvec 0.20.1", "futures 0.3.17", @@ -7000,7 +6959,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "always-assert", "assert_matches", @@ -7031,7 +6989,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-runtime-api" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "memory-lru", @@ -7049,7 +7006,6 @@ dependencies = [ [[package]] name = "polkadot-node-jaeger" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-std", "lazy_static", @@ -7067,7 +7023,6 @@ dependencies = [ [[package]] name = "polkadot-node-metrics" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -7078,7 +7033,6 @@ dependencies = [ [[package]] name = "polkadot-node-network-protocol" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-trait", "derive_more", @@ -7096,7 +7050,6 @@ dependencies = [ [[package]] name = "polkadot-node-primitives" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bounded-vec", "futures 0.3.17", @@ -7118,7 +7071,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "polkadot-node-jaeger", "polkadot-node-subsystem-types", @@ -7128,7 +7080,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-test-helpers" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-trait", "futures 0.3.17", @@ -7146,7 +7097,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-types" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "derive_more", "futures 0.3.17", @@ -7165,7 +7115,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-util" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-trait", "derive_more", @@ -7192,7 +7141,6 @@ dependencies = [ [[package]] name = "polkadot-overseer" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -7213,7 +7161,6 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-trait", "futures 0.3.17", @@ -7230,7 +7177,6 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen-proc-macro" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", @@ -7241,7 +7187,6 @@ dependencies = [ [[package]] name = "polkadot-parachain" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "derive_more", "frame-support", @@ -7258,7 +7203,6 @@ dependencies = [ [[package]] name = "polkadot-primitives" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitvec 0.20.1", "frame-system", @@ -7288,7 +7232,6 @@ dependencies = [ [[package]] name = "polkadot-rpc" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "beefy-gadget", "beefy-gadget-rpc", @@ -7319,7 +7262,6 @@ dependencies = [ [[package]] name = "polkadot-runtime" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -7396,7 +7338,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -7443,7 +7384,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-parachains" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "bitflags", "bitvec 0.20.1", @@ -7482,7 +7422,6 @@ dependencies = [ [[package]] name = "polkadot-service" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "async-trait", "beefy-gadget", @@ -7580,7 +7519,6 @@ dependencies = [ [[package]] name = "polkadot-statement-distribution" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "arrayvec 0.5.2", "derive_more", @@ -7601,7 +7539,6 @@ dependencies = [ [[package]] name = "polkadot-statement-table" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "parity-scale-codec", "polkadot-primitives", @@ -7611,7 +7548,6 @@ dependencies = [ [[package]] name = "polkadot-test-client" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "parity-scale-codec", "polkadot-node-subsystem", @@ -7636,7 +7572,6 @@ dependencies = [ [[package]] name = "polkadot-test-runtime" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -7697,7 +7632,6 @@ dependencies = [ [[package]] name = "polkadot-test-service" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "frame-benchmarking", "frame-system", @@ -8420,7 +8354,6 @@ dependencies = [ [[package]] name = "rococo-runtime" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "beefy-primitives", "bp-messages", @@ -8609,7 +8542,7 @@ dependencies = [ [[package]] name = "sc-allocator" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "log", "sp-core", @@ -8620,7 +8553,7 @@ dependencies = [ [[package]] name = "sc-authority-discovery" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "derive_more", @@ -8647,7 +8580,7 @@ dependencies = [ [[package]] name = "sc-basic-authorship" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -8670,7 +8603,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "sc-client-api", @@ -8686,7 +8619,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -8702,7 +8635,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", @@ -8713,7 +8646,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "chrono", "fdlimit", @@ -8751,7 +8684,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "fnv", "futures 0.3.17", @@ -8779,7 +8712,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "hash-db", "kvdb", @@ -8804,7 +8737,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "futures 0.3.17", @@ -8857,7 +8790,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "derive_more", @@ -8900,7 +8833,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "derive_more", "futures 0.3.17", @@ -8924,7 +8857,7 @@ dependencies = [ [[package]] name = "sc-consensus-epochs" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "fork-tree", "parity-scale-codec", @@ -8937,7 +8870,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "futures 0.3.17", @@ -8963,7 +8896,7 @@ dependencies = [ [[package]] name = "sc-consensus-uncles" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "sc-client-api", "sp-authorship", @@ -8974,7 +8907,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "lazy_static", "libsecp256k1 0.6.0", @@ -9000,7 +8933,7 @@ dependencies = [ [[package]] name = "sc-executor-common" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "derive_more", "environmental", @@ -9018,7 +8951,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmi" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "log", "parity-scale-codec", @@ -9034,7 +8967,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmtime" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "cfg-if 1.0.0", "libc", @@ -9052,7 +8985,7 @@ dependencies = [ [[package]] name = "sc-finality-grandpa" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "derive_more", @@ -9089,7 +9022,7 @@ dependencies = [ [[package]] name = "sc-finality-grandpa-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "derive_more", "finality-grandpa", @@ -9113,7 +9046,7 @@ dependencies = [ [[package]] name = "sc-informant" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "ansi_term 0.12.1", "futures 0.3.17", @@ -9130,7 +9063,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "derive_more", @@ -9145,7 +9078,7 @@ dependencies = [ [[package]] name = "sc-light" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "hash-db", "parity-scale-codec", @@ -9163,7 +9096,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-std", "async-trait", @@ -9214,7 +9147,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -9230,7 +9163,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "bytes 1.0.1", "fnv", @@ -9257,7 +9190,7 @@ dependencies = [ [[package]] name = "sc-peerset" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "libp2p", @@ -9270,7 +9203,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.9.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -9279,7 +9212,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "hash-db", @@ -9310,7 +9243,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "jsonrpc-core", @@ -9335,7 +9268,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "jsonrpc-core", @@ -9352,7 +9285,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "directories", @@ -9417,7 +9350,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "log", "parity-scale-codec", @@ -9431,7 +9364,7 @@ dependencies = [ [[package]] name = "sc-sync-state-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -9453,7 +9386,7 @@ dependencies = [ [[package]] name = "sc-telemetry" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "chrono", "futures 0.3.17", @@ -9471,7 +9404,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "ansi_term 0.12.1", "atty", @@ -9502,7 +9435,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", @@ -9513,7 +9446,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "intervalier", @@ -9540,7 +9473,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "derive_more", "futures 0.3.17", @@ -9554,7 +9487,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -9902,7 +9835,6 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "slot-range-helper" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "enumn", "parity-scale-codec", @@ -10005,7 +9937,7 @@ dependencies = [ [[package]] name = "sp-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "hash-db", "log", @@ -10022,7 +9954,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "blake2-rfc", "proc-macro-crate 1.1.0", @@ -10034,7 +9966,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "scale-info", @@ -10047,7 +9979,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "integer-sqrt", "num-traits", @@ -10062,7 +9994,7 @@ dependencies = [ [[package]] name = "sp-authority-discovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "scale-info", @@ -10075,7 +10007,7 @@ dependencies = [ [[package]] name = "sp-authorship" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "parity-scale-codec", @@ -10087,7 +10019,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "sp-api", @@ -10099,7 +10031,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "log", @@ -10117,7 +10049,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "futures 0.3.17", @@ -10136,7 +10068,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#66296a0af0aede07c27104e6174a3534b15f14aa" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "parity-scale-codec", @@ -10154,7 +10086,7 @@ dependencies = [ [[package]] name = "sp-consensus-babe" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "merlin", @@ -10177,7 +10109,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "scale-info", @@ -10188,7 +10120,7 @@ dependencies = [ [[package]] name = "sp-consensus-vrf" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "schnorrkel", @@ -10200,7 +10132,7 @@ dependencies = [ [[package]] name = "sp-core" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "base58", "blake2-rfc", @@ -10246,7 +10178,7 @@ dependencies = [ [[package]] name = "sp-database" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "kvdb", "parking_lot 0.11.1", @@ -10255,7 +10187,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "proc-macro2", "quote", @@ -10265,7 +10197,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "environmental", "parity-scale-codec", @@ -10276,7 +10208,7 @@ dependencies = [ [[package]] name = "sp-finality-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "finality-grandpa", "log", @@ -10294,7 +10226,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -10308,7 +10240,7 @@ dependencies = [ [[package]] name = "sp-io" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "hash-db", @@ -10332,7 +10264,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "lazy_static", "sp-core", @@ -10343,7 +10275,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "derive_more", @@ -10360,7 +10292,7 @@ dependencies = [ [[package]] name = "sp-maybe-compressed-blob" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "zstd", ] @@ -10368,7 +10300,7 @@ dependencies = [ [[package]] name = "sp-npos-elections" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "scale-info", @@ -10383,7 +10315,7 @@ dependencies = [ [[package]] name = "sp-npos-elections-solution-type" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", @@ -10394,7 +10326,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "sp-api", "sp-core", @@ -10404,7 +10336,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "backtrace", ] @@ -10412,7 +10344,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "rustc-hash", "serde", @@ -10422,7 +10354,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "either", "hash256-std-hasher", @@ -10444,7 +10376,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -10461,7 +10393,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "Inflector", "proc-macro-crate 1.1.0", @@ -10473,7 +10405,7 @@ dependencies = [ [[package]] name = "sp-serializer" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "serde", "serde_json", @@ -10482,7 +10414,7 @@ dependencies = [ [[package]] name = "sp-session" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "scale-info", @@ -10496,7 +10428,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "scale-info", @@ -10507,7 +10439,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "hash-db", "log", @@ -10530,12 +10462,12 @@ dependencies = [ [[package]] name = "sp-std" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" [[package]] name = "sp-storage" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "impl-serde", "parity-scale-codec", @@ -10548,7 +10480,7 @@ dependencies = [ [[package]] name = "sp-tasks" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "log", "sp-core", @@ -10561,7 +10493,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "futures-timer 3.0.2", @@ -10577,7 +10509,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "sp-std", @@ -10589,7 +10521,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "sp-api", "sp-runtime", @@ -10598,7 +10530,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "log", @@ -10614,7 +10546,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "hash-db", "memory-db", @@ -10629,7 +10561,7 @@ dependencies = [ [[package]] name = "sp-version" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "impl-serde", "parity-scale-codec", @@ -10645,7 +10577,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -10656,7 +10588,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -10980,7 +10912,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "frame-system-rpc-runtime-api", "futures 0.3.17", @@ -11002,7 +10934,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.9.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-std", "derive_more", @@ -11016,7 +10948,7 @@ dependencies = [ [[package]] name = "substrate-test-client" version = "2.0.1" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "async-trait", "futures 0.3.17", @@ -11043,7 +10975,7 @@ dependencies = [ [[package]] name = "substrate-test-utils" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#66296a0af0aede07c27104e6174a3534b15f14aa" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "futures 0.3.17", "substrate-test-utils-derive", @@ -11053,7 +10985,7 @@ dependencies = [ [[package]] name = "substrate-test-utils-derive" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#66296a0af0aede07c27104e6174a3534b15f14aa" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", @@ -11064,7 +10996,7 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "ansi_term 0.12.1", "build-helper", @@ -11426,7 +11358,7 @@ dependencies = [ "chrono", "lazy_static", "matchers", - "parking_lot 0.10.2", + "parking_lot 0.11.1", "regex", "serde", "serde_json", @@ -12095,7 +12027,6 @@ dependencies = [ [[package]] name = "westend-runtime" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -12340,7 +12271,6 @@ dependencies = [ [[package]] name = "xcm" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "derivative", "impl-trait-for-tuples", @@ -12353,7 +12283,6 @@ dependencies = [ [[package]] name = "xcm-builder" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "frame-support", "frame-system", @@ -12373,7 +12302,6 @@ dependencies = [ [[package]] name = "xcm-executor" version = "0.9.12" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "frame-benchmarking", "frame-support", @@ -12391,7 +12319,6 @@ dependencies = [ [[package]] name = "xcm-procedural" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#fc32642c5011721916cb0f9e083800c281979980" dependencies = [ "proc-macro2", "quote", @@ -12461,3 +12388,67 @@ dependencies = [ "cc", "libc", ] + +[[patch.unused]] +name = "polkadot" +version = "0.9.12" + +[[patch.unused]] +name = "polkadot-simnet" +version = "0.9.12" + +[[patch.unused]] +name = "polkadot-simnet-node" +version = "0.9.12" + +[[patch.unused]] +name = "polkadot-simnet-test" +version = "0.9.12" + +[[patch.unused]] +name = "polkadot-test-malus" +version = "0.9.12" + +[[patch.unused]] +name = "polkadot-voter-bags" +version = "0.9.0" + +[[patch.unused]] +name = "remote-ext-tests-bags-list" +version = "0.9.12" + +[[patch.unused]] +name = "staking-miner" +version = "0.9.12" + +[[patch.unused]] +name = "test-parachain-adder" +version = "0.9.12" + +[[patch.unused]] +name = "test-parachain-adder-collator" +version = "0.9.12" + +[[patch.unused]] +name = "test-parachain-halt" +version = "0.9.12" + +[[patch.unused]] +name = "test-parachains" +version = "0.9.12" + +[[patch.unused]] +name = "xcm-executor-integration-tests" +version = "0.9.12" + +[[patch.unused]] +name = "xcm-simulator" +version = "0.9.12" + +[[patch.unused]] +name = "xcm-simulator-example" +version = "0.9.12" + +[[patch.unused]] +name = "xcm-simulator-fuzzer" +version = "0.9.9" diff --git a/Cargo.toml b/Cargo.toml index c89e88b214e..2197b1352d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,3 +41,93 @@ members = [ [profile.release] panic = "unwind" +[patch."https://github.com/paritytech/polkadot"] +polkadot-cli ={path = "/Users/gav/Core/polkadot/cli" } +polkadot-node-core-pvf ={path = "/Users/gav/Core/polkadot/node/core/pvf" } +polkadot-core-primitives ={path = "/Users/gav/Core/polkadot/core-primitives" } +polkadot-node-subsystem-util ={path = "/Users/gav/Core/polkadot/node/subsystem-util" } +metered-channel ={path = "/Users/gav/Core/polkadot/node/metered-channel" } +polkadot-node-jaeger ={path = "/Users/gav/Core/polkadot/node/jaeger" } +polkadot-node-primitives ={path = "/Users/gav/Core/polkadot/node/primitives" } +polkadot-parachain ={path = "/Users/gav/Core/polkadot/parachain" } +polkadot-primitives ={path = "/Users/gav/Core/polkadot/primitives" } +polkadot-erasure-coding ={path = "/Users/gav/Core/polkadot/erasure-coding" } +polkadot-node-metrics ={path = "/Users/gav/Core/polkadot/node/metrics" } +polkadot-node-network-protocol ={path = "/Users/gav/Core/polkadot/node/network/protocol" } +polkadot-node-subsystem ={path = "/Users/gav/Core/polkadot/node/subsystem" } +polkadot-node-subsystem-types ={path = "/Users/gav/Core/polkadot/node/subsystem-types" } +polkadot-overseer-gen ={path = "/Users/gav/Core/polkadot/node/overseer/overseer-gen" } +polkadot-overseer-gen-proc-macro ={path = "/Users/gav/Core/polkadot/node/overseer/overseer-gen/proc-macro" } +polkadot-statement-table ={path = "/Users/gav/Core/polkadot/statement-table" } +polkadot-overseer ={path = "/Users/gav/Core/polkadot/node/overseer" } +polkadot-node-subsystem-test-helpers ={path = "/Users/gav/Core/polkadot/node/subsystem-test-helpers" } +test-parachain-adder ={path = "/Users/gav/Core/polkadot/parachain/test-parachains/adder" } +test-parachain-halt ={path = "/Users/gav/Core/polkadot/parachain/test-parachains/halt" } +polkadot-service ={path = "/Users/gav/Core/polkadot/node/service" } +kusama-runtime ={path = "/Users/gav/Core/polkadot/runtime/kusama" } +pallet-xcm ={path = "/Users/gav/Core/polkadot/xcm/pallet-xcm" } +xcm ={path = "/Users/gav/Core/polkadot/xcm" } +xcm-procedural ={path = "/Users/gav/Core/polkadot/xcm/procedural" } +xcm-executor ={path = "/Users/gav/Core/polkadot/xcm/xcm-executor" } +polkadot-runtime-parachains ={path = "/Users/gav/Core/polkadot/runtime/parachains" } +xcm-builder ={path = "/Users/gav/Core/polkadot/xcm/xcm-builder" } +polkadot-runtime-common ={path = "/Users/gav/Core/polkadot/runtime/common" } +slot-range-helper ={path = "/Users/gav/Core/polkadot/runtime/common/slot_range_helper" } +polkadot-approval-distribution ={path = "/Users/gav/Core/polkadot/node/network/approval-distribution" } +polkadot-availability-bitfield-distribution ={path = "/Users/gav/Core/polkadot/node/network/bitfield-distribution" } +polkadot-availability-distribution ={path = "/Users/gav/Core/polkadot/node/network/availability-distribution" } +polkadot-availability-recovery ={path = "/Users/gav/Core/polkadot/node/network/availability-recovery" } +polkadot-client ={path = "/Users/gav/Core/polkadot/node/client" } +polkadot-runtime ={path = "/Users/gav/Core/polkadot/runtime/polkadot" } +rococo-runtime ={path = "/Users/gav/Core/polkadot/runtime/rococo" } +bp-messages ={path = "/Users/gav/Core/polkadot/bridges/primitives/messages" } +bp-runtime ={path = "/Users/gav/Core/polkadot/bridges/primitives/runtime" } +bp-rococo ={path = "/Users/gav/Core/polkadot/bridges/primitives/chain-rococo" } +bp-polkadot-core ={path = "/Users/gav/Core/polkadot/bridges/primitives/polkadot-core" } +bp-wococo ={path = "/Users/gav/Core/polkadot/bridges/primitives/chain-wococo" } +bridge-runtime-common ={path = "/Users/gav/Core/polkadot/bridges/bin/runtime-common" } +bp-message-dispatch ={path = "/Users/gav/Core/polkadot/bridges/primitives/message-dispatch" } +pallet-bridge-dispatch ={path = "/Users/gav/Core/polkadot/bridges/modules/dispatch" } +pallet-bridge-grandpa ={path = "/Users/gav/Core/polkadot/bridges/modules/grandpa" } +bp-header-chain ={path = "/Users/gav/Core/polkadot/bridges/primitives/header-chain" } +bp-test-utils ={path = "/Users/gav/Core/polkadot/bridges/primitives/test-utils" } +pallet-bridge-messages ={path = "/Users/gav/Core/polkadot/bridges/modules/messages" } +bp-rialto ={path = "/Users/gav/Core/polkadot/bridges/primitives/chain-rialto" } +westend-runtime ={path = "/Users/gav/Core/polkadot/runtime/westend" } +pallet-xcm-benchmarks ={path = "/Users/gav/Core/polkadot/xcm/pallet-xcm-benchmarks" } +polkadot-collator-protocol ={path = "/Users/gav/Core/polkadot/node/network/collator-protocol" } +polkadot-dispute-distribution ={path = "/Users/gav/Core/polkadot/node/network/dispute-distribution" } +polkadot-gossip-support ={path = "/Users/gav/Core/polkadot/node/network/gossip-support" } +polkadot-network-bridge ={path = "/Users/gav/Core/polkadot/node/network/bridge" } +polkadot-node-collation-generation ={path = "/Users/gav/Core/polkadot/node/collation-generation" } +polkadot-node-core-approval-voting ={path = "/Users/gav/Core/polkadot/node/core/approval-voting" } +polkadot-node-core-av-store ={path = "/Users/gav/Core/polkadot/node/core/av-store" } +polkadot-node-core-backing ={path = "/Users/gav/Core/polkadot/node/core/backing" } +polkadot-node-core-bitfield-signing ={path = "/Users/gav/Core/polkadot/node/core/bitfield-signing" } +polkadot-node-core-candidate-validation ={path = "/Users/gav/Core/polkadot/node/core/candidate-validation" } +polkadot-node-core-chain-api ={path = "/Users/gav/Core/polkadot/node/core/chain-api" } +polkadot-node-core-chain-selection ={path = "/Users/gav/Core/polkadot/node/core/chain-selection" } +polkadot-node-core-dispute-coordinator ={path = "/Users/gav/Core/polkadot/node/core/dispute-coordinator" } +polkadot-node-core-dispute-participation ={path = "/Users/gav/Core/polkadot/node/core/dispute-participation" } +polkadot-node-core-parachains-inherent ={path = "/Users/gav/Core/polkadot/node/core/parachains-inherent" } +polkadot-node-core-provisioner ={path = "/Users/gav/Core/polkadot/node/core/provisioner" } +polkadot-node-core-runtime-api ={path = "/Users/gav/Core/polkadot/node/core/runtime-api" } +polkadot-rpc ={path = "/Users/gav/Core/polkadot/rpc" } +polkadot-statement-distribution ={path = "/Users/gav/Core/polkadot/node/network/statement-distribution" } +polkadot-test-client ={path = "/Users/gav/Core/polkadot/node/test/client" } +polkadot-test-runtime ={path = "/Users/gav/Core/polkadot/runtime/test-runtime" } +polkadot-test-service ={path = "/Users/gav/Core/polkadot/node/test/service" } +xcm-executor-integration-tests ={path = "/Users/gav/Core/polkadot/xcm/xcm-executor/integration-tests" } +xcm-simulator ={path = "/Users/gav/Core/polkadot/xcm/xcm-simulator" } +xcm-simulator-example ={path = "/Users/gav/Core/polkadot/xcm/xcm-simulator/example" } +xcm-simulator-fuzzer ={path = "/Users/gav/Core/polkadot/xcm/xcm-simulator/fuzzer" } +polkadot-test-malus ={path = "/Users/gav/Core/polkadot/node/malus" } +polkadot-simnet ={path = "/Users/gav/Core/polkadot/node/test/polkadot-simnet/common" } +polkadot-simnet-node ={path = "/Users/gav/Core/polkadot/node/test/polkadot-simnet/node" } +polkadot-simnet-test ={path = "/Users/gav/Core/polkadot/node/test/polkadot-simnet/test" } +test-parachains ={path = "/Users/gav/Core/polkadot/parachain/test-parachains" } +test-parachain-adder-collator ={path = "/Users/gav/Core/polkadot/parachain/test-parachains/adder/collator" } +staking-miner ={path = "/Users/gav/Core/polkadot/utils/staking-miner" } +remote-ext-tests-bags-list ={path = "/Users/gav/Core/polkadot/utils/remote-ext-tests/bags-list" } +polkadot-voter-bags ={path = "/Users/gav/Core/polkadot/utils/voter-bags" } +polkadot ={path = "/Users/gav/Core/polkadot" } diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 251d3acbb38..8766f618d6c 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -473,7 +473,7 @@ mod tests { fn msg(weight: Weight) -> Xcm { Xcm(vec![Transact { - origin_type: OriginKind::Native, + origin_kind: OriginKind::Native, require_weight_at_most: weight, call: vec![].into(), }]) diff --git a/pallets/parachain-system/src/lib.rs b/pallets/parachain-system/src/lib.rs index 9f2e044c720..198a7646f3e 100644 --- a/pallets/parachain-system/src/lib.rs +++ b/pallets/parachain-system/src/lib.rs @@ -660,7 +660,7 @@ impl GetChannelInfo for Pallet { // None then it must be that this is an edge-case where a message is attempted to be // sent at the first block. It should be safe to assume that there are no channels // opened at all so early. At least, relying on this assumption seems to be a better - // tradeoff, compared to introducing an error variant that the clients should be + // trade-off, compared to introducing an error variant that the clients should be // prepared to handle. let index = match channels.binary_search_by_key(&id, |item| item.0) { Err(_) => return ChannelStatus::Closed, diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 3496db5aa32..51f19bf4a50 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -110,6 +110,7 @@ parameter_types! { pub Ancestry: MultiLocation = X1(Parachain(1u32.into())).into(); pub UnitWeightCost: Weight = 1_000_000; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } /// Means for transacting assets on this chain. diff --git a/parachain-template/runtime/src/lib.rs b/parachain-template/runtime/src/lib.rs index 9012dcf1500..bcdefa48f92 100644 --- a/parachain-template/runtime/src/lib.rs +++ b/parachain-template/runtime/src/lib.rs @@ -457,6 +457,7 @@ parameter_types! { // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = 1_000_000_000; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } match_type! { @@ -490,6 +491,8 @@ impl Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; } parameter_types! { diff --git a/polkadot-parachains/pallets/ping/src/lib.rs b/polkadot-parachains/pallets/ping/src/lib.rs index 9960d66c504..621c3afd923 100644 --- a/polkadot-parachains/pallets/ping/src/lib.rs +++ b/polkadot-parachains/pallets/ping/src/lib.rs @@ -91,7 +91,7 @@ pub mod pallet { match T::XcmSender::send_xcm( (1, Junction::Parachain(para.into())), Xcm(vec![Transact { - origin_type: OriginKind::Native, + origin_kind: OriginKind::Native, require_weight_at_most: 1_000, call: ::Call::from(Call::::ping { seq, @@ -167,7 +167,7 @@ pub mod pallet { match T::XcmSender::send_xcm( (1, Junction::Parachain(para.into())), Xcm(vec![Transact { - origin_type: OriginKind::Native, + origin_kind: OriginKind::Native, require_weight_at_most: 1_000, call: ::Call::from(Call::::pong { seq, diff --git a/polkadot-parachains/rococo/src/lib.rs b/polkadot-parachains/rococo/src/lib.rs index d835cd4aa3e..c45306d4e36 100644 --- a/polkadot-parachains/rococo/src/lib.rs +++ b/polkadot-parachains/rococo/src/lib.rs @@ -380,6 +380,7 @@ pub type Barrier = ( parameter_types! { pub StatemintLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(1000))); + pub MaxAssetsIntoHolding: u32 = 64; } pub type Reserves = (NativeAsset, AssetsFrom); @@ -401,6 +402,8 @@ impl Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. diff --git a/polkadot-parachains/shell/src/lib.rs b/polkadot-parachains/shell/src/lib.rs index 84823d52b58..a202afd63d4 100644 --- a/polkadot-parachains/shell/src/lib.rs +++ b/polkadot-parachains/shell/src/lib.rs @@ -197,6 +197,7 @@ parameter_types! { // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = 1_000_000; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } pub struct XcmConfig; @@ -215,6 +216,8 @@ impl Config for XcmConfig { type AssetTrap = (); // don't trap for now type AssetClaims = (); // don't claim for now type SubscriptionService = (); // don't handle subscriptions for now + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/polkadot-parachains/statemine/src/lib.rs b/polkadot-parachains/statemine/src/lib.rs index 474a9f93fd4..d3d257056f5 100644 --- a/polkadot-parachains/statemine/src/lib.rs +++ b/polkadot-parachains/statemine/src/lib.rs @@ -549,6 +549,7 @@ parameter_types! { // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = 1_000_000_000; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } match_type! { @@ -582,6 +583,8 @@ impl Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; } parameter_types! { diff --git a/polkadot-parachains/statemint/src/lib.rs b/polkadot-parachains/statemint/src/lib.rs index ade13cc1e55..b4e99ece7f2 100644 --- a/polkadot-parachains/statemint/src/lib.rs +++ b/polkadot-parachains/statemint/src/lib.rs @@ -526,6 +526,7 @@ parameter_types! { // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = 1_000_000; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } match_type! { @@ -558,6 +559,8 @@ impl Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; } parameter_types! { diff --git a/polkadot-parachains/westmint/src/lib.rs b/polkadot-parachains/westmint/src/lib.rs index ace9ce9afac..7c1853c1743 100644 --- a/polkadot-parachains/westmint/src/lib.rs +++ b/polkadot-parachains/westmint/src/lib.rs @@ -512,6 +512,7 @@ pub type XcmOriginToTransactDispatchOrigin = ( parameter_types! { pub UnitWeightCost: Weight = 1_000; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } match_type! { @@ -544,6 +545,8 @@ impl Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; } parameter_types! { From 2ac537ee1f2bdbb2fd3063981c8c0e3b12718d08 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Oct 2021 23:32:37 +0200 Subject: [PATCH 02/91] Undiener --- Cargo.toml | 90 ------------------------------------------------------ 1 file changed, 90 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2197b1352d5..c89e88b214e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,93 +41,3 @@ members = [ [profile.release] panic = "unwind" -[patch."https://github.com/paritytech/polkadot"] -polkadot-cli ={path = "/Users/gav/Core/polkadot/cli" } -polkadot-node-core-pvf ={path = "/Users/gav/Core/polkadot/node/core/pvf" } -polkadot-core-primitives ={path = "/Users/gav/Core/polkadot/core-primitives" } -polkadot-node-subsystem-util ={path = "/Users/gav/Core/polkadot/node/subsystem-util" } -metered-channel ={path = "/Users/gav/Core/polkadot/node/metered-channel" } -polkadot-node-jaeger ={path = "/Users/gav/Core/polkadot/node/jaeger" } -polkadot-node-primitives ={path = "/Users/gav/Core/polkadot/node/primitives" } -polkadot-parachain ={path = "/Users/gav/Core/polkadot/parachain" } -polkadot-primitives ={path = "/Users/gav/Core/polkadot/primitives" } -polkadot-erasure-coding ={path = "/Users/gav/Core/polkadot/erasure-coding" } -polkadot-node-metrics ={path = "/Users/gav/Core/polkadot/node/metrics" } -polkadot-node-network-protocol ={path = "/Users/gav/Core/polkadot/node/network/protocol" } -polkadot-node-subsystem ={path = "/Users/gav/Core/polkadot/node/subsystem" } -polkadot-node-subsystem-types ={path = "/Users/gav/Core/polkadot/node/subsystem-types" } -polkadot-overseer-gen ={path = "/Users/gav/Core/polkadot/node/overseer/overseer-gen" } -polkadot-overseer-gen-proc-macro ={path = "/Users/gav/Core/polkadot/node/overseer/overseer-gen/proc-macro" } -polkadot-statement-table ={path = "/Users/gav/Core/polkadot/statement-table" } -polkadot-overseer ={path = "/Users/gav/Core/polkadot/node/overseer" } -polkadot-node-subsystem-test-helpers ={path = "/Users/gav/Core/polkadot/node/subsystem-test-helpers" } -test-parachain-adder ={path = "/Users/gav/Core/polkadot/parachain/test-parachains/adder" } -test-parachain-halt ={path = "/Users/gav/Core/polkadot/parachain/test-parachains/halt" } -polkadot-service ={path = "/Users/gav/Core/polkadot/node/service" } -kusama-runtime ={path = "/Users/gav/Core/polkadot/runtime/kusama" } -pallet-xcm ={path = "/Users/gav/Core/polkadot/xcm/pallet-xcm" } -xcm ={path = "/Users/gav/Core/polkadot/xcm" } -xcm-procedural ={path = "/Users/gav/Core/polkadot/xcm/procedural" } -xcm-executor ={path = "/Users/gav/Core/polkadot/xcm/xcm-executor" } -polkadot-runtime-parachains ={path = "/Users/gav/Core/polkadot/runtime/parachains" } -xcm-builder ={path = "/Users/gav/Core/polkadot/xcm/xcm-builder" } -polkadot-runtime-common ={path = "/Users/gav/Core/polkadot/runtime/common" } -slot-range-helper ={path = "/Users/gav/Core/polkadot/runtime/common/slot_range_helper" } -polkadot-approval-distribution ={path = "/Users/gav/Core/polkadot/node/network/approval-distribution" } -polkadot-availability-bitfield-distribution ={path = "/Users/gav/Core/polkadot/node/network/bitfield-distribution" } -polkadot-availability-distribution ={path = "/Users/gav/Core/polkadot/node/network/availability-distribution" } -polkadot-availability-recovery ={path = "/Users/gav/Core/polkadot/node/network/availability-recovery" } -polkadot-client ={path = "/Users/gav/Core/polkadot/node/client" } -polkadot-runtime ={path = "/Users/gav/Core/polkadot/runtime/polkadot" } -rococo-runtime ={path = "/Users/gav/Core/polkadot/runtime/rococo" } -bp-messages ={path = "/Users/gav/Core/polkadot/bridges/primitives/messages" } -bp-runtime ={path = "/Users/gav/Core/polkadot/bridges/primitives/runtime" } -bp-rococo ={path = "/Users/gav/Core/polkadot/bridges/primitives/chain-rococo" } -bp-polkadot-core ={path = "/Users/gav/Core/polkadot/bridges/primitives/polkadot-core" } -bp-wococo ={path = "/Users/gav/Core/polkadot/bridges/primitives/chain-wococo" } -bridge-runtime-common ={path = "/Users/gav/Core/polkadot/bridges/bin/runtime-common" } -bp-message-dispatch ={path = "/Users/gav/Core/polkadot/bridges/primitives/message-dispatch" } -pallet-bridge-dispatch ={path = "/Users/gav/Core/polkadot/bridges/modules/dispatch" } -pallet-bridge-grandpa ={path = "/Users/gav/Core/polkadot/bridges/modules/grandpa" } -bp-header-chain ={path = "/Users/gav/Core/polkadot/bridges/primitives/header-chain" } -bp-test-utils ={path = "/Users/gav/Core/polkadot/bridges/primitives/test-utils" } -pallet-bridge-messages ={path = "/Users/gav/Core/polkadot/bridges/modules/messages" } -bp-rialto ={path = "/Users/gav/Core/polkadot/bridges/primitives/chain-rialto" } -westend-runtime ={path = "/Users/gav/Core/polkadot/runtime/westend" } -pallet-xcm-benchmarks ={path = "/Users/gav/Core/polkadot/xcm/pallet-xcm-benchmarks" } -polkadot-collator-protocol ={path = "/Users/gav/Core/polkadot/node/network/collator-protocol" } -polkadot-dispute-distribution ={path = "/Users/gav/Core/polkadot/node/network/dispute-distribution" } -polkadot-gossip-support ={path = "/Users/gav/Core/polkadot/node/network/gossip-support" } -polkadot-network-bridge ={path = "/Users/gav/Core/polkadot/node/network/bridge" } -polkadot-node-collation-generation ={path = "/Users/gav/Core/polkadot/node/collation-generation" } -polkadot-node-core-approval-voting ={path = "/Users/gav/Core/polkadot/node/core/approval-voting" } -polkadot-node-core-av-store ={path = "/Users/gav/Core/polkadot/node/core/av-store" } -polkadot-node-core-backing ={path = "/Users/gav/Core/polkadot/node/core/backing" } -polkadot-node-core-bitfield-signing ={path = "/Users/gav/Core/polkadot/node/core/bitfield-signing" } -polkadot-node-core-candidate-validation ={path = "/Users/gav/Core/polkadot/node/core/candidate-validation" } -polkadot-node-core-chain-api ={path = "/Users/gav/Core/polkadot/node/core/chain-api" } -polkadot-node-core-chain-selection ={path = "/Users/gav/Core/polkadot/node/core/chain-selection" } -polkadot-node-core-dispute-coordinator ={path = "/Users/gav/Core/polkadot/node/core/dispute-coordinator" } -polkadot-node-core-dispute-participation ={path = "/Users/gav/Core/polkadot/node/core/dispute-participation" } -polkadot-node-core-parachains-inherent ={path = "/Users/gav/Core/polkadot/node/core/parachains-inherent" } -polkadot-node-core-provisioner ={path = "/Users/gav/Core/polkadot/node/core/provisioner" } -polkadot-node-core-runtime-api ={path = "/Users/gav/Core/polkadot/node/core/runtime-api" } -polkadot-rpc ={path = "/Users/gav/Core/polkadot/rpc" } -polkadot-statement-distribution ={path = "/Users/gav/Core/polkadot/node/network/statement-distribution" } -polkadot-test-client ={path = "/Users/gav/Core/polkadot/node/test/client" } -polkadot-test-runtime ={path = "/Users/gav/Core/polkadot/runtime/test-runtime" } -polkadot-test-service ={path = "/Users/gav/Core/polkadot/node/test/service" } -xcm-executor-integration-tests ={path = "/Users/gav/Core/polkadot/xcm/xcm-executor/integration-tests" } -xcm-simulator ={path = "/Users/gav/Core/polkadot/xcm/xcm-simulator" } -xcm-simulator-example ={path = "/Users/gav/Core/polkadot/xcm/xcm-simulator/example" } -xcm-simulator-fuzzer ={path = "/Users/gav/Core/polkadot/xcm/xcm-simulator/fuzzer" } -polkadot-test-malus ={path = "/Users/gav/Core/polkadot/node/malus" } -polkadot-simnet ={path = "/Users/gav/Core/polkadot/node/test/polkadot-simnet/common" } -polkadot-simnet-node ={path = "/Users/gav/Core/polkadot/node/test/polkadot-simnet/node" } -polkadot-simnet-test ={path = "/Users/gav/Core/polkadot/node/test/polkadot-simnet/test" } -test-parachains ={path = "/Users/gav/Core/polkadot/parachain/test-parachains" } -test-parachain-adder-collator ={path = "/Users/gav/Core/polkadot/parachain/test-parachains/adder/collator" } -staking-miner ={path = "/Users/gav/Core/polkadot/utils/staking-miner" } -remote-ext-tests-bags-list ={path = "/Users/gav/Core/polkadot/utils/remote-ext-tests/bags-list" } -polkadot-voter-bags ={path = "/Users/gav/Core/polkadot/utils/voter-bags" } -polkadot ={path = "/Users/gav/Core/polkadot" } From 138f06d54cf387c26558eece6b521fa8862b2cee Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 24 Oct 2021 11:22:58 +0200 Subject: [PATCH 03/91] Undiener --- Cargo.lock | 8 ++++---- client/network/src/tests.rs | 7 ------- pallets/xcmp-queue/src/mock.rs | 2 ++ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index decd42d333d..cfce9411bbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2472,7 +2472,7 @@ dependencies = [ [[package]] name = "frame-benchmarking-cli" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "Inflector", "chrono", @@ -8225,7 +8225,7 @@ dependencies = [ [[package]] name = "remote-externalities" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "env_logger 0.9.0", "jsonrpsee-proc-macros", @@ -10904,7 +10904,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "platforms", ] @@ -11451,7 +11451,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "try-runtime-cli" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#632b32300eb9376767c2ae7b38e79b3f7f5329b1" +source = "git+https://github.com/paritytech/substrate?branch=master#969a70d1864fc5d5f6c378bcfd03f1b3ea434049" dependencies = [ "jsonrpsee-ws-client", "log", diff --git a/client/network/src/tests.rs b/client/network/src/tests.rs index 7124ebca85d..f3177dbe3a6 100644 --- a/client/network/src/tests.rs +++ b/client/network/src/tests.rs @@ -484,13 +484,6 @@ sp_api::mock_impl_runtime_apis! { BTreeMap::new() } - fn assumed_validation_data( - _: ParaId, - _: Hash, - ) -> Option<(PersistedValidationData, ValidationCodeHash)> { - None - } - fn validation_code_by_hash(_: ValidationCodeHash) -> Option { None } diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 51f19bf4a50..c0dea9bd1fa 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -146,6 +146,8 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = (); type AssetClaims = (); type SubscriptionService = (); + type PalletInstancesInfo = AllPallets; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; } pub type XcmRouter = ( From 535b2545dc020e0e0bc13c56bdde0f1e3eaa714e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 24 Oct 2021 11:38:46 +0200 Subject: [PATCH 04/91] Undiener --- client/network/src/tests.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/network/src/tests.rs b/client/network/src/tests.rs index f3177dbe3a6..7124ebca85d 100644 --- a/client/network/src/tests.rs +++ b/client/network/src/tests.rs @@ -484,6 +484,13 @@ sp_api::mock_impl_runtime_apis! { BTreeMap::new() } + fn assumed_validation_data( + _: ParaId, + _: Hash, + ) -> Option<(PersistedValidationData, ValidationCodeHash)> { + None + } + fn validation_code_by_hash(_: ValidationCodeHash) -> Option { None } From 9f5c19ffa5cd72068b415ab0b5ba7c9e6bb0f38f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 26 Oct 2021 12:55:21 +0200 Subject: [PATCH 05/91] Lockfile --- Cargo.lock | 137 ++++++++++++++++++++++++++++------------------------- 1 file changed, 73 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfce9411bbc..bbf74a6884f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -706,6 +706,7 @@ dependencies = [ [[package]] name = "bp-header-chain" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "finality-grandpa", "frame-support", @@ -721,6 +722,7 @@ dependencies = [ [[package]] name = "bp-message-dispatch" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-runtime", "frame-support", @@ -732,6 +734,7 @@ dependencies = [ [[package]] name = "bp-messages" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitvec 0.20.1", "bp-runtime", @@ -747,6 +750,7 @@ dependencies = [ [[package]] name = "bp-polkadot-core" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-messages", "bp-runtime", @@ -764,6 +768,7 @@ dependencies = [ [[package]] name = "bp-rialto" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-messages", "bp-runtime", @@ -778,6 +783,7 @@ dependencies = [ [[package]] name = "bp-rococo" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -794,6 +800,7 @@ dependencies = [ [[package]] name = "bp-runtime" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "frame-support", "hash-db", @@ -811,6 +818,7 @@ dependencies = [ [[package]] name = "bp-test-utils" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-header-chain", "ed25519-dalek", @@ -825,6 +833,7 @@ dependencies = [ [[package]] name = "bp-wococo" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -839,6 +848,7 @@ dependencies = [ [[package]] name = "bridge-runtime-common" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-message-dispatch", "bp-messages", @@ -3594,6 +3604,7 @@ dependencies = [ [[package]] name = "kusama-runtime" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -4563,6 +4574,7 @@ dependencies = [ [[package]] name = "metered-channel" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "derive_more", "futures 0.3.17", @@ -5225,6 +5237,7 @@ dependencies = [ [[package]] name = "pallet-bridge-dispatch" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-message-dispatch", "bp-runtime", @@ -5241,6 +5254,7 @@ dependencies = [ [[package]] name = "pallet-bridge-grandpa" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bp-header-chain", "bp-runtime", @@ -5262,6 +5276,7 @@ dependencies = [ [[package]] name = "pallet-bridge-messages" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitvec 0.20.1", "bp-message-dispatch", @@ -5938,6 +5953,7 @@ dependencies = [ [[package]] name = "pallet-xcm" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "frame-support", "frame-system", @@ -5955,6 +5971,7 @@ dependencies = [ [[package]] name = "pallet-xcm-benchmarks" version = "0.9.8" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "frame-benchmarking", "frame-support", @@ -6474,6 +6491,7 @@ checksum = "989d43012e2ca1c4a02507c67282691a0a3207f9dc67cec596b43fe925b3d325" [[package]] name = "polkadot-approval-distribution" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "polkadot-node-network-protocol", @@ -6487,6 +6505,7 @@ dependencies = [ [[package]] name = "polkadot-availability-bitfield-distribution" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "polkadot-node-network-protocol", @@ -6499,6 +6518,7 @@ dependencies = [ [[package]] name = "polkadot-availability-distribution" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "derive_more", "futures 0.3.17", @@ -6520,6 +6540,7 @@ dependencies = [ [[package]] name = "polkadot-availability-recovery" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "lru 0.7.0", @@ -6539,6 +6560,7 @@ dependencies = [ [[package]] name = "polkadot-cli" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "frame-benchmarking-cli", "futures 0.3.17", @@ -6558,6 +6580,7 @@ dependencies = [ [[package]] name = "polkadot-client" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "beefy-primitives", "frame-benchmarking", @@ -6655,6 +6678,7 @@ dependencies = [ [[package]] name = "polkadot-collator-protocol" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "always-assert", "derive_more", @@ -6675,6 +6699,7 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "parity-scale-codec", "parity-util-mem", @@ -6687,6 +6712,7 @@ dependencies = [ [[package]] name = "polkadot-dispute-distribution" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "derive_more", "futures 0.3.17", @@ -6708,6 +6734,7 @@ dependencies = [ [[package]] name = "polkadot-erasure-coding" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "parity-scale-codec", "polkadot-node-primitives", @@ -6721,6 +6748,7 @@ dependencies = [ [[package]] name = "polkadot-gossip-support" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -6740,6 +6768,7 @@ dependencies = [ [[package]] name = "polkadot-network-bridge" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-trait", "futures 0.3.17", @@ -6758,6 +6787,7 @@ dependencies = [ [[package]] name = "polkadot-node-collation-generation" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "parity-scale-codec", @@ -6775,6 +6805,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-approval-voting" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitvec 0.20.1", "derive_more", @@ -6802,6 +6833,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-av-store" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitvec 0.20.1", "futures 0.3.17", @@ -6821,6 +6853,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-backing" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitvec 0.20.1", "futures 0.3.17", @@ -6838,6 +6871,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-bitfield-signing" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "polkadot-node-subsystem", @@ -6852,6 +6886,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-candidate-validation" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-trait", "futures 0.3.17", @@ -6869,6 +6904,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-api" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "polkadot-node-subsystem", @@ -6883,6 +6919,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-selection" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -6899,6 +6936,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-coordinator" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitvec 0.20.1", "derive_more", @@ -6917,6 +6955,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-participation" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "polkadot-node-primitives", @@ -6929,6 +6968,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-parachains-inherent" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-trait", "futures 0.3.17", @@ -6945,6 +6985,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-provisioner" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitvec 0.20.1", "futures 0.3.17", @@ -6959,6 +7000,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "always-assert", "assert_matches", @@ -6989,6 +7031,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-runtime-api" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "memory-lru", @@ -7006,6 +7049,7 @@ dependencies = [ [[package]] name = "polkadot-node-jaeger" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-std", "lazy_static", @@ -7023,6 +7067,7 @@ dependencies = [ [[package]] name = "polkadot-node-metrics" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -7033,6 +7078,7 @@ dependencies = [ [[package]] name = "polkadot-node-network-protocol" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-trait", "derive_more", @@ -7050,6 +7096,7 @@ dependencies = [ [[package]] name = "polkadot-node-primitives" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bounded-vec", "futures 0.3.17", @@ -7071,6 +7118,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "polkadot-node-jaeger", "polkadot-node-subsystem-types", @@ -7080,6 +7128,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-test-helpers" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-trait", "futures 0.3.17", @@ -7097,6 +7146,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-types" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "derive_more", "futures 0.3.17", @@ -7115,6 +7165,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-util" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-trait", "derive_more", @@ -7141,6 +7192,7 @@ dependencies = [ [[package]] name = "polkadot-overseer" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "futures 0.3.17", "futures-timer 3.0.2", @@ -7161,6 +7213,7 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-trait", "futures 0.3.17", @@ -7177,6 +7230,7 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen-proc-macro" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "proc-macro-crate 1.1.0", "proc-macro2", @@ -7187,6 +7241,7 @@ dependencies = [ [[package]] name = "polkadot-parachain" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "derive_more", "frame-support", @@ -7203,6 +7258,7 @@ dependencies = [ [[package]] name = "polkadot-primitives" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitvec 0.20.1", "frame-system", @@ -7232,6 +7288,7 @@ dependencies = [ [[package]] name = "polkadot-rpc" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "beefy-gadget", "beefy-gadget-rpc", @@ -7262,6 +7319,7 @@ dependencies = [ [[package]] name = "polkadot-runtime" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -7338,6 +7396,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -7384,6 +7443,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-parachains" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "bitflags", "bitvec 0.20.1", @@ -7422,6 +7482,7 @@ dependencies = [ [[package]] name = "polkadot-service" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "async-trait", "beefy-gadget", @@ -7519,6 +7580,7 @@ dependencies = [ [[package]] name = "polkadot-statement-distribution" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "arrayvec 0.5.2", "derive_more", @@ -7539,6 +7601,7 @@ dependencies = [ [[package]] name = "polkadot-statement-table" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "parity-scale-codec", "polkadot-primitives", @@ -7548,6 +7611,7 @@ dependencies = [ [[package]] name = "polkadot-test-client" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "parity-scale-codec", "polkadot-node-subsystem", @@ -7572,6 +7636,7 @@ dependencies = [ [[package]] name = "polkadot-test-runtime" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -7632,6 +7697,7 @@ dependencies = [ [[package]] name = "polkadot-test-service" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "frame-benchmarking", "frame-system", @@ -8354,6 +8420,7 @@ dependencies = [ [[package]] name = "rococo-runtime" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "beefy-primitives", "bp-messages", @@ -9835,6 +9902,7 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "slot-range-helper" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "enumn", "parity-scale-codec", @@ -12027,6 +12095,7 @@ dependencies = [ [[package]] name = "westend-runtime" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "beefy-primitives", "bitvec 0.20.1", @@ -12271,6 +12340,7 @@ dependencies = [ [[package]] name = "xcm" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "derivative", "impl-trait-for-tuples", @@ -12283,6 +12353,7 @@ dependencies = [ [[package]] name = "xcm-builder" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "frame-support", "frame-system", @@ -12302,6 +12373,7 @@ dependencies = [ [[package]] name = "xcm-executor" version = "0.9.12" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "frame-benchmarking", "frame-support", @@ -12319,6 +12391,7 @@ dependencies = [ [[package]] name = "xcm-procedural" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#ee1b80aa117516918bb0591a1ec5a27030e4c64c" dependencies = [ "proc-macro2", "quote", @@ -12388,67 +12461,3 @@ dependencies = [ "cc", "libc", ] - -[[patch.unused]] -name = "polkadot" -version = "0.9.12" - -[[patch.unused]] -name = "polkadot-simnet" -version = "0.9.12" - -[[patch.unused]] -name = "polkadot-simnet-node" -version = "0.9.12" - -[[patch.unused]] -name = "polkadot-simnet-test" -version = "0.9.12" - -[[patch.unused]] -name = "polkadot-test-malus" -version = "0.9.12" - -[[patch.unused]] -name = "polkadot-voter-bags" -version = "0.9.0" - -[[patch.unused]] -name = "remote-ext-tests-bags-list" -version = "0.9.12" - -[[patch.unused]] -name = "staking-miner" -version = "0.9.12" - -[[patch.unused]] -name = "test-parachain-adder" -version = "0.9.12" - -[[patch.unused]] -name = "test-parachain-adder-collator" -version = "0.9.12" - -[[patch.unused]] -name = "test-parachain-halt" -version = "0.9.12" - -[[patch.unused]] -name = "test-parachains" -version = "0.9.12" - -[[patch.unused]] -name = "xcm-executor-integration-tests" -version = "0.9.12" - -[[patch.unused]] -name = "xcm-simulator" -version = "0.9.12" - -[[patch.unused]] -name = "xcm-simulator-example" -version = "0.9.12" - -[[patch.unused]] -name = "xcm-simulator-fuzzer" -version = "0.9.9" From fc381c9fc8d6832a9579a554febd869a7067d8c0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 20 Jan 2022 15:14:10 +0100 Subject: [PATCH 06/91] Changes for send returning hash --- pallets/xcmp-queue/src/lib.rs | 4 ++-- primitives/utility/Cargo.toml | 1 + primitives/utility/src/lib.rs | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index cab3a18e424..7a25640f9d6 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -938,7 +938,7 @@ impl XcmpMessageSource for Pallet { /// Xcm sender for sending to a sibling parachain. impl SendXcm for Pallet { - fn send_xcm(dest: impl Into, msg: Xcm<()>) -> Result<(), SendError> { + fn send_xcm(dest: impl Into, msg: Xcm<()>) -> SendResult { let dest = dest.into(); match &dest { @@ -954,7 +954,7 @@ impl SendXcm for Pallet { ) .map_err(|e| SendError::Transport(<&'static str>::from(e)))?; Self::deposit_event(Event::XcmpMessageSent(Some(hash))); - Ok(()) + Ok(hash) }, // Anything else is unhandled. This includes a message this is meant for us. _ => Err(SendError::CannotReachDestination(dest, msg)), diff --git a/primitives/utility/Cargo.toml b/primitives/utility/Cargo.toml index 41cfa53a321..e628b9126bf 100644 --- a/primitives/utility/Cargo.toml +++ b/primitives/utility/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] # Substrate dependencies sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } sp-trie = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index 69397247a39..a53e9286fc1 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -33,7 +33,7 @@ use xcm::{latest::prelude::*, WrapVersion}; /// for the `SendXcm` implementation. pub struct ParentAsUmp(PhantomData<(T, W)>); impl SendXcm for ParentAsUmp { - fn send_xcm(dest: impl Into, msg: Xcm<()>) -> Result<(), SendError> { + fn send_xcm(dest: impl Into, msg: Xcm<()>) -> SendResult { let dest = dest.into(); if dest.contains_parents_only(1) { @@ -41,10 +41,11 @@ impl SendXcm for ParentAsUmp { let versioned_xcm = W::wrap_version(&dest, msg).map_err(|()| SendError::DestinationUnsupported)?; let data = versioned_xcm.encode(); + let hash = data.using_encoded(sp_io::hashing::blake2_256); T::send_upward_message(data).map_err(|e| SendError::Transport(e.into()))?; - Ok(()) + Ok(hash) } else { // Anything else is unhandled. This includes a message this is meant for us. Err(SendError::CannotReachDestination(dest, msg)) From 23c26db563d1b96779388693a617da0dd51b0413 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 9 Mar 2022 03:11:16 -0800 Subject: [PATCH 07/91] Include message ID as params to execute_xcm --- pallets/xcm/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/xcm/src/lib.rs b/pallets/xcm/src/lib.rs index 10c306f1be9..60c5d4b3568 100644 --- a/pallets/xcm/src/lib.rs +++ b/pallets/xcm/src/lib.rs @@ -114,7 +114,7 @@ impl DmpMessageHandler for UnlimitedDmpExecution { ) -> Weight { let mut used = 0; for (_sent_at, data) in iter { - let id = sp_io::hashing::twox_64(&data[..]); + let id = sp_io::hashing::blake2_256(&data[..]); let msg = VersionedXcm::::decode_all_with_depth_limit( MAX_XCM_DECODE_DEPTH, &mut data.as_slice(), @@ -124,7 +124,7 @@ impl DmpMessageHandler for UnlimitedDmpExecution { Err(_) => Pallet::::deposit_event(Event::InvalidFormat(id)), Ok(Err(())) => Pallet::::deposit_event(Event::UnsupportedVersion(id)), Ok(Ok(x)) => { - let outcome = T::XcmExecutor::execute_xcm(Parent, x, limit); + let outcome = T::XcmExecutor::execute_xcm(Parent, x, id, limit); used += outcome.weight_used(); Pallet::::deposit_event(Event::ExecutedDownward(id, outcome)); }, @@ -147,7 +147,7 @@ impl DmpMessageHandler for LimitAndDropDmpExecution { ) -> Weight { let mut used = 0; for (_sent_at, data) in iter { - let id = sp_io::hashing::twox_64(&data[..]); + let id = sp_io::hashing::blake2_256(&data[..]); let msg = VersionedXcm::::decode_all_with_depth_limit( MAX_XCM_DECODE_DEPTH, &mut data.as_slice(), @@ -158,7 +158,7 @@ impl DmpMessageHandler for LimitAndDropDmpExecution { Ok(Err(())) => Pallet::::deposit_event(Event::UnsupportedVersion(id)), Ok(Ok(x)) => { let weight_limit = limit.saturating_sub(used); - let outcome = T::XcmExecutor::execute_xcm(Parent, x, weight_limit); + let outcome = T::XcmExecutor::execute_xcm(Parent, x, id, weight_limit); used += outcome.weight_used(); Pallet::::deposit_event(Event::ExecutedDownward(id, outcome)); }, From 6cec5c8912aa1e99615d9d844ab8c57014ec372d Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 9 Mar 2022 18:57:24 -0800 Subject: [PATCH 08/91] Fixes --- pallets/xcm/src/lib.rs | 6 +++--- primitives/utility/src/lib.rs | 35 +++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/pallets/xcm/src/lib.rs b/pallets/xcm/src/lib.rs index 60c5d4b3568..4b8432a2ab8 100644 --- a/pallets/xcm/src/lib.rs +++ b/pallets/xcm/src/lib.rs @@ -67,13 +67,13 @@ pub mod pallet { pub enum Event { /// Downward message is invalid XCM. /// \[ id \] - InvalidFormat([u8; 8]), + InvalidFormat([u8; 32]), /// Downward message is unsupported version of XCM. /// \[ id \] - UnsupportedVersion([u8; 8]), + UnsupportedVersion([u8; 32]), /// Downward message executed with the given outcome. /// \[ id, outcome \] - ExecutedDownward([u8; 8], Outcome), + ExecutedDownward([u8; 32], Outcome), } /// Origin for the parachains module. diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index a53e9286fc1..0686cd05d89 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -20,7 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::Encode; -use cumulus_primitives_core::UpwardMessageSender; +use cumulus_primitives_core::{MessageSendError, UpwardMessageSender}; use sp_std::marker::PhantomData; use xcm::{latest::prelude::*, WrapVersion}; @@ -33,22 +33,37 @@ use xcm::{latest::prelude::*, WrapVersion}; /// for the `SendXcm` implementation. pub struct ParentAsUmp(PhantomData<(T, W)>); impl SendXcm for ParentAsUmp { - fn send_xcm(dest: impl Into, msg: Xcm<()>) -> SendResult { - let dest = dest.into(); + type Ticket = Vec; - if dest.contains_parents_only(1) { + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult> { + let d = dest.take().ok_or(SendError::MissingArgument)?; + let xcm = msg.take().ok_or(SendError::MissingArgument)?; + + if d.contains_parents_only(1) { // An upward message for the relay chain. let versioned_xcm = - W::wrap_version(&dest, msg).map_err(|()| SendError::DestinationUnsupported)?; + W::wrap_version(&d, msg).map_err(|()| SendError::DestinationUnsupported)?; let data = versioned_xcm.encode(); - let hash = data.using_encoded(sp_io::hashing::blake2_256); - - T::send_upward_message(data).map_err(|e| SendError::Transport(e.into()))?; - Ok(hash) + Ok(data, MultiAssets::new()) } else { + *dest = Some(d.clone()); // Anything else is unhandled. This includes a message this is meant for us. - Err(SendError::CannotReachDestination(dest, msg)) + Err(SendError::NotApplicable(d, xcm)) } } + + fn deliver(blob: Vec) -> Result { + let hash = data.using_encoded(sp_io::hashing::blake2_256); + + T::send_upward_message(data).map_err(|e| match e { + MessageSendError::TooBig => SendError::ExceedsMaxMessageSize, + e => SendError::Transport(e.into()), + })?; + + Ok(hash) + } } From 005d37a1d497840b2b838c1f441f0f485d567982 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 10 Mar 2022 21:21:34 -0800 Subject: [PATCH 09/91] Fixes --- pallets/dmp-queue/src/lib.rs | 2 +- primitives/utility/src/lib.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 8becd30bb9b..23841190d19 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -241,7 +241,7 @@ pub mod pallet { Ok(0) }, Ok(Ok(x)) => { - let outcome = T::XcmExecutor::execute_xcm(Parent, x, limit); + let outcome = T::XcmExecutor::execute_xcm(Parent, x, id, limit); match outcome { Outcome::Error(XcmError::WeightLimitReached(required)) => Err((id, required)), diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index 0686cd05d89..f07e65a88b1 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -45,18 +45,18 @@ impl SendXcm for ParentAsUmp { if d.contains_parents_only(1) { // An upward message for the relay chain. let versioned_xcm = - W::wrap_version(&d, msg).map_err(|()| SendError::DestinationUnsupported)?; + W::wrap_version(&d, xcm).map_err(|()| SendError::DestinationUnsupported)?; let data = versioned_xcm.encode(); - Ok(data, MultiAssets::new()) + Ok((data, MultiAssets::new())) } else { - *dest = Some(d.clone()); + *dest = Some(d); // Anything else is unhandled. This includes a message this is meant for us. - Err(SendError::NotApplicable(d, xcm)) + Err(SendError::NotApplicable) } } - fn deliver(blob: Vec) -> Result { + fn deliver(data: Vec) -> Result { let hash = data.using_encoded(sp_io::hashing::blake2_256); T::send_upward_message(data).map_err(|e| match e { From a10ac2e7d23fccc9ada24bb788a5d688c7b1109d Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 10 Mar 2022 21:51:38 -0800 Subject: [PATCH 10/91] Fixes --- pallets/dmp-queue/src/lib.rs | 11 +++++++++ pallets/xcmp-queue/Cargo.toml | 3 ++- pallets/xcmp-queue/src/lib.rs | 43 +++++++++++++++++++++++------------ 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 23841190d19..f743f2f7df6 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -422,9 +422,16 @@ mod tests { pub struct MockExec; impl ExecuteXcm for MockExec { + type Prepared = Weightless; + + fn prepare(message: Xcm<()>) -> Result> { + Err(message) + } + fn execute_xcm_in_credit( _origin: impl Into, message: Xcm, + _hash: XcmHash, weight_limit: Weight, _credit: Weight, ) -> Outcome { @@ -442,6 +449,10 @@ mod tests { TRACE.with(|q| q.borrow_mut().push((message, o.clone()))); o } + + fn charge_fees(_location: impl Into, _fees: MultiAssets) -> XcmResult { + Err(XcmError::Unimplemented) + } } impl Config for Test { diff --git a/pallets/xcmp-queue/Cargo.toml b/pallets/xcmp-queue/Cargo.toml index b9439bb4434..d457e322bd4 100644 --- a/pallets/xcmp-queue/Cargo.toml +++ b/pallets/xcmp-queue/Cargo.toml @@ -13,6 +13,7 @@ scale-info = { version = "2.0.0", default-features = false, features = ["derive" # Substrate frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } @@ -30,7 +31,6 @@ frame-benchmarking = { default-features = false, optional = true, git = "https:/ # Substrate sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } # Polkadot @@ -48,6 +48,7 @@ std = [ "frame-support/std", "frame-system/std", "log/std", + "sp-io/std", "sp-runtime/std", "sp-std/std", "xcm-executor/std", diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 41e055bfdc0..03abce5291d 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -1079,26 +1079,41 @@ impl XcmpMessageSource for Pallet { /// Xcm sender for sending to a sibling parachain. impl SendXcm for Pallet { - fn send_xcm(dest: impl Into, msg: Xcm<()>) -> SendResult { - let dest = dest.into(); + type Ticket = (ParaId, VersionedXcm<()>); - match &dest { + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(ParaId, VersionedXcm<()>)> { + let d = dest.take().ok_or(SendError::MissingArgument)?; + let xcm = msg.take().ok_or(SendError::MissingArgument)?; + + match &d { // An HRMP message for a sibling parachain. MultiLocation { parents: 1, interior: X1(Parachain(id)) } => { - let versioned_xcm = T::VersionWrapper::wrap_version(&dest, msg) + let versioned_xcm = T::VersionWrapper::wrap_version(&d, xcm) .map_err(|()| SendError::DestinationUnsupported)?; - let hash = T::Hashing::hash_of(&versioned_xcm); - Self::send_fragment( - (*id).into(), - XcmpMessageFormat::ConcatenatedVersionedXcm, - versioned_xcm, - ) - .map_err(|e| SendError::Transport(<&'static str>::from(e)))?; - Self::deposit_event(Event::XcmpMessageSent(Some(hash))); - Ok(hash) + Ok(((*id).into(), versioned_xcm)) }, // Anything else is unhandled. This includes a message this is meant for us. - _ => Err(SendError::CannotReachDestination(dest, msg)), + _ => { + *dest = Some(d); + Err(SendError::NotApplicable) + } + } + } + + fn deliver((id, xcm): (ParaId, VersionedXcm<()>)) -> Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + + match Self::send_fragment(id, XcmpMessageFormat::ConcatenatedVersionedXcm, xcm) { + Ok(_) => { + Self::deposit_event(Event::XcmpMessageSent(Some(hash))); + Ok(hash) + } + Err(e) => { + Err(SendError::Transport(<&'static str>::from(e))) + } } } } From 9e70ba8317849c3eff4c9f41235779b7c85f2344 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 10 Mar 2022 22:48:48 -0800 Subject: [PATCH 11/91] Fixes --- pallets/dmp-queue/src/lib.rs | 4 ++-- pallets/xcmp-queue/src/lib.rs | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index f743f2f7df6..101bead3a5f 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -345,7 +345,7 @@ mod tests { }; use sp_version::RuntimeVersion; use std::cell::RefCell; - use xcm::latest::{MultiLocation, OriginKind}; + use xcm::latest::{MultiLocation, OriginKind, Weightless}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -424,7 +424,7 @@ mod tests { impl ExecuteXcm for MockExec { type Prepared = Weightless; - fn prepare(message: Xcm<()>) -> Result> { + fn prepare(message: Xcm) -> Result { Err(message) } diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 03abce5291d..1a9a48065c0 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -263,17 +263,17 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Some XCM was executed ok. - Success(Option), + Success(Option), /// Some XCM failed. - Fail(Option, XcmError), + Fail(Option, XcmError), /// Bad XCM version used. - BadVersion(Option), + BadVersion(Option), /// Bad XCM format used. - BadFormat(Option), + BadFormat(Option), /// An upward message was sent to the relay chain. - UpwardMessageSent(Option), + UpwardMessageSent(Option), /// An HRMP message was sent to a sibling parachain. - XcmpMessageSent(Option), + XcmpMessageSent(Option), /// An XCM exceeded the individual message weight budget. OverweightEnqueued(ParaId, RelayBlockNumber, OverweightIndex, Weight), /// An XCM from the overweight queue was executed with the given actual weight used. @@ -594,12 +594,12 @@ impl Pallet { xcm: VersionedXcm, max_weight: Weight, ) -> Result { - let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); log::debug!("Processing XCMP-XCM: {:?}", &hash); let (result, event) = match Xcm::::try_from(xcm) { Ok(xcm) => { let location = (1, Parachain(sender.into())); - match T::XcmExecutor::execute_xcm(location, xcm, max_weight) { + match T::XcmExecutor::execute_xcm(location, xcm, hash, max_weight) { Outcome::Error(e) => (Err(e.clone()), Event::Fail(Some(hash), e)), Outcome::Complete(w) => (Ok(w), Event::Success(Some(hash))), // As far as the caller is concerned, this was dispatched without error, so @@ -781,7 +781,7 @@ impl Pallet { let index = shuffled[shuffle_index]; let sender = status[index].sender; let sender_origin = T::ControllerOriginConverter::convert_origin( - (1, Parachain(sender.into())), + (Parent, Parachain(sender.into())), OriginKind::Superuser, ); let is_controller = sender_origin @@ -1093,7 +1093,7 @@ impl SendXcm for Pallet { MultiLocation { parents: 1, interior: X1(Parachain(id)) } => { let versioned_xcm = T::VersionWrapper::wrap_version(&d, xcm) .map_err(|()| SendError::DestinationUnsupported)?; - Ok(((*id).into(), versioned_xcm)) + Ok((((*id).into(), versioned_xcm), MultiAssets::new())) }, // Anything else is unhandled. This includes a message this is meant for us. _ => { From 484e63daf110271f24db81458303932576f2112b Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 10 Mar 2022 23:25:36 -0800 Subject: [PATCH 12/91] Fixes --- pallets/dmp-queue/src/lib.rs | 8 +++++--- pallets/xcmp-queue/src/lib.rs | 12 +++++------- polkadot-parachains/pallets/ping/src/lib.rs | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 101bead3a5f..7f050f09553 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -345,7 +345,10 @@ mod tests { }; use sp_version::RuntimeVersion; use std::cell::RefCell; - use xcm::latest::{MultiLocation, OriginKind, Weightless}; + use xcm::{ + latest::{MultiLocation, OriginKind}, + v3::traits::Weightless, + }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -428,12 +431,11 @@ mod tests { Err(message) } - fn execute_xcm_in_credit( + fn execute( _origin: impl Into, message: Xcm, _hash: XcmHash, weight_limit: Weight, - _credit: Weight, ) -> Outcome { let o = match (message.0.len(), &message.0.first()) { (1, Some(Transact { require_weight_at_most, .. })) => { diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 1a9a48065c0..112aaf378d7 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -52,7 +52,7 @@ use rand_chacha::{ ChaChaRng, }; use scale_info::TypeInfo; -use sp_runtime::{traits::Hash, RuntimeDebug}; +use sp_runtime::RuntimeDebug; use sp_std::{convert::TryFrom, prelude::*}; use xcm::{latest::prelude::*, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH}; use xcm_executor::traits::ConvertOrigin; @@ -598,7 +598,7 @@ impl Pallet { log::debug!("Processing XCMP-XCM: {:?}", &hash); let (result, event) = match Xcm::::try_from(xcm) { Ok(xcm) => { - let location = (1, Parachain(sender.into())); + let location = (Parent, Parachain(sender.into())); match T::XcmExecutor::execute_xcm(location, xcm, hash, max_weight) { Outcome::Error(e) => (Err(e.clone()), Event::Fail(Some(hash), e)), Outcome::Complete(w) => (Ok(w), Event::Success(Some(hash))), @@ -1099,7 +1099,7 @@ impl SendXcm for Pallet { _ => { *dest = Some(d); Err(SendError::NotApplicable) - } + }, } } @@ -1110,10 +1110,8 @@ impl SendXcm for Pallet { Ok(_) => { Self::deposit_event(Event::XcmpMessageSent(Some(hash))); Ok(hash) - } - Err(e) => { - Err(SendError::Transport(<&'static str>::from(e))) - } + }, + Err(e) => Err(SendError::Transport(<&'static str>::from(e))), } } } diff --git a/polkadot-parachains/pallets/ping/src/lib.rs b/polkadot-parachains/pallets/ping/src/lib.rs index 7ca75669c2d..ffac149a65f 100644 --- a/polkadot-parachains/pallets/ping/src/lib.rs +++ b/polkadot-parachains/pallets/ping/src/lib.rs @@ -89,8 +89,8 @@ pub mod pallet { *seq += 1; *seq }); - match T::XcmSender::send_xcm( - (1, Junction::Parachain(para.into())), + match send_xcm::( + (Parent, Junction::Parachain(para.into())), Xcm(vec![Transact { origin_kind: OriginKind::Native, require_weight_at_most: 1_000, @@ -165,8 +165,8 @@ pub mod pallet { let para = ensure_sibling_para(::Origin::from(origin))?; Self::deposit_event(Event::Pinged(para, seq, payload.clone())); - match T::XcmSender::send_xcm( - (1, Junction::Parachain(para.into())), + match send_xcm::( + (Parent, Junction::Parachain(para.into())), Xcm(vec![Transact { origin_kind: OriginKind::Native, require_weight_at_most: 1_000, From f474297b5b606f7951cf539920b064eb3bbe75d4 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Fri, 11 Mar 2022 00:22:39 -0800 Subject: [PATCH 13/91] Fixes --- pallets/dmp-queue/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 7f050f09553..2774be7f0d3 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -345,10 +345,7 @@ mod tests { }; use sp_version::RuntimeVersion; use std::cell::RefCell; - use xcm::{ - latest::{MultiLocation, OriginKind}, - v3::traits::Weightless, - }; + use xcm::latest::{MultiLocation, OriginKind}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -423,6 +420,13 @@ mod tests { }) } + pub enum Weightless {} + impl PreparedMessage for Weightless { + fn weight_of(&self) -> Weight { + unreachable!() + } + } + pub struct MockExec; impl ExecuteXcm for MockExec { type Prepared = Weightless; From ed87d188c6706e73228c44235b67688d394cd245 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 11 Mar 2022 18:13:49 +0100 Subject: [PATCH 14/91] Companion fixes --- Cargo.lock | 152 +++++++++--------- pallets/xcmp-queue/src/mock.rs | 4 +- parachain-template/runtime/src/xcm_config.rs | 22 ++- .../canvas-kusama/src/xcm_config.rs | 16 +- polkadot-parachains/pallets/ping/src/lib.rs | 14 +- .../parachains-common/src/impls.rs | 7 +- .../rococo-parachain/src/lib.rs | 23 ++- polkadot-parachains/shell/src/xcm_config.rs | 13 +- .../statemine/src/xcm_config.rs | 26 ++- .../statemint/src/xcm_config.rs | 25 ++- .../westmint/src/xcm_config.rs | 22 ++- primitives/utility/src/lib.rs | 2 +- 12 files changed, 192 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d79f6ba5227..ee303e6d5af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -732,7 +732,6 @@ dependencies = [ [[package]] name = "bp-header-chain" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "finality-grandpa", "frame-support", @@ -748,7 +747,6 @@ dependencies = [ [[package]] name = "bp-message-dispatch" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bp-runtime", "frame-support", @@ -760,7 +758,6 @@ dependencies = [ [[package]] name = "bp-messages" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bitvec", "bp-runtime", @@ -776,7 +773,6 @@ dependencies = [ [[package]] name = "bp-polkadot-core" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bp-messages", "bp-runtime", @@ -794,7 +790,6 @@ dependencies = [ [[package]] name = "bp-rococo" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -811,7 +806,6 @@ dependencies = [ [[package]] name = "bp-runtime" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-support", "hash-db", @@ -829,7 +823,6 @@ dependencies = [ [[package]] name = "bp-test-utils" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bp-header-chain", "ed25519-dalek", @@ -844,7 +837,6 @@ dependencies = [ [[package]] name = "bp-wococo" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -859,7 +851,6 @@ dependencies = [ [[package]] name = "bridge-runtime-common" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bp-message-dispatch", "bp-messages", @@ -4182,7 +4173,6 @@ dependencies = [ [[package]] name = "kusama-runtime" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "beefy-primitives", "bitvec", @@ -4270,7 +4260,6 @@ dependencies = [ [[package]] name = "kusama-runtime-constants" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-support", "polkadot-primitives", @@ -5134,7 +5123,6 @@ dependencies = [ [[package]] name = "metered-channel" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "derive_more", "futures 0.3.21", @@ -5791,7 +5779,6 @@ dependencies = [ [[package]] name = "pallet-bridge-dispatch" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bp-message-dispatch", "bp-runtime", @@ -5808,7 +5795,6 @@ dependencies = [ [[package]] name = "pallet-bridge-grandpa" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bp-header-chain", "bp-runtime", @@ -5830,7 +5816,6 @@ dependencies = [ [[package]] name = "pallet-bridge-messages" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bitvec", "bp-message-dispatch", @@ -6606,15 +6591,16 @@ dependencies = [ [[package]] name = "pallet-xcm" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", "serde", "sp-core", + "sp-io", "sp-runtime", "sp-std", "xcm", @@ -6624,7 +6610,6 @@ dependencies = [ [[package]] name = "pallet-xcm-benchmarks" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-benchmarking", "frame-support", @@ -6632,6 +6617,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", + "sp-io", "sp-runtime", "sp-std", "xcm", @@ -7185,7 +7171,6 @@ dependencies = [ [[package]] name = "polkadot-approval-distribution" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "polkadot-node-network-protocol", @@ -7199,7 +7184,6 @@ dependencies = [ [[package]] name = "polkadot-availability-bitfield-distribution" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "polkadot-node-network-protocol", @@ -7212,7 +7196,6 @@ dependencies = [ [[package]] name = "polkadot-availability-distribution" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "derive_more", "fatality", @@ -7235,7 +7218,6 @@ dependencies = [ [[package]] name = "polkadot-availability-recovery" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "fatality", "futures 0.3.21", @@ -7256,7 +7238,6 @@ dependencies = [ [[package]] name = "polkadot-cli" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "clap 3.1.6", "frame-benchmarking-cli", @@ -7279,7 +7260,6 @@ dependencies = [ [[package]] name = "polkadot-client" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "beefy-primitives", "frame-benchmarking", @@ -7385,7 +7365,6 @@ dependencies = [ [[package]] name = "polkadot-collator-protocol" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "always-assert", "fatality", @@ -7406,7 +7385,6 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "parity-scale-codec", "parity-util-mem", @@ -7419,7 +7397,6 @@ dependencies = [ [[package]] name = "polkadot-dispute-distribution" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "derive_more", "fatality", @@ -7442,7 +7419,6 @@ dependencies = [ [[package]] name = "polkadot-erasure-coding" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "parity-scale-codec", "polkadot-node-primitives", @@ -7456,7 +7432,6 @@ dependencies = [ [[package]] name = "polkadot-gossip-support" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7476,7 +7451,6 @@ dependencies = [ [[package]] name = "polkadot-network-bridge" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-trait", "futures 0.3.21", @@ -7495,7 +7469,6 @@ dependencies = [ [[package]] name = "polkadot-node-collation-generation" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "parity-scale-codec", @@ -7513,7 +7486,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-approval-voting" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bitvec", "derive_more", @@ -7541,7 +7513,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-av-store" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bitvec", "futures 0.3.21", @@ -7561,7 +7532,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-backing" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bitvec", "futures 0.3.21", @@ -7579,7 +7549,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-bitfield-signing" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "polkadot-node-subsystem", @@ -7594,7 +7563,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-candidate-validation" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-trait", "futures 0.3.21", @@ -7612,7 +7580,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-api" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "polkadot-node-subsystem", @@ -7627,7 +7594,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-selection" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7644,7 +7610,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-coordinator" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "fatality", "futures 0.3.21", @@ -7663,7 +7628,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-parachains-inherent" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-trait", "futures 0.3.21", @@ -7680,7 +7644,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-provisioner" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bitvec", "futures 0.3.21", @@ -7697,7 +7660,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "always-assert", "assert_matches", @@ -7727,7 +7689,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf-checker" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "polkadot-node-primitives", @@ -7743,7 +7704,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-runtime-api" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "memory-lru", @@ -7761,7 +7721,6 @@ dependencies = [ [[package]] name = "polkadot-node-jaeger" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-std", "lazy_static", @@ -7779,7 +7738,6 @@ dependencies = [ [[package]] name = "polkadot-node-metrics" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bs58", "futures 0.3.21", @@ -7798,7 +7756,6 @@ dependencies = [ [[package]] name = "polkadot-node-network-protocol" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-trait", "fatality", @@ -7816,7 +7773,6 @@ dependencies = [ [[package]] name = "polkadot-node-primitives" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bounded-vec", "futures 0.3.21", @@ -7838,7 +7794,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "polkadot-node-jaeger", "polkadot-node-subsystem-types", @@ -7848,7 +7803,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-test-helpers" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-trait", "futures 0.3.21", @@ -7866,7 +7820,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-types" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "derive_more", "futures 0.3.21", @@ -7885,7 +7838,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-util" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-trait", "derive_more", @@ -7918,7 +7870,6 @@ dependencies = [ [[package]] name = "polkadot-overseer" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7939,7 +7890,6 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-trait", "futures 0.3.21", @@ -7956,7 +7906,6 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen-proc-macro" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "expander 0.0.5", "proc-macro-crate 1.1.3", @@ -7968,7 +7917,6 @@ dependencies = [ [[package]] name = "polkadot-parachain" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "derive_more", "frame-support", @@ -7985,7 +7933,6 @@ dependencies = [ [[package]] name = "polkadot-performance-test" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "env_logger", "kusama-runtime", @@ -8000,7 +7947,6 @@ dependencies = [ [[package]] name = "polkadot-primitives" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bitvec", "frame-system", @@ -8030,7 +7976,6 @@ dependencies = [ [[package]] name = "polkadot-rpc" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "beefy-gadget", "beefy-gadget-rpc", @@ -8061,7 +8006,6 @@ dependencies = [ [[package]] name = "polkadot-runtime" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "beefy-primitives", "bitvec", @@ -8145,7 +8089,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "beefy-primitives", "bitvec", @@ -8192,7 +8135,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-constants" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-support", "polkadot-primitives", @@ -8204,7 +8146,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-metrics" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bs58", "parity-scale-codec", @@ -8216,7 +8157,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-parachains" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "bitflags", "bitvec", @@ -8258,7 +8198,6 @@ dependencies = [ [[package]] name = "polkadot-service" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "async-trait", "beefy-gadget", @@ -8359,7 +8298,6 @@ dependencies = [ [[package]] name = "polkadot-statement-distribution" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "arrayvec 0.5.2", "fatality", @@ -8380,7 +8318,6 @@ dependencies = [ [[package]] name = "polkadot-statement-table" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "parity-scale-codec", "polkadot-primitives", @@ -8390,7 +8327,6 @@ dependencies = [ [[package]] name = "polkadot-test-client" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "parity-scale-codec", "polkadot-node-subsystem", @@ -8415,7 +8351,6 @@ dependencies = [ [[package]] name = "polkadot-test-runtime" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "beefy-primitives", "bitvec", @@ -8477,7 +8412,6 @@ dependencies = [ [[package]] name = "polkadot-test-service" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-benchmarking", "frame-system", @@ -9137,7 +9071,6 @@ dependencies = [ [[package]] name = "rococo-runtime" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "beefy-primitives", "bp-messages", @@ -9212,7 +9145,6 @@ dependencies = [ [[package]] name = "rococo-runtime-constants" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-support", "polkadot-primitives", @@ -10801,7 +10733,6 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "slot-range-helper" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "enumn", "parity-scale-codec", @@ -12054,7 +11985,6 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-runtime-constants" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-support", "polkadot-primitives", @@ -13052,7 +12982,6 @@ dependencies = [ [[package]] name = "westend-runtime" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "beefy-primitives", "bitvec", @@ -13138,7 +13067,6 @@ dependencies = [ [[package]] name = "westend-runtime-constants" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-support", "polkadot-primitives", @@ -13358,23 +13286,23 @@ dependencies = [ [[package]] name = "xcm" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "derivative", "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", + "sp-io", "xcm-procedural", ] [[package]] name = "xcm-builder" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "pallet-transaction-payment", "parity-scale-codec", @@ -13391,7 +13319,6 @@ dependencies = [ [[package]] name = "xcm-executor" version = "0.9.17" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "frame-benchmarking", "frame-support", @@ -13409,7 +13336,6 @@ dependencies = [ [[package]] name = "xcm-procedural" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#dead598b50f8b946ed90cd437a38e73231d768a8" dependencies = [ "Inflector", "proc-macro2", @@ -13480,3 +13406,71 @@ dependencies = [ "cc", "libc", ] + +[[patch.unused]] +name = "polkadot" +version = "0.9.17" + +[[patch.unused]] +name = "polkadot-primitives-test-helpers" +version = "0.9.17" + +[[patch.unused]] +name = "polkadot-test-malus" +version = "0.9.17" + +[[patch.unused]] +name = "polkadot-voter-bags" +version = "0.9.17" + +[[patch.unused]] +name = "remote-ext-tests-bags-list" +version = "0.9.17" + +[[patch.unused]] +name = "staking-miner" +version = "0.9.17" + +[[patch.unused]] +name = "test-parachain-adder" +version = "0.9.17" + +[[patch.unused]] +name = "test-parachain-adder-collator" +version = "0.9.17" + +[[patch.unused]] +name = "test-parachain-halt" +version = "0.9.17" + +[[patch.unused]] +name = "test-parachain-undying" +version = "0.9.17" + +[[patch.unused]] +name = "test-parachain-undying-collator" +version = "0.9.17" + +[[patch.unused]] +name = "test-parachains" +version = "0.9.17" + +[[patch.unused]] +name = "xcm-executor-integration-tests" +version = "0.9.17" + +[[patch.unused]] +name = "xcm-simulator" +version = "0.9.17" + +[[patch.unused]] +name = "xcm-simulator-example" +version = "0.9.17" + +[[patch.unused]] +name = "xcm-simulator-fuzzer" +version = "0.9.17" + +[[patch.unused]] +name = "zombienet-backchannel" +version = "0.9.17" diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 4d8bf0b2a41..18a099e5ab3 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -113,7 +113,7 @@ impl cumulus_pallet_parachain_system::Config for Test { parameter_types! { pub const RelayChain: MultiLocation = MultiLocation::parent(); - pub Ancestry: MultiLocation = X1(Parachain(1u32.into())).into(); + pub UniversalLocation: InteriorMultiLocation = X1(Parachain(1u32.into())).into(); pub UnitWeightCost: Weight = 1_000_000; pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; @@ -144,7 +144,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = (); type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Barrier = (); type Weigher = FixedWeightBounds; type Trader = (); diff --git a/parachain-template/runtime/src/xcm_config.rs b/parachain-template/runtime/src/xcm_config.rs index 0684020fea2..bf7a717c26c 100644 --- a/parachain-template/runtime/src/xcm_config.rs +++ b/parachain-template/runtime/src/xcm_config.rs @@ -4,7 +4,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing}, + traits::{Everything, Nothing, ConstU32}, weights::Weight, }; use pallet_xcm::XcmPassthrough; @@ -22,9 +22,9 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Any; + pub const RelayNetwork: Option = None; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); } /// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used @@ -104,7 +104,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = (); // Teleporting is disabled. - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -113,8 +113,13 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstanceInfo = (); + type PalletInstancesInfo = (); type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } /// No local origins on this chain are allowed to dispatch XCM sends/executions. @@ -141,13 +146,18 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Nothing; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; // ^ Override for AdvertisedXcmVersion default type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/polkadot-parachains/canvas-kusama/src/xcm_config.rs b/polkadot-parachains/canvas-kusama/src/xcm_config.rs index c129d4d0162..33d563f165b 100644 --- a/polkadot-parachains/canvas-kusama/src/xcm_config.rs +++ b/polkadot-parachains/canvas-kusama/src/xcm_config.rs @@ -40,7 +40,7 @@ parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Any; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); pub const Local: MultiLocation = Here.into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); pub const ExecutiveBody: BodyId = BodyId::Executive; @@ -136,7 +136,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = UsingComponents; @@ -144,6 +144,11 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } /// Converts a local signed origin into an XCM multilocation. @@ -172,11 +177,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/polkadot-parachains/pallets/ping/src/lib.rs b/polkadot-parachains/pallets/ping/src/lib.rs index ffac149a65f..621190bc79e 100644 --- a/polkadot-parachains/pallets/ping/src/lib.rs +++ b/polkadot-parachains/pallets/ping/src/lib.rs @@ -69,9 +69,9 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - PingSent(ParaId, u32, Vec), + PingSent(ParaId, u32, Vec, XcmHash, MultiAssets), Pinged(ParaId, u32, Vec), - PongSent(ParaId, u32, Vec), + PongSent(ParaId, u32, Vec, XcmHash, MultiAssets), Ponged(ParaId, u32, Vec, T::BlockNumber), ErrorSendingPing(SendError, ParaId, u32, Vec), ErrorSendingPong(SendError, ParaId, u32, Vec), @@ -90,7 +90,7 @@ pub mod pallet { *seq }); match send_xcm::( - (Parent, Junction::Parachain(para.into())), + (Parent, Junction::Parachain(para.into())).into(), Xcm(vec![Transact { origin_kind: OriginKind::Native, require_weight_at_most: 1_000, @@ -102,9 +102,9 @@ pub mod pallet { .into(), }]), ) { - Ok(()) => { + Ok((hash, cost)) => { Pings::::insert(seq, n); - Self::deposit_event(Event::PingSent(para, seq, payload)); + Self::deposit_event(Event::PingSent(para, seq, payload, hash, cost)); }, Err(e) => { Self::deposit_event(Event::ErrorSendingPing(e, para, seq, payload)); @@ -166,7 +166,7 @@ pub mod pallet { Self::deposit_event(Event::Pinged(para, seq, payload.clone())); match send_xcm::( - (Parent, Junction::Parachain(para.into())), + (Parent, Junction::Parachain(para.into())).into(), Xcm(vec![Transact { origin_kind: OriginKind::Native, require_weight_at_most: 1_000, @@ -178,7 +178,7 @@ pub mod pallet { .into(), }]), ) { - Ok(()) => Self::deposit_event(Event::PongSent(para, seq, payload)), + Ok((hash, cost)) => Self::deposit_event(Event::PongSent(para, seq, payload, hash, cost)), Err(e) => Self::deposit_event(Event::ErrorSendingPong(e, para, seq, payload)), } Ok(()) diff --git a/polkadot-parachains/parachains-common/src/impls.rs b/polkadot-parachains/parachains-common/src/impls.rs index 4a2411c2cf3..2b66cd416d1 100644 --- a/polkadot-parachains/parachains-common/src/impls.rs +++ b/polkadot-parachains/parachains-common/src/impls.rs @@ -18,13 +18,12 @@ use frame_support::traits::{ fungibles::{self, Balanced, CreditOf}, - Contains, Currency, Get, Imbalance, OnUnbalanced, + Contains, ContainsPair, Currency, Get, Imbalance, OnUnbalanced, }; use pallet_asset_tx_payment::HandleCredit; use sp_runtime::traits::Zero; use sp_std::marker::PhantomData; use xcm::latest::{AssetId, Fungibility::Fungible, MultiAsset, MultiLocation}; -use xcm_executor::traits::FilterAssetLocation; /// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type. pub type NegativeImbalance = as Currency< @@ -100,8 +99,8 @@ where /// Asset filter that allows all assets from a certain location. pub struct AssetsFrom(PhantomData); -impl> FilterAssetLocation for AssetsFrom { - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl> ContainsPair for AssetsFrom { + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { let loc = T::get(); &loc == origin && matches!(asset, MultiAsset { id: AssetId::Concrete(asset_loc), fun: Fungible(_a) } diff --git a/polkadot-parachains/rococo-parachain/src/lib.rs b/polkadot-parachains/rococo-parachain/src/lib.rs index b7c30ebb1c9..8697a63fbac 100644 --- a/polkadot-parachains/rococo-parachain/src/lib.rs +++ b/polkadot-parachains/rococo-parachain/src/lib.rs @@ -22,6 +22,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +use frame_support::traits::{Nothing, ConstU32}; use sp_api::impl_runtime_apis; use sp_core::OpaqueMetadata; use sp_runtime::{ @@ -62,7 +63,7 @@ use parachains_common::{ }; use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, AsPrefixedGeneralIndex, - ConvertedConcreteAssetId, FungiblesAdapter, + ConvertedConcreteId, FungiblesAdapter, }; use xcm_executor::traits::JustTry; @@ -275,7 +276,7 @@ parameter_types! { pub const RocLocation: MultiLocation = MultiLocation::parent(); pub const RococoNetwork: NetworkId = NetworkId::Polkadot; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); } @@ -310,7 +311,7 @@ pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation: Assets, // Use this currency when it is a fungible asset matching the given location or name: - ConvertedConcreteAssetId< + ConvertedConcreteId< AssetId, u64, AsPrefixedGeneralIndex, @@ -403,7 +404,7 @@ impl Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = Reserves; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of ROC - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = UsingComponents, RocLocation, AccountId, Balances, ()>; @@ -413,6 +414,11 @@ impl Config for XcmConfig { type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = (); type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. @@ -435,13 +441,18 @@ impl pallet_xcm::Config for Runtime { type XcmExecuteFilter = Everything; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = frame_support::traits::Nothing; + type XcmReserveTransferFilter = Nothing; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/polkadot-parachains/shell/src/xcm_config.rs b/polkadot-parachains/shell/src/xcm_config.rs index dad6b053a55..88bc28b7285 100644 --- a/polkadot-parachains/shell/src/xcm_config.rs +++ b/polkadot-parachains/shell/src/xcm_config.rs @@ -14,7 +14,7 @@ // limitations under the License. use super::{AccountId, Call, Event, Origin, ParachainInfo, Runtime}; -use frame_support::{match_types, parameter_types, weights::Weight}; +use frame_support::{match_types, parameter_types, weights::Weight, traits::Nothing}; use xcm::latest::prelude::*; use xcm_builder::{ AllowUnpaidExecutionFrom, FixedWeightBounds, LocationInverter, ParentAsSuperuser, @@ -24,7 +24,7 @@ use xcm_builder::{ parameter_types! { pub const RococoLocation: MultiLocation = MultiLocation::parent(); pub const RococoNetwork: NetworkId = NetworkId::Polkadot; - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); } /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, @@ -59,7 +59,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = (); // balances not supported type IsTeleporter = (); // balances not supported - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Barrier = AllowUnpaidExecutionFrom; type Weigher = FixedWeightBounds; // balances not supported type Trader = (); // balances not supported @@ -67,8 +67,13 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = (); // don't trap for now type AssetClaims = (); // don't claim for now type SubscriptionService = (); // don't handle subscriptions for now - type PalletInstanceInfo = (); + type PalletInstancesInfo = (); type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/polkadot-parachains/statemine/src/xcm_config.rs b/polkadot-parachains/statemine/src/xcm_config.rs index 726603d5789..5adcf22d066 100644 --- a/polkadot-parachains/statemine/src/xcm_config.rs +++ b/polkadot-parachains/statemine/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing, PalletInfoAccess}, + traits::{Everything, Nothing, PalletInfoAccess, ConstU32}, weights::Weight, }; use pallet_xcm::XcmPassthrough; @@ -29,7 +29,7 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteAssetId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, @@ -41,8 +41,8 @@ parameter_types! { pub const KsmLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Kusama; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); - pub const Local: MultiLocation = Here.into(); + pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + pub const Local: MultiLocation = Here.into_location(); pub AssetsPalletLocation: MultiLocation = PalletInstance(::index() as u8).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); @@ -79,7 +79,7 @@ pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation: Assets, // Use this currency when it is a fungible asset matching the given location or name: - ConvertedConcreteAssetId< + ConvertedConcreteId< AssetId, Balance, AsPrefixedGeneralIndex, @@ -159,7 +159,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of KSM - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -168,8 +168,13 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstanceInfo = (); + type PalletInstancesInfo = (); type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } /// Converts a local signed origin into an XCM multilocation. @@ -198,11 +203,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/polkadot-parachains/statemint/src/xcm_config.rs b/polkadot-parachains/statemint/src/xcm_config.rs index 380282df56b..e12578567b7 100644 --- a/polkadot-parachains/statemint/src/xcm_config.rs +++ b/polkadot-parachains/statemint/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing, PalletInfoAccess}, + traits::{Everything, Nothing, PalletInfoAccess, ConstU32}, weights::Weight, }; use pallet_xcm::XcmPassthrough; @@ -29,7 +29,7 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteAssetId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, @@ -41,7 +41,7 @@ parameter_types! { pub const DotLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Polkadot; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); pub const Local: MultiLocation = Here.into(); pub AssetsPalletLocation: MultiLocation = PalletInstance(::index() as u8).into(); @@ -79,7 +79,7 @@ pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation: Assets, // Use this currency when it is a fungible asset matching the given location or name: - ConvertedConcreteAssetId< + ConvertedConcreteId< AssetId, Balance, AsPrefixedGeneralIndex, @@ -159,7 +159,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of DOT - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -168,8 +168,13 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstanceInfo = (); + type PalletInstancesInfo = (); type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } /// Converts a local signed origin into an XCM multilocation. @@ -198,12 +203,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Nothing; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; -} + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>;} impl cumulus_pallet_xcm::Config for Runtime { type Event = Event; diff --git a/polkadot-parachains/westmint/src/xcm_config.rs b/polkadot-parachains/westmint/src/xcm_config.rs index e59336864a2..54acfeb2a8a 100644 --- a/polkadot-parachains/westmint/src/xcm_config.rs +++ b/polkadot-parachains/westmint/src/xcm_config.rs @@ -29,7 +29,7 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteAssetId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, @@ -41,7 +41,7 @@ parameter_types! { pub const WestendLocation: MultiLocation = MultiLocation::parent(); pub RelayNetwork: NetworkId = NetworkId::Named(b"Westend".to_vec()); pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); pub const Local: MultiLocation = Here.into(); pub AssetsPalletLocation: MultiLocation = PalletInstance(::index() as u8).into(); @@ -79,7 +79,7 @@ pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation: Assets, // Use this currency when it is a fungible asset matching the given location or name: - ConvertedConcreteAssetId< + ConvertedConcreteId< AssetId, Balance, AsPrefixedGeneralIndex, @@ -155,7 +155,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of WND - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -164,8 +164,13 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstanceInfo = (); + type PalletInstancesInfo = (); type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. @@ -190,11 +195,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index f07e65a88b1..d48045dbbb4 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -21,7 +21,7 @@ use codec::Encode; use cumulus_primitives_core::{MessageSendError, UpwardMessageSender}; -use sp_std::marker::PhantomData; +use sp_std::{prelude::*, marker::PhantomData}; use xcm::{latest::prelude::*, WrapVersion}; /// Xcm router which recognises the `Parent` destination and handles it by sending the message into From e5cdda0d15aa8ee0edd8832bf94cd2f1991ecea9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 11 Mar 2022 18:28:32 +0100 Subject: [PATCH 15/91] Formatting --- parachain-template/runtime/src/xcm_config.rs | 2 +- polkadot-parachains/pallets/ping/src/lib.rs | 3 ++- polkadot-parachains/rococo-parachain/src/lib.rs | 6 +++--- polkadot-parachains/shell/src/xcm_config.rs | 2 +- polkadot-parachains/statemine/src/xcm_config.rs | 6 +++--- polkadot-parachains/statemint/src/xcm_config.rs | 9 +++++---- polkadot-parachains/westmint/src/xcm_config.rs | 4 ++-- primitives/utility/src/lib.rs | 2 +- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/parachain-template/runtime/src/xcm_config.rs b/parachain-template/runtime/src/xcm_config.rs index bf7a717c26c..fc394a05d44 100644 --- a/parachain-template/runtime/src/xcm_config.rs +++ b/parachain-template/runtime/src/xcm_config.rs @@ -4,7 +4,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing, ConstU32}, + traits::{ConstU32, Everything, Nothing}, weights::Weight, }; use pallet_xcm::XcmPassthrough; diff --git a/polkadot-parachains/pallets/ping/src/lib.rs b/polkadot-parachains/pallets/ping/src/lib.rs index 621190bc79e..aa83a2ce5b5 100644 --- a/polkadot-parachains/pallets/ping/src/lib.rs +++ b/polkadot-parachains/pallets/ping/src/lib.rs @@ -178,7 +178,8 @@ pub mod pallet { .into(), }]), ) { - Ok((hash, cost)) => Self::deposit_event(Event::PongSent(para, seq, payload, hash, cost)), + Ok((hash, cost)) => + Self::deposit_event(Event::PongSent(para, seq, payload, hash, cost)), Err(e) => Self::deposit_event(Event::ErrorSendingPong(e, para, seq, payload)), } Ok(()) diff --git a/polkadot-parachains/rococo-parachain/src/lib.rs b/polkadot-parachains/rococo-parachain/src/lib.rs index 8697a63fbac..41b22f6d38c 100644 --- a/polkadot-parachains/rococo-parachain/src/lib.rs +++ b/polkadot-parachains/rococo-parachain/src/lib.rs @@ -22,7 +22,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use frame_support::traits::{Nothing, ConstU32}; +use frame_support::traits::{ConstU32, Nothing}; use sp_api::impl_runtime_apis; use sp_core::OpaqueMetadata; use sp_runtime::{ @@ -62,8 +62,8 @@ use parachains_common::{ AssetId, }; use xcm_builder::{ - AllowKnownQueryResponses, AllowSubscriptionsFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, FungiblesAdapter, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, + FungiblesAdapter, }; use xcm_executor::traits::JustTry; diff --git a/polkadot-parachains/shell/src/xcm_config.rs b/polkadot-parachains/shell/src/xcm_config.rs index 88bc28b7285..0d491043af1 100644 --- a/polkadot-parachains/shell/src/xcm_config.rs +++ b/polkadot-parachains/shell/src/xcm_config.rs @@ -14,7 +14,7 @@ // limitations under the License. use super::{AccountId, Call, Event, Origin, ParachainInfo, Runtime}; -use frame_support::{match_types, parameter_types, weights::Weight, traits::Nothing}; +use frame_support::{match_types, parameter_types, traits::Nothing, weights::Weight}; use xcm::latest::prelude::*; use xcm_builder::{ AllowUnpaidExecutionFrom, FixedWeightBounds, LocationInverter, ParentAsSuperuser, diff --git a/polkadot-parachains/statemine/src/xcm_config.rs b/polkadot-parachains/statemine/src/xcm_config.rs index 5adcf22d066..a1f19a919cb 100644 --- a/polkadot-parachains/statemine/src/xcm_config.rs +++ b/polkadot-parachains/statemine/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing, PalletInfoAccess, ConstU32}, + traits::{ConstU32, Everything, Nothing, PalletInfoAccess}, weights::Weight, }; use pallet_xcm::XcmPassthrough; @@ -29,8 +29,8 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, - FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, + IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, diff --git a/polkadot-parachains/statemint/src/xcm_config.rs b/polkadot-parachains/statemint/src/xcm_config.rs index e12578567b7..52a7e845736 100644 --- a/polkadot-parachains/statemint/src/xcm_config.rs +++ b/polkadot-parachains/statemint/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing, PalletInfoAccess, ConstU32}, + traits::{ConstU32, Everything, Nothing, PalletInfoAccess}, weights::Weight, }; use pallet_xcm::XcmPassthrough; @@ -29,8 +29,8 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, - FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, + IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -212,7 +212,8 @@ impl pallet_xcm::Config for Runtime { type CurrencyMatcher = (); type TrustedLockers = (); type SovereignAccountOf = LocationToAccountId; - type MaxLockers = ConstU32<8>;} + type MaxLockers = ConstU32<8>; +} impl cumulus_pallet_xcm::Config for Runtime { type Event = Event; diff --git a/polkadot-parachains/westmint/src/xcm_config.rs b/polkadot-parachains/westmint/src/xcm_config.rs index 54acfeb2a8a..5785254b340 100644 --- a/polkadot-parachains/westmint/src/xcm_config.rs +++ b/polkadot-parachains/westmint/src/xcm_config.rs @@ -29,8 +29,8 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, - FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, + IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index d48045dbbb4..f99ebce9ec6 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -21,7 +21,7 @@ use codec::Encode; use cumulus_primitives_core::{MessageSendError, UpwardMessageSender}; -use sp_std::{prelude::*, marker::PhantomData}; +use sp_std::{marker::PhantomData, prelude::*}; use xcm::{latest::prelude::*, WrapVersion}; /// Xcm router which recognises the `Parent` destination and handles it by sending the message into From 8e73c005e9618efd348f437095204d8626cab535 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 11 Mar 2022 18:45:17 +0100 Subject: [PATCH 16/91] Fixes --- Cargo.lock | 152 +++++++++--------- .../canvas-kusama/src/xcm_config.rs | 8 +- .../statemint/src/xcm_config.rs | 2 +- .../westmint/src/xcm_config.rs | 10 +- 4 files changed, 84 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c05122fe15d..9a49377747d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -732,7 +732,6 @@ dependencies = [ [[package]] name = "bp-header-chain" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "finality-grandpa", "frame-support", @@ -748,7 +747,6 @@ dependencies = [ [[package]] name = "bp-message-dispatch" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bp-runtime", "frame-support", @@ -760,7 +758,6 @@ dependencies = [ [[package]] name = "bp-messages" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bitvec", "bp-runtime", @@ -776,7 +773,6 @@ dependencies = [ [[package]] name = "bp-polkadot-core" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bp-messages", "bp-runtime", @@ -794,7 +790,6 @@ dependencies = [ [[package]] name = "bp-rococo" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -811,7 +806,6 @@ dependencies = [ [[package]] name = "bp-runtime" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-support", "hash-db", @@ -829,7 +823,6 @@ dependencies = [ [[package]] name = "bp-test-utils" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bp-header-chain", "ed25519-dalek", @@ -844,7 +837,6 @@ dependencies = [ [[package]] name = "bp-wococo" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -859,7 +851,6 @@ dependencies = [ [[package]] name = "bridge-runtime-common" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bp-message-dispatch", "bp-messages", @@ -4182,7 +4173,6 @@ dependencies = [ [[package]] name = "kusama-runtime" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "beefy-primitives", "bitvec", @@ -4270,7 +4260,6 @@ dependencies = [ [[package]] name = "kusama-runtime-constants" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-support", "polkadot-primitives", @@ -5134,7 +5123,6 @@ dependencies = [ [[package]] name = "metered-channel" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "derive_more", "futures 0.3.21", @@ -5791,7 +5779,6 @@ dependencies = [ [[package]] name = "pallet-bridge-dispatch" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bp-message-dispatch", "bp-runtime", @@ -5808,7 +5795,6 @@ dependencies = [ [[package]] name = "pallet-bridge-grandpa" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bp-header-chain", "bp-runtime", @@ -5830,7 +5816,6 @@ dependencies = [ [[package]] name = "pallet-bridge-messages" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bitvec", "bp-message-dispatch", @@ -6606,15 +6591,16 @@ dependencies = [ [[package]] name = "pallet-xcm" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", "serde", "sp-core", + "sp-io", "sp-runtime", "sp-std", "xcm", @@ -6624,7 +6610,6 @@ dependencies = [ [[package]] name = "pallet-xcm-benchmarks" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-benchmarking", "frame-support", @@ -6632,6 +6617,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", + "sp-io", "sp-runtime", "sp-std", "xcm", @@ -7186,7 +7172,6 @@ dependencies = [ [[package]] name = "polkadot-approval-distribution" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "polkadot-node-network-protocol", @@ -7200,7 +7185,6 @@ dependencies = [ [[package]] name = "polkadot-availability-bitfield-distribution" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "polkadot-node-network-protocol", @@ -7213,7 +7197,6 @@ dependencies = [ [[package]] name = "polkadot-availability-distribution" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "derive_more", "fatality", @@ -7236,7 +7219,6 @@ dependencies = [ [[package]] name = "polkadot-availability-recovery" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "fatality", "futures 0.3.21", @@ -7257,7 +7239,6 @@ dependencies = [ [[package]] name = "polkadot-cli" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "clap 3.1.6", "frame-benchmarking-cli", @@ -7280,7 +7261,6 @@ dependencies = [ [[package]] name = "polkadot-client" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "beefy-primitives", "frame-benchmarking", @@ -7386,7 +7366,6 @@ dependencies = [ [[package]] name = "polkadot-collator-protocol" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "always-assert", "fatality", @@ -7407,7 +7386,6 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "parity-scale-codec", "parity-util-mem", @@ -7420,7 +7398,6 @@ dependencies = [ [[package]] name = "polkadot-dispute-distribution" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "derive_more", "fatality", @@ -7443,7 +7420,6 @@ dependencies = [ [[package]] name = "polkadot-erasure-coding" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "parity-scale-codec", "polkadot-node-primitives", @@ -7457,7 +7433,6 @@ dependencies = [ [[package]] name = "polkadot-gossip-support" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7477,7 +7452,6 @@ dependencies = [ [[package]] name = "polkadot-network-bridge" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-trait", "futures 0.3.21", @@ -7496,7 +7470,6 @@ dependencies = [ [[package]] name = "polkadot-node-collation-generation" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "parity-scale-codec", @@ -7514,7 +7487,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-approval-voting" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bitvec", "derive_more", @@ -7542,7 +7514,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-av-store" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bitvec", "futures 0.3.21", @@ -7562,7 +7533,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-backing" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bitvec", "futures 0.3.21", @@ -7580,7 +7550,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-bitfield-signing" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "polkadot-node-subsystem", @@ -7595,7 +7564,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-candidate-validation" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-trait", "futures 0.3.21", @@ -7613,7 +7581,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-api" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "polkadot-node-subsystem", @@ -7628,7 +7595,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-selection" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7645,7 +7611,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-coordinator" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "fatality", "futures 0.3.21", @@ -7664,7 +7629,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-parachains-inherent" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-trait", "futures 0.3.21", @@ -7681,7 +7645,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-provisioner" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bitvec", "futures 0.3.21", @@ -7698,7 +7661,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "always-assert", "assert_matches", @@ -7728,7 +7690,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf-checker" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "polkadot-node-primitives", @@ -7744,7 +7705,6 @@ dependencies = [ [[package]] name = "polkadot-node-core-runtime-api" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "memory-lru", @@ -7762,7 +7722,6 @@ dependencies = [ [[package]] name = "polkadot-node-jaeger" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-std", "lazy_static", @@ -7780,7 +7739,6 @@ dependencies = [ [[package]] name = "polkadot-node-metrics" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bs58", "futures 0.3.21", @@ -7799,7 +7757,6 @@ dependencies = [ [[package]] name = "polkadot-node-network-protocol" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-trait", "fatality", @@ -7817,7 +7774,6 @@ dependencies = [ [[package]] name = "polkadot-node-primitives" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bounded-vec", "futures 0.3.21", @@ -7839,7 +7795,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "polkadot-node-jaeger", "polkadot-node-subsystem-types", @@ -7849,7 +7804,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-test-helpers" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-trait", "futures 0.3.21", @@ -7867,7 +7821,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-types" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "derive_more", "futures 0.3.21", @@ -7886,7 +7839,6 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-util" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-trait", "derive_more", @@ -7919,7 +7871,6 @@ dependencies = [ [[package]] name = "polkadot-overseer" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7940,7 +7891,6 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-trait", "futures 0.3.21", @@ -7957,7 +7907,6 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen-proc-macro" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "expander 0.0.6", "proc-macro-crate 1.1.3", @@ -7969,7 +7918,6 @@ dependencies = [ [[package]] name = "polkadot-parachain" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "derive_more", "frame-support", @@ -7986,7 +7934,6 @@ dependencies = [ [[package]] name = "polkadot-performance-test" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "env_logger", "kusama-runtime", @@ -8001,7 +7948,6 @@ dependencies = [ [[package]] name = "polkadot-primitives" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bitvec", "frame-system", @@ -8031,7 +7977,6 @@ dependencies = [ [[package]] name = "polkadot-rpc" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "beefy-gadget", "beefy-gadget-rpc", @@ -8062,7 +8007,6 @@ dependencies = [ [[package]] name = "polkadot-runtime" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "beefy-primitives", "bitvec", @@ -8146,7 +8090,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "beefy-primitives", "bitvec", @@ -8193,7 +8136,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-constants" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-support", "polkadot-primitives", @@ -8205,7 +8147,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-metrics" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bs58", "parity-scale-codec", @@ -8217,7 +8158,6 @@ dependencies = [ [[package]] name = "polkadot-runtime-parachains" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "bitflags", "bitvec", @@ -8259,7 +8199,6 @@ dependencies = [ [[package]] name = "polkadot-service" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "async-trait", "beefy-gadget", @@ -8360,7 +8299,6 @@ dependencies = [ [[package]] name = "polkadot-statement-distribution" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "arrayvec 0.5.2", "fatality", @@ -8381,7 +8319,6 @@ dependencies = [ [[package]] name = "polkadot-statement-table" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "parity-scale-codec", "polkadot-primitives", @@ -8391,7 +8328,6 @@ dependencies = [ [[package]] name = "polkadot-test-client" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "parity-scale-codec", "polkadot-node-subsystem", @@ -8416,7 +8352,6 @@ dependencies = [ [[package]] name = "polkadot-test-runtime" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "beefy-primitives", "bitvec", @@ -8478,7 +8413,6 @@ dependencies = [ [[package]] name = "polkadot-test-service" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-benchmarking", "frame-system", @@ -9137,7 +9071,6 @@ dependencies = [ [[package]] name = "rococo-runtime" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "beefy-primitives", "bp-messages", @@ -9212,7 +9145,6 @@ dependencies = [ [[package]] name = "rococo-runtime-constants" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-support", "polkadot-primitives", @@ -10801,7 +10733,6 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "slot-range-helper" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "enumn", "parity-scale-codec", @@ -12054,7 +11985,6 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-runtime-constants" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-support", "polkadot-primitives", @@ -13052,7 +12982,6 @@ dependencies = [ [[package]] name = "westend-runtime" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "beefy-primitives", "bitvec", @@ -13138,7 +13067,6 @@ dependencies = [ [[package]] name = "westend-runtime-constants" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-support", "polkadot-primitives", @@ -13358,23 +13286,23 @@ dependencies = [ [[package]] name = "xcm" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "derivative", "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", + "sp-io", "xcm-procedural", ] [[package]] name = "xcm-builder" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "pallet-transaction-payment", "parity-scale-codec", @@ -13391,7 +13319,6 @@ dependencies = [ [[package]] name = "xcm-executor" version = "0.9.18" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "frame-benchmarking", "frame-support", @@ -13409,7 +13336,6 @@ dependencies = [ [[package]] name = "xcm-procedural" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot?branch=master#e6fa871e39bab8ccec27070b3b93a2e5e50ff0a4" dependencies = [ "Inflector", "proc-macro2", @@ -13480,3 +13406,71 @@ dependencies = [ "cc", "libc", ] + +[[patch.unused]] +name = "polkadot" +version = "0.9.18" + +[[patch.unused]] +name = "polkadot-primitives-test-helpers" +version = "0.9.18" + +[[patch.unused]] +name = "polkadot-test-malus" +version = "0.9.18" + +[[patch.unused]] +name = "polkadot-voter-bags" +version = "0.9.18" + +[[patch.unused]] +name = "remote-ext-tests-bags-list" +version = "0.9.18" + +[[patch.unused]] +name = "staking-miner" +version = "0.9.18" + +[[patch.unused]] +name = "test-parachain-adder" +version = "0.9.18" + +[[patch.unused]] +name = "test-parachain-adder-collator" +version = "0.9.18" + +[[patch.unused]] +name = "test-parachain-halt" +version = "0.9.18" + +[[patch.unused]] +name = "test-parachain-undying" +version = "0.9.18" + +[[patch.unused]] +name = "test-parachain-undying-collator" +version = "0.9.18" + +[[patch.unused]] +name = "test-parachains" +version = "0.9.18" + +[[patch.unused]] +name = "xcm-executor-integration-tests" +version = "0.9.18" + +[[patch.unused]] +name = "xcm-simulator" +version = "0.9.18" + +[[patch.unused]] +name = "xcm-simulator-example" +version = "0.9.18" + +[[patch.unused]] +name = "xcm-simulator-fuzzer" +version = "0.9.18" + +[[patch.unused]] +name = "zombienet-backchannel" +version = "0.9.18" diff --git a/polkadot-parachains/canvas-kusama/src/xcm_config.rs b/polkadot-parachains/canvas-kusama/src/xcm_config.rs index 33d563f165b..6249410b1a8 100644 --- a/polkadot-parachains/canvas-kusama/src/xcm_config.rs +++ b/polkadot-parachains/canvas-kusama/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{EnsureOneOf, Everything, Nothing}, + traits::{EnsureOneOf, Everything, Nothing, ConstU32}, weights::Weight, }; use frame_system::EnsureRoot; @@ -38,10 +38,10 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Any; + pub const RelayNetwork: Option = None; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); - pub const Local: MultiLocation = Here.into(); + pub const Local: MultiLocation = Here.into_location(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); pub const ExecutiveBody: BodyId = BodyId::Executive; } @@ -144,6 +144,8 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = super::AllPalletsWithSystem; + type MaxAssetsIntoHolding = ConstU32<8>; type AssetLocker = (); type AssetExchanger = (); type FeeManager = (); diff --git a/polkadot-parachains/statemint/src/xcm_config.rs b/polkadot-parachains/statemint/src/xcm_config.rs index 52a7e845736..b0d6e6328fa 100644 --- a/polkadot-parachains/statemint/src/xcm_config.rs +++ b/polkadot-parachains/statemint/src/xcm_config.rs @@ -42,7 +42,7 @@ parameter_types! { pub const RelayNetwork: NetworkId = NetworkId::Polkadot; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); - pub const Local: MultiLocation = Here.into(); + pub const Local: MultiLocation = MultiLocation::here(); pub AssetsPalletLocation: MultiLocation = PalletInstance(::index() as u8).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); diff --git a/polkadot-parachains/westmint/src/xcm_config.rs b/polkadot-parachains/westmint/src/xcm_config.rs index 5785254b340..8d01605703d 100644 --- a/polkadot-parachains/westmint/src/xcm_config.rs +++ b/polkadot-parachains/westmint/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, PalletInfoAccess}, + traits::{Everything, PalletInfoAccess, ConstU32, Nothing}, weights::Weight, }; use pallet_xcm::XcmPassthrough; @@ -29,8 +29,8 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, + FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -39,10 +39,10 @@ use xcm_executor::{traits::JustTry, XcmExecutor}; parameter_types! { pub const WestendLocation: MultiLocation = MultiLocation::parent(); - pub RelayNetwork: NetworkId = NetworkId::Named(b"Westend".to_vec()); + pub RelayNetwork: NetworkId = NetworkId::Westend; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); - pub const Local: MultiLocation = Here.into(); + pub const Local: MultiLocation = Here.into_location(); pub AssetsPalletLocation: MultiLocation = PalletInstance(::index() as u8).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); From 3f9e25a759c0db4fc5facc3d81bb75fe8157c031 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 11 Mar 2022 18:45:22 +0100 Subject: [PATCH 17/91] Formatting --- polkadot-parachains/canvas-kusama/src/xcm_config.rs | 2 +- polkadot-parachains/westmint/src/xcm_config.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/polkadot-parachains/canvas-kusama/src/xcm_config.rs b/polkadot-parachains/canvas-kusama/src/xcm_config.rs index 6249410b1a8..f3432a20766 100644 --- a/polkadot-parachains/canvas-kusama/src/xcm_config.rs +++ b/polkadot-parachains/canvas-kusama/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{EnsureOneOf, Everything, Nothing, ConstU32}, + traits::{ConstU32, EnsureOneOf, Everything, Nothing}, weights::Weight, }; use frame_system::EnsureRoot; diff --git a/polkadot-parachains/westmint/src/xcm_config.rs b/polkadot-parachains/westmint/src/xcm_config.rs index 8d01605703d..dd6bcc4965b 100644 --- a/polkadot-parachains/westmint/src/xcm_config.rs +++ b/polkadot-parachains/westmint/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, PalletInfoAccess, ConstU32, Nothing}, + traits::{ConstU32, Everything, Nothing, PalletInfoAccess}, weights::Weight, }; use pallet_xcm::XcmPassthrough; @@ -29,8 +29,8 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, - FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, + IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, From 4b72261c15a207340735039f5af12b5005311d56 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 11 Mar 2022 23:26:51 +0100 Subject: [PATCH 18/91] Bump --- Cargo.lock | 340 ++++++++++++++++++++++++++--------------------------- 1 file changed, 170 insertions(+), 170 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a49377747d..bb2fcf85072 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -458,7 +458,7 @@ dependencies = [ [[package]] name = "beefy-gadget" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "beefy-primitives", "fnv", @@ -488,7 +488,7 @@ dependencies = [ [[package]] name = "beefy-gadget-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "beefy-gadget", "beefy-primitives", @@ -511,12 +511,12 @@ dependencies = [ [[package]] name = "beefy-merkle-tree" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" [[package]] name = "beefy-primitives" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "scale-info", @@ -2768,7 +2768,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", ] @@ -2786,7 +2786,7 @@ dependencies = [ [[package]] name = "frame-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -2808,7 +2808,7 @@ dependencies = [ [[package]] name = "frame-benchmarking-cli" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "Inflector", "chrono", @@ -2849,7 +2849,7 @@ dependencies = [ [[package]] name = "frame-election-provider-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -2864,7 +2864,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -2892,7 +2892,7 @@ dependencies = [ [[package]] name = "frame-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "bitflags", "frame-metadata", @@ -2921,7 +2921,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "Inflector", "frame-support-procedural-tools", @@ -2933,7 +2933,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate 1.1.3", @@ -2945,7 +2945,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro2", "quote", @@ -2955,7 +2955,7 @@ dependencies = [ [[package]] name = "frame-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "log", @@ -2972,7 +2972,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -2987,7 +2987,7 @@ dependencies = [ [[package]] name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "sp-api", @@ -2996,7 +2996,7 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "sp-api", @@ -5583,7 +5583,7 @@ dependencies = [ [[package]] name = "pallet-asset-tx-payment" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -5600,7 +5600,7 @@ dependencies = [ [[package]] name = "pallet-assets" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -5614,7 +5614,7 @@ dependencies = [ [[package]] name = "pallet-aura" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -5630,7 +5630,7 @@ dependencies = [ [[package]] name = "pallet-authority-discovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -5646,7 +5646,7 @@ dependencies = [ [[package]] name = "pallet-authorship" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -5661,7 +5661,7 @@ dependencies = [ [[package]] name = "pallet-babe" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -5685,7 +5685,7 @@ dependencies = [ [[package]] name = "pallet-bags-list" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5705,7 +5705,7 @@ dependencies = [ [[package]] name = "pallet-balances" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -5720,7 +5720,7 @@ dependencies = [ [[package]] name = "pallet-beefy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "beefy-primitives", "frame-support", @@ -5736,7 +5736,7 @@ dependencies = [ [[package]] name = "pallet-beefy-mmr" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "beefy-merkle-tree", "beefy-primitives", @@ -5761,7 +5761,7 @@ dependencies = [ [[package]] name = "pallet-bounties" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -5862,7 +5862,7 @@ dependencies = [ [[package]] name = "pallet-collective" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -5879,7 +5879,7 @@ dependencies = [ [[package]] name = "pallet-contracts" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "bitflags", "frame-benchmarking", @@ -5906,7 +5906,7 @@ dependencies = [ [[package]] name = "pallet-contracts-primitives" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "bitflags", "parity-scale-codec", @@ -5921,7 +5921,7 @@ dependencies = [ [[package]] name = "pallet-contracts-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro2", "quote", @@ -5931,7 +5931,7 @@ dependencies = [ [[package]] name = "pallet-contracts-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -5950,7 +5950,7 @@ dependencies = [ [[package]] name = "pallet-contracts-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "pallet-contracts-primitives", "parity-scale-codec", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "pallet-democracy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -5979,7 +5979,7 @@ dependencies = [ [[package]] name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -6002,7 +6002,7 @@ dependencies = [ [[package]] name = "pallet-elections-phragmen" version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6020,7 +6020,7 @@ dependencies = [ [[package]] name = "pallet-gilt" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6035,7 +6035,7 @@ dependencies = [ [[package]] name = "pallet-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6058,7 +6058,7 @@ dependencies = [ [[package]] name = "pallet-identity" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "enumflags2", "frame-benchmarking", @@ -6074,7 +6074,7 @@ dependencies = [ [[package]] name = "pallet-im-online" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6094,7 +6094,7 @@ dependencies = [ [[package]] name = "pallet-indices" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6111,7 +6111,7 @@ dependencies = [ [[package]] name = "pallet-membership" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6128,7 +6128,7 @@ dependencies = [ [[package]] name = "pallet-mmr" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "ckb-merkle-mountain-range", "frame-benchmarking", @@ -6146,7 +6146,7 @@ dependencies = [ [[package]] name = "pallet-mmr-primitives" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6162,7 +6162,7 @@ dependencies = [ [[package]] name = "pallet-mmr-rpc" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -6179,7 +6179,7 @@ dependencies = [ [[package]] name = "pallet-multisig" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6194,7 +6194,7 @@ dependencies = [ [[package]] name = "pallet-nicks" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6208,7 +6208,7 @@ dependencies = [ [[package]] name = "pallet-offences" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6225,7 +6225,7 @@ dependencies = [ [[package]] name = "pallet-offences-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -6248,7 +6248,7 @@ dependencies = [ [[package]] name = "pallet-preimage" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6264,7 +6264,7 @@ dependencies = [ [[package]] name = "pallet-proxy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6279,7 +6279,7 @@ dependencies = [ [[package]] name = "pallet-randomness-collective-flip" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6293,7 +6293,7 @@ dependencies = [ [[package]] name = "pallet-recovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6307,7 +6307,7 @@ dependencies = [ [[package]] name = "pallet-scheduler" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6323,7 +6323,7 @@ dependencies = [ [[package]] name = "pallet-session" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6344,7 +6344,7 @@ dependencies = [ [[package]] name = "pallet-session-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6360,7 +6360,7 @@ dependencies = [ [[package]] name = "pallet-society" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6374,7 +6374,7 @@ dependencies = [ [[package]] name = "pallet-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -6397,7 +6397,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-curve" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -6408,7 +6408,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-fn" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "log", "sp-arithmetic", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6446,7 +6446,7 @@ dependencies = [ [[package]] name = "pallet-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6464,7 +6464,7 @@ dependencies = [ [[package]] name = "pallet-tips" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6483,7 +6483,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-support", "frame-system", @@ -6500,7 +6500,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -6517,7 +6517,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -6528,7 +6528,7 @@ dependencies = [ [[package]] name = "pallet-treasury" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6545,7 +6545,7 @@ dependencies = [ [[package]] name = "pallet-uniques" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6560,7 +6560,7 @@ dependencies = [ [[package]] name = "pallet-utility" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -6576,7 +6576,7 @@ dependencies = [ [[package]] name = "pallet-vesting" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-benchmarking", "frame-support", @@ -8954,7 +8954,7 @@ dependencies = [ [[package]] name = "remote-externalities" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "env_logger", "jsonrpsee 0.8.0", @@ -9333,7 +9333,7 @@ dependencies = [ [[package]] name = "sc-allocator" version = "4.1.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "log", "sp-core", @@ -9344,7 +9344,7 @@ dependencies = [ [[package]] name = "sc-authority-discovery" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "futures 0.3.21", @@ -9371,7 +9371,7 @@ dependencies = [ [[package]] name = "sc-basic-authorship" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "futures-timer", @@ -9394,7 +9394,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "sc-client-api", @@ -9410,7 +9410,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "impl-trait-for-tuples", "memmap2 0.5.3", @@ -9427,7 +9427,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -9438,7 +9438,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "chrono", "clap 3.1.6", @@ -9476,7 +9476,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "fnv", "futures 0.3.21", @@ -9504,7 +9504,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "hash-db", "kvdb", @@ -9529,7 +9529,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "futures 0.3.21", @@ -9553,7 +9553,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "futures 0.3.21", @@ -9582,7 +9582,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "fork-tree", @@ -9625,7 +9625,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "jsonrpc-core", @@ -9649,7 +9649,7 @@ dependencies = [ [[package]] name = "sc-consensus-epochs" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "fork-tree", "parity-scale-codec", @@ -9662,7 +9662,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "futures 0.3.21", @@ -9687,7 +9687,7 @@ dependencies = [ [[package]] name = "sc-consensus-uncles" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "sc-client-api", "sp-authorship", @@ -9698,7 +9698,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "lazy_static", "lru 0.6.6", @@ -9725,7 +9725,7 @@ dependencies = [ [[package]] name = "sc-executor-common" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "environmental", "parity-scale-codec", @@ -9742,7 +9742,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmi" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "log", "parity-scale-codec", @@ -9758,7 +9758,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmtime" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "cfg-if 1.0.0", "libc", @@ -9776,7 +9776,7 @@ dependencies = [ [[package]] name = "sc-finality-grandpa" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "ahash", "async-trait", @@ -9816,7 +9816,7 @@ dependencies = [ [[package]] name = "sc-finality-grandpa-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "finality-grandpa", "futures 0.3.21", @@ -9840,7 +9840,7 @@ dependencies = [ [[package]] name = "sc-informant" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "ansi_term", "futures 0.3.21", @@ -9857,7 +9857,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "hex", @@ -9872,7 +9872,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "asynchronous-codec 0.5.0", @@ -9921,7 +9921,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "ahash", "futures 0.3.21", @@ -9938,7 +9938,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "bytes 1.1.0", "fnv", @@ -9966,7 +9966,7 @@ dependencies = [ [[package]] name = "sc-peerset" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "libp2p", @@ -9979,7 +9979,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -9988,7 +9988,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "hash-db", @@ -10019,7 +10019,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "jsonrpc-core", @@ -10044,7 +10044,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "jsonrpc-core", @@ -10061,7 +10061,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "directories", @@ -10125,7 +10125,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "log", "parity-scale-codec", @@ -10139,7 +10139,7 @@ dependencies = [ [[package]] name = "sc-sync-state-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "jsonrpc-core", "jsonrpc-core-client", @@ -10160,7 +10160,7 @@ dependencies = [ [[package]] name = "sc-telemetry" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "chrono", "futures 0.3.21", @@ -10178,7 +10178,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "ansi_term", "atty", @@ -10209,7 +10209,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -10220,7 +10220,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "futures-timer", @@ -10247,7 +10247,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "log", @@ -10260,7 +10260,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "futures-timer", @@ -10820,7 +10820,7 @@ dependencies = [ [[package]] name = "sp-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "hash-db", "log", @@ -10837,7 +10837,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "blake2 0.10.4", "proc-macro-crate 1.1.3", @@ -10849,7 +10849,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "scale-info", @@ -10862,7 +10862,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "integer-sqrt", "num-traits", @@ -10877,7 +10877,7 @@ dependencies = [ [[package]] name = "sp-authority-discovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "scale-info", @@ -10890,7 +10890,7 @@ dependencies = [ [[package]] name = "sp-authorship" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "parity-scale-codec", @@ -10902,7 +10902,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "sp-api", @@ -10914,7 +10914,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "log", @@ -10932,7 +10932,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "futures 0.3.21", @@ -10951,7 +10951,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "parity-scale-codec", @@ -10969,7 +10969,7 @@ dependencies = [ [[package]] name = "sp-consensus-babe" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "merlin", @@ -10992,7 +10992,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "scale-info", @@ -11006,7 +11006,7 @@ dependencies = [ [[package]] name = "sp-consensus-vrf" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "schnorrkel", @@ -11018,7 +11018,7 @@ dependencies = [ [[package]] name = "sp-core" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "base58", "bitflags", @@ -11064,7 +11064,7 @@ dependencies = [ [[package]] name = "sp-core-hashing" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "blake2 0.10.4", "byteorder", @@ -11078,7 +11078,7 @@ dependencies = [ [[package]] name = "sp-core-hashing-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro2", "quote", @@ -11089,7 +11089,7 @@ dependencies = [ [[package]] name = "sp-database" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "kvdb", "parking_lot 0.12.0", @@ -11098,7 +11098,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro2", "quote", @@ -11108,7 +11108,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "environmental", "parity-scale-codec", @@ -11119,7 +11119,7 @@ dependencies = [ [[package]] name = "sp-finality-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "finality-grandpa", "log", @@ -11137,7 +11137,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -11151,7 +11151,7 @@ dependencies = [ [[package]] name = "sp-io" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "hash-db", @@ -11176,7 +11176,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "lazy_static", "sp-core", @@ -11187,7 +11187,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "futures 0.3.21", @@ -11204,7 +11204,7 @@ dependencies = [ [[package]] name = "sp-maybe-compressed-blob" version = "4.1.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "thiserror", "zstd", @@ -11213,7 +11213,7 @@ dependencies = [ [[package]] name = "sp-npos-elections" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "scale-info", @@ -11228,7 +11228,7 @@ dependencies = [ [[package]] name = "sp-npos-elections-solution-type" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -11239,7 +11239,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "sp-api", "sp-core", @@ -11249,7 +11249,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "backtrace", "lazy_static", @@ -11259,7 +11259,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "rustc-hash", "serde", @@ -11269,7 +11269,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "either", "hash256-std-hasher", @@ -11291,7 +11291,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -11308,7 +11308,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "Inflector", "proc-macro-crate 1.1.3", @@ -11320,7 +11320,7 @@ dependencies = [ [[package]] name = "sp-sandbox" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "log", "parity-scale-codec", @@ -11334,7 +11334,7 @@ dependencies = [ [[package]] name = "sp-serializer" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "serde", "serde_json", @@ -11343,7 +11343,7 @@ dependencies = [ [[package]] name = "sp-session" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "scale-info", @@ -11357,7 +11357,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "scale-info", @@ -11368,7 +11368,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "hash-db", "log", @@ -11391,12 +11391,12 @@ dependencies = [ [[package]] name = "sp-std" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" [[package]] name = "sp-storage" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "impl-serde", "parity-scale-codec", @@ -11409,7 +11409,7 @@ dependencies = [ [[package]] name = "sp-tasks" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "log", "sp-core", @@ -11422,7 +11422,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "futures-timer", @@ -11438,7 +11438,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "sp-std", @@ -11450,7 +11450,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "sp-api", "sp-runtime", @@ -11459,7 +11459,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "log", @@ -11475,7 +11475,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "hash-db", "memory-db", @@ -11491,7 +11491,7 @@ dependencies = [ [[package]] name = "sp-version" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "impl-serde", "parity-scale-codec", @@ -11508,7 +11508,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -11519,7 +11519,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "impl-trait-for-tuples", "log", @@ -11809,7 +11809,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "platforms", ] @@ -11817,7 +11817,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "frame-system-rpc-runtime-api", "futures 0.3.21", @@ -11839,7 +11839,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures-util", "hyper", @@ -11852,7 +11852,7 @@ dependencies = [ [[package]] name = "substrate-test-client" version = "2.0.1" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "async-trait", "futures 0.3.21", @@ -11878,7 +11878,7 @@ dependencies = [ [[package]] name = "substrate-test-utils" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "futures 0.3.21", "substrate-test-utils-derive", @@ -11888,7 +11888,7 @@ dependencies = [ [[package]] name = "substrate-test-utils-derive" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -11899,7 +11899,7 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "ansi_term", "build-helper", @@ -12380,7 +12380,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "try-runtime-cli" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#f5f286db0da99761792b69bf4a997535dcc8f260" +source = "git+https://github.com/paritytech/substrate?branch=master#5cc2ef8e4c03b64691db367036fedd8f4d7f0326" dependencies = [ "clap 3.1.6", "jsonrpsee 0.4.1", From 865839a180e285fd93cb6ed82ac1312c93a3754e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 11 Mar 2022 23:28:15 +0100 Subject: [PATCH 19/91] Bump --- pallets/dmp-queue/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 2774be7f0d3..c25b56a84dd 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -437,7 +437,8 @@ mod tests { fn execute( _origin: impl Into, - message: Xcm, + _: Weightless, + message: &Xcm, _hash: XcmHash, weight_limit: Weight, ) -> Outcome { From 6462dd9e0da79f3fef593fd01163a3dbaa6e0375 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 11 Mar 2022 23:38:23 +0100 Subject: [PATCH 20/91] Fixes --- pallets/dmp-queue/src/lib.rs | 17 ++--------------- pallets/xcmp-queue/src/mock.rs | 9 +++++++-- .../parachains-common/src/impls.rs | 4 ++-- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index c25b56a84dd..b1b12cf18d1 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -438,23 +438,10 @@ mod tests { fn execute( _origin: impl Into, _: Weightless, - message: &Xcm, _hash: XcmHash, - weight_limit: Weight, + _weight_limit: Weight, ) -> Outcome { - let o = match (message.0.len(), &message.0.first()) { - (1, Some(Transact { require_weight_at_most, .. })) => { - if *require_weight_at_most <= weight_limit { - Outcome::Complete(*require_weight_at_most) - } else { - Outcome::Error(XcmError::WeightLimitReached(*require_weight_at_most)) - } - }, - // use 1000 to decide that it's not supported. - _ => Outcome::Incomplete(1000.min(weight_limit), XcmError::Unimplemented), - }; - TRACE.with(|q| q.borrow_mut().push((message, o.clone()))); - o + unreachable!() } fn charge_fees(_location: impl Into, _fees: MultiAssets) -> XcmResult { diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 18a099e5ab3..4b7fbc895b0 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -17,7 +17,7 @@ use super::*; use crate as xcmp_queue; use core::marker::PhantomData; use cumulus_primitives_core::{IsSystem, ParaId}; -use frame_support::{parameter_types, traits::OriginTrait}; +use frame_support::{parameter_types, traits::{OriginTrait, Nothing}}; use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ @@ -152,8 +152,13 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = (); type AssetClaims = (); type SubscriptionService = (); - type PalletInstancesInfo = AllPallets; + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } pub type XcmRouter = ( diff --git a/polkadot-parachains/parachains-common/src/impls.rs b/polkadot-parachains/parachains-common/src/impls.rs index 2b66cd416d1..d511593c76f 100644 --- a/polkadot-parachains/parachains-common/src/impls.rs +++ b/polkadot-parachains/parachains-common/src/impls.rs @@ -269,9 +269,9 @@ mod tests { .clone() .pushed_with_interior(GeneralIndex(42)) .expect("multilocation will only have 2 junctions; qed"); - let asset = MultiAsset { id: Concrete(asset_location), fun: 1_000_000.into() }; + let asset = MultiAsset { id: Concrete(asset_location), fun: 1_000_000u128.into() }; assert!( - AssetsFrom::::filter_asset_location( + AssetsFrom::::contains( &asset, &SomeSiblingParachain::get() ), From b9cc2f232821a6a68312660bd43238edf9a276ca Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 11 Mar 2022 23:38:39 +0100 Subject: [PATCH 21/91] Formatting --- pallets/xcmp-queue/src/mock.rs | 5 ++++- polkadot-parachains/parachains-common/src/impls.rs | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 4b7fbc895b0..c285479521e 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -17,7 +17,10 @@ use super::*; use crate as xcmp_queue; use core::marker::PhantomData; use cumulus_primitives_core::{IsSystem, ParaId}; -use frame_support::{parameter_types, traits::{OriginTrait, Nothing}}; +use frame_support::{ + parameter_types, + traits::{Nothing, OriginTrait}, +}; use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ diff --git a/polkadot-parachains/parachains-common/src/impls.rs b/polkadot-parachains/parachains-common/src/impls.rs index d511593c76f..a9e1c8212ac 100644 --- a/polkadot-parachains/parachains-common/src/impls.rs +++ b/polkadot-parachains/parachains-common/src/impls.rs @@ -271,10 +271,7 @@ mod tests { .expect("multilocation will only have 2 junctions; qed"); let asset = MultiAsset { id: Concrete(asset_location), fun: 1_000_000u128.into() }; assert!( - AssetsFrom::::contains( - &asset, - &SomeSiblingParachain::get() - ), + AssetsFrom::::contains(&asset, &SomeSiblingParachain::get()), "AssetsFrom should allow assets from any of its interior locations" ); } From fb1d3f2a2be1d06c34440cf49a9f369f75483e11 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Fri, 11 Mar 2022 23:56:56 -0800 Subject: [PATCH 22/91] Make the price of UMP/XCMP message sending configurable --- pallets/xcmp-queue/src/lib.rs | 24 +++++++++++++++++++++++- primitives/utility/src/lib.rs | 30 +++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 112aaf378d7..31885c4fa93 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -99,6 +99,9 @@ pub mod pallet { /// superuser origin. type ControllerOriginConverter: ConvertOrigin; + /// The price for delivering an XCM to a sibling parachain destination. + type PriceForSiblingDelivery: PriceForSiblingDelivery; + /// The weight information of this pallet. type WeightInfo: WeightInfo; } @@ -1077,6 +1080,23 @@ impl XcmpMessageSource for Pallet { } } +pub trait PriceForSiblingDelivery { + fn price_for_sibling_delivery(id: ParaId, message: &Xcm<()>) -> MultiAssets; +} + +impl PriceForSiblingDelivery for () { + fn price_for_sibling_delivery(_: ParaId, _: &Xcm<()>) -> MultiAssets { + MultiAssets::new() + } +} + +pub struct ConstantPrice(sp_std::marker::PhantomData); +impl> PriceForSiblingDelivery for ConstantPrice { + fn price_for_sibling_delivery(_: ParaId, _: &Xcm<()>) -> MultiAssets { + T::get() + } +} + /// Xcm sender for sending to a sibling parachain. impl SendXcm for Pallet { type Ticket = (ParaId, VersionedXcm<()>); @@ -1091,9 +1111,11 @@ impl SendXcm for Pallet { match &d { // An HRMP message for a sibling parachain. MultiLocation { parents: 1, interior: X1(Parachain(id)) } => { + let id = ParaId::from(*id); + let price = T::PriceForSiblingDelivery::price_for_sibling_delivery(id, &xcm); let versioned_xcm = T::VersionWrapper::wrap_version(&d, xcm) .map_err(|()| SendError::DestinationUnsupported)?; - Ok((((*id).into(), versioned_xcm), MultiAssets::new())) + Ok(((id, versioned_xcm), price)) }, // Anything else is unhandled. This includes a message this is meant for us. _ => { diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index f99ebce9ec6..ac5b783eba2 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -21,9 +21,27 @@ use codec::Encode; use cumulus_primitives_core::{MessageSendError, UpwardMessageSender}; +use frame_support::traits::Get; use sp_std::{marker::PhantomData, prelude::*}; use xcm::{latest::prelude::*, WrapVersion}; +pub trait PriceForParentDelivery { + fn price_for_parent_delivery(message: &Xcm<()>) -> MultiAssets; +} + +impl PriceForParentDelivery for () { + fn price_for_parent_delivery(_: &Xcm<()>) -> MultiAssets { + MultiAssets::new() + } +} + +pub struct ConstantPrice(PhantomData); +impl> PriceForParentDelivery for ConstantPrice { + fn price_for_parent_delivery(_: &Xcm<()>) -> MultiAssets { + T::get() + } +} + /// Xcm router which recognises the `Parent` destination and handles it by sending the message into /// the given UMP `UpwardMessageSender` implementation. Thus this essentially adapts an /// `UpwardMessageSender` trait impl into a `SendXcm` trait impl. @@ -31,8 +49,13 @@ use xcm::{latest::prelude::*, WrapVersion}; /// NOTE: This is a pretty dumb "just send it" router; we will probably want to introduce queuing /// to UMP eventually and when we do, the pallet which implements the queuing will be responsible /// for the `SendXcm` implementation. -pub struct ParentAsUmp(PhantomData<(T, W)>); -impl SendXcm for ParentAsUmp { +pub struct ParentAsUmp(PhantomData<(T, W, P)>); +impl SendXcm for ParentAsUmp +where + T: UpwardMessageSender, + W: WrapVersion, + P: PriceForParentDelivery, +{ type Ticket = Vec; fn validate( @@ -44,11 +67,12 @@ impl SendXcm for ParentAsUmp { if d.contains_parents_only(1) { // An upward message for the relay chain. + let price = P::price_for_parent_delivery(&xcm); let versioned_xcm = W::wrap_version(&d, xcm).map_err(|()| SendError::DestinationUnsupported)?; let data = versioned_xcm.encode(); - Ok((data, MultiAssets::new())) + Ok((data, price)) } else { *dest = Some(d); // Anything else is unhandled. This includes a message this is meant for us. From c3ee8a1547937a3ed8274e76d9e3f2794253b679 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sat, 12 Mar 2022 00:02:40 -0800 Subject: [PATCH 23/91] cargo fmt --- pallets/xcmp-queue/src/lib.rs | 4 ++-- primitives/utility/src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 31885c4fa93..70cf75d796a 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -1086,14 +1086,14 @@ pub trait PriceForSiblingDelivery { impl PriceForSiblingDelivery for () { fn price_for_sibling_delivery(_: ParaId, _: &Xcm<()>) -> MultiAssets { - MultiAssets::new() + MultiAssets::new() } } pub struct ConstantPrice(sp_std::marker::PhantomData); impl> PriceForSiblingDelivery for ConstantPrice { fn price_for_sibling_delivery(_: ParaId, _: &Xcm<()>) -> MultiAssets { - T::get() + T::get() } } diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index ac5b783eba2..621506c146b 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -31,14 +31,14 @@ pub trait PriceForParentDelivery { impl PriceForParentDelivery for () { fn price_for_parent_delivery(_: &Xcm<()>) -> MultiAssets { - MultiAssets::new() + MultiAssets::new() } } pub struct ConstantPrice(PhantomData); impl> PriceForParentDelivery for ConstantPrice { fn price_for_parent_delivery(_: &Xcm<()>) -> MultiAssets { - T::get() + T::get() } } From ae1436ecd75ba1213f43162175fab3f90c1555da Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 12 Mar 2022 12:29:21 +0100 Subject: [PATCH 24/91] Remove InvertLocation --- .../src/validate_block/implementation.rs | 2 +- pallets/xcmp-queue/src/lib.rs | 2 +- pallets/xcmp-queue/src/mock.rs | 5 +++-- parachain-template/runtime/src/lib.rs | 1 + parachain-template/runtime/src/xcm_config.rs | 8 ++++---- polkadot-parachains/canvas-kusama/src/xcm_config.rs | 9 +++++---- polkadot-parachains/rococo-parachain/src/lib.rs | 9 +++++---- polkadot-parachains/shell/src/xcm_config.rs | 4 ++-- polkadot-parachains/statemine/src/lib.rs | 1 + polkadot-parachains/statemine/src/xcm_config.rs | 8 ++++---- polkadot-parachains/statemint/src/lib.rs | 3 ++- polkadot-parachains/statemint/src/xcm_config.rs | 8 ++++---- polkadot-parachains/westmint/src/lib.rs | 1 + polkadot-parachains/westmint/src/xcm_config.rs | 8 ++++---- 14 files changed, 38 insertions(+), 31 deletions(-) diff --git a/pallets/parachain-system/src/validate_block/implementation.rs b/pallets/parachain-system/src/validate_block/implementation.rs index 3dcbf291584..1ab841c1a7f 100644 --- a/pallets/parachain-system/src/validate_block/implementation.rs +++ b/pallets/parachain-system/src/validate_block/implementation.rs @@ -67,7 +67,7 @@ where assert!(parent_head.hash() == *block.header().parent_hash(), "Invalid parent hash",); // Create the db - let (db, root) = match storage_proof.to_memory_db(Some(parent_head.state_root())) { + let (db, _root) = match storage_proof.to_memory_db(Some(parent_head.state_root())) { Ok((db, root)) => (db, root), Err(_) => panic!("Compact proof decoding failure."), }; diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 70cf75d796a..a1ba81b6d73 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -44,7 +44,7 @@ use cumulus_primitives_core::{ ParaId, XcmpMessageFormat, XcmpMessageHandler, XcmpMessageSource, }; use frame_support::{ - traits::EnsureOrigin, + traits::{EnsureOrigin, Get}, weights::{constants::WEIGHT_PER_MILLIS, Weight}, }; use rand_chacha::{ diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index c285479521e..9641742d620 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -29,7 +29,7 @@ use sp_runtime::{ }; use xcm::prelude::*; use xcm_builder::{ - CurrencyAdapter, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentIsPreset, + CurrencyAdapter, FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset, }; use xcm_executor::traits::ConvertOrigin; @@ -147,7 +147,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = (); type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = (); type Weigher = FixedWeightBounds; type Trader = (); @@ -200,6 +200,7 @@ impl Config for Test { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = SystemParachainAsSuperuser; type WeightInfo = (); + type PriceForSiblingDelivery = (); } pub fn new_test_ext() -> sp_io::TestExternalities { diff --git a/parachain-template/runtime/src/lib.rs b/parachain-template/runtime/src/lib.rs index b3d3d59715b..fab19fe7bb9 100644 --- a/parachain-template/runtime/src/lib.rs +++ b/parachain-template/runtime/src/lib.rs @@ -387,6 +387,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = (); + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/parachain-template/runtime/src/xcm_config.rs b/parachain-template/runtime/src/xcm_config.rs index fc394a05d44..88635089ee3 100644 --- a/parachain-template/runtime/src/xcm_config.rs +++ b/parachain-template/runtime/src/xcm_config.rs @@ -13,7 +13,7 @@ use polkadot_runtime_common::impls::ToAuthor; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentIsPreset, + EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -104,7 +104,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = (); // Teleporting is disabled. - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -129,7 +129,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -146,7 +146,7 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Nothing; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; diff --git a/polkadot-parachains/canvas-kusama/src/xcm_config.rs b/polkadot-parachains/canvas-kusama/src/xcm_config.rs index f3432a20766..7f71577916b 100644 --- a/polkadot-parachains/canvas-kusama/src/xcm_config.rs +++ b/polkadot-parachains/canvas-kusama/src/xcm_config.rs @@ -29,7 +29,7 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, - FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, + FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -136,7 +136,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = UsingComponents; @@ -161,7 +161,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -179,7 +179,7 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; @@ -208,6 +208,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { >; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = cumulus_pallet_xcmp_queue::weights::SubstrateWeight; + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/rococo-parachain/src/lib.rs b/polkadot-parachains/rococo-parachain/src/lib.rs index 41b22f6d38c..47d0588e6eb 100644 --- a/polkadot-parachains/rococo-parachain/src/lib.rs +++ b/polkadot-parachains/rococo-parachain/src/lib.rs @@ -73,7 +73,7 @@ use polkadot_parachain::primitives::Sibling; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, + EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -404,7 +404,7 @@ impl Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = Reserves; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of ROC - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = UsingComponents, RocLocation, AccountId, Balances, ()>; @@ -428,7 +428,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -443,7 +443,7 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Nothing; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; @@ -469,6 +469,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = cumulus_pallet_xcmp_queue::weights::SubstrateWeight; + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/shell/src/xcm_config.rs b/polkadot-parachains/shell/src/xcm_config.rs index 0d491043af1..3025651c323 100644 --- a/polkadot-parachains/shell/src/xcm_config.rs +++ b/polkadot-parachains/shell/src/xcm_config.rs @@ -17,7 +17,7 @@ use super::{AccountId, Call, Event, Origin, ParachainInfo, Runtime}; use frame_support::{match_types, parameter_types, traits::Nothing, weights::Weight}; use xcm::latest::prelude::*; use xcm_builder::{ - AllowUnpaidExecutionFrom, FixedWeightBounds, LocationInverter, ParentAsSuperuser, + AllowUnpaidExecutionFrom, FixedWeightBounds, ParentAsSuperuser, ParentIsPreset, SovereignSignedViaLocation, }; @@ -59,7 +59,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = (); // balances not supported type IsTeleporter = (); // balances not supported - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = AllowUnpaidExecutionFrom; type Weigher = FixedWeightBounds; // balances not supported type Trader = (); // balances not supported diff --git a/polkadot-parachains/statemine/src/lib.rs b/polkadot-parachains/statemine/src/lib.rs index 6b363703fea..e0ac1d51824 100644 --- a/polkadot-parachains/statemine/src/lib.rs +++ b/polkadot-parachains/statemine/src/lib.rs @@ -430,6 +430,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { EnsureOneOf, EnsureXcm>>; type ControllerOriginConverter = xcm_config::XcmOriginToTransactDispatchOrigin; type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/statemine/src/xcm_config.rs b/polkadot-parachains/statemine/src/xcm_config.rs index a1f19a919cb..8a61fc87c3e 100644 --- a/polkadot-parachains/statemine/src/xcm_config.rs +++ b/polkadot-parachains/statemine/src/xcm_config.rs @@ -30,7 +30,7 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -159,7 +159,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of KSM - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -185,7 +185,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -203,7 +203,7 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; diff --git a/polkadot-parachains/statemint/src/lib.rs b/polkadot-parachains/statemint/src/lib.rs index 70d9e8cd4cd..ed7c455e748 100644 --- a/polkadot-parachains/statemint/src/lib.rs +++ b/polkadot-parachains/statemint/src/lib.rs @@ -434,6 +434,7 @@ impl parachain_info::Config for Runtime {} impl cumulus_pallet_aura_ext::Config for Runtime {} impl cumulus_pallet_xcmp_queue::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; type Event = Event; type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; @@ -442,7 +443,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureOneOf, EnsureXcm>>; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; - type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/statemint/src/xcm_config.rs b/polkadot-parachains/statemint/src/xcm_config.rs index b0d6e6328fa..6e2261b96ca 100644 --- a/polkadot-parachains/statemint/src/xcm_config.rs +++ b/polkadot-parachains/statemint/src/xcm_config.rs @@ -30,7 +30,7 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -159,7 +159,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of DOT - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -185,7 +185,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -203,7 +203,7 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Nothing; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; diff --git a/polkadot-parachains/westmint/src/lib.rs b/polkadot-parachains/westmint/src/lib.rs index ca299cb082e..93a5deb6abb 100644 --- a/polkadot-parachains/westmint/src/lib.rs +++ b/polkadot-parachains/westmint/src/lib.rs @@ -424,6 +424,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/polkadot-parachains/westmint/src/xcm_config.rs b/polkadot-parachains/westmint/src/xcm_config.rs index dd6bcc4965b..28b4ec72d34 100644 --- a/polkadot-parachains/westmint/src/xcm_config.rs +++ b/polkadot-parachains/westmint/src/xcm_config.rs @@ -30,7 +30,7 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -155,7 +155,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of WND - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -180,7 +180,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -195,7 +195,7 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; From d42833b4b9b77b0f3370e2367fa7c69faf52f0d2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 12 Mar 2022 12:32:37 +0100 Subject: [PATCH 25/91] Formatting --- pallets/xcmp-queue/src/mock.rs | 4 +--- polkadot-parachains/canvas-kusama/src/xcm_config.rs | 4 ++-- polkadot-parachains/rococo-parachain/src/lib.rs | 8 ++++---- polkadot-parachains/shell/src/xcm_config.rs | 4 ++-- polkadot-parachains/statemine/src/xcm_config.rs | 7 +++---- polkadot-parachains/statemint/src/xcm_config.rs | 7 +++---- polkadot-parachains/westmint/src/xcm_config.rs | 7 +++---- 7 files changed, 18 insertions(+), 23 deletions(-) diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 9641742d620..f0a191649a5 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -28,9 +28,7 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, }; use xcm::prelude::*; -use xcm_builder::{ - CurrencyAdapter, FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset, -}; +use xcm_builder::{CurrencyAdapter, FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset}; use xcm_executor::traits::ConvertOrigin; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/polkadot-parachains/canvas-kusama/src/xcm_config.rs b/polkadot-parachains/canvas-kusama/src/xcm_config.rs index 7f71577916b..0f4af7594d4 100644 --- a/polkadot-parachains/canvas-kusama/src/xcm_config.rs +++ b/polkadot-parachains/canvas-kusama/src/xcm_config.rs @@ -29,8 +29,8 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, - FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; diff --git a/polkadot-parachains/rococo-parachain/src/lib.rs b/polkadot-parachains/rococo-parachain/src/lib.rs index 47d0588e6eb..63ededdcdc1 100644 --- a/polkadot-parachains/rococo-parachain/src/lib.rs +++ b/polkadot-parachains/rococo-parachain/src/lib.rs @@ -73,10 +73,10 @@ use polkadot_parachain::primitives::Sibling; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + UsingComponents, }; use xcm_executor::{Config, XcmExecutor}; diff --git a/polkadot-parachains/shell/src/xcm_config.rs b/polkadot-parachains/shell/src/xcm_config.rs index 3025651c323..370d6f9d8bb 100644 --- a/polkadot-parachains/shell/src/xcm_config.rs +++ b/polkadot-parachains/shell/src/xcm_config.rs @@ -17,8 +17,8 @@ use super::{AccountId, Call, Event, Origin, ParachainInfo, Runtime}; use frame_support::{match_types, parameter_types, traits::Nothing, weights::Weight}; use xcm::latest::prelude::*; use xcm_builder::{ - AllowUnpaidExecutionFrom, FixedWeightBounds, ParentAsSuperuser, - ParentIsPreset, SovereignSignedViaLocation, + AllowUnpaidExecutionFrom, FixedWeightBounds, ParentAsSuperuser, ParentIsPreset, + SovereignSignedViaLocation, }; parameter_types! { diff --git a/polkadot-parachains/statemine/src/xcm_config.rs b/polkadot-parachains/statemine/src/xcm_config.rs index 8a61fc87c3e..601159de87d 100644 --- a/polkadot-parachains/statemine/src/xcm_config.rs +++ b/polkadot-parachains/statemine/src/xcm_config.rs @@ -30,10 +30,9 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::{traits::JustTry, XcmExecutor}; diff --git a/polkadot-parachains/statemint/src/xcm_config.rs b/polkadot-parachains/statemint/src/xcm_config.rs index 6e2261b96ca..fe80356967a 100644 --- a/polkadot-parachains/statemint/src/xcm_config.rs +++ b/polkadot-parachains/statemint/src/xcm_config.rs @@ -30,10 +30,9 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::{traits::JustTry, XcmExecutor}; diff --git a/polkadot-parachains/westmint/src/xcm_config.rs b/polkadot-parachains/westmint/src/xcm_config.rs index 28b4ec72d34..24eb59403c0 100644 --- a/polkadot-parachains/westmint/src/xcm_config.rs +++ b/polkadot-parachains/westmint/src/xcm_config.rs @@ -30,10 +30,9 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::{traits::JustTry, XcmExecutor}; From 780475dc70bf9783ff2362ff330768f4adb303f5 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 14 Mar 2022 09:09:02 -0700 Subject: [PATCH 26/91] Use ConstantPrice from polkadot-runtime-common --- Cargo.lock | 155 ++++++++++++++++++---------------- pallets/xcmp-queue/Cargo.toml | 2 + pallets/xcmp-queue/src/lib.rs | 2 +- primitives/utility/Cargo.toml | 2 + primitives/utility/src/lib.rs | 2 +- 5 files changed, 88 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb2fcf85072..20a38c28572 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -732,6 +732,7 @@ dependencies = [ [[package]] name = "bp-header-chain" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "finality-grandpa", "frame-support", @@ -747,6 +748,7 @@ dependencies = [ [[package]] name = "bp-message-dispatch" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bp-runtime", "frame-support", @@ -758,6 +760,7 @@ dependencies = [ [[package]] name = "bp-messages" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bitvec", "bp-runtime", @@ -773,6 +776,7 @@ dependencies = [ [[package]] name = "bp-polkadot-core" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bp-messages", "bp-runtime", @@ -790,6 +794,7 @@ dependencies = [ [[package]] name = "bp-rococo" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -806,6 +811,7 @@ dependencies = [ [[package]] name = "bp-runtime" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-support", "hash-db", @@ -823,6 +829,7 @@ dependencies = [ [[package]] name = "bp-test-utils" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bp-header-chain", "ed25519-dalek", @@ -837,6 +844,7 @@ dependencies = [ [[package]] name = "bp-wococo" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -851,6 +859,7 @@ dependencies = [ [[package]] name = "bridge-runtime-common" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bp-message-dispatch", "bp-messages", @@ -1923,6 +1932,7 @@ dependencies = [ "log", "pallet-balances", "parity-scale-codec", + "polkadot-runtime-common", "rand_chacha 0.3.1", "scale-info", "sp-core", @@ -2014,6 +2024,7 @@ dependencies = [ "polkadot-core-primitives", "polkadot-parachain", "polkadot-primitives", + "polkadot-runtime-common", "sp-io", "sp-runtime", "sp-std", @@ -4173,6 +4184,7 @@ dependencies = [ [[package]] name = "kusama-runtime" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "beefy-primitives", "bitvec", @@ -4260,6 +4272,7 @@ dependencies = [ [[package]] name = "kusama-runtime-constants" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-support", "polkadot-primitives", @@ -5123,6 +5136,7 @@ dependencies = [ [[package]] name = "metered-channel" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "derive_more", "futures 0.3.21", @@ -5779,6 +5793,7 @@ dependencies = [ [[package]] name = "pallet-bridge-dispatch" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bp-message-dispatch", "bp-runtime", @@ -5795,6 +5810,7 @@ dependencies = [ [[package]] name = "pallet-bridge-grandpa" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bp-header-chain", "bp-runtime", @@ -5816,6 +5832,7 @@ dependencies = [ [[package]] name = "pallet-bridge-messages" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bitvec", "bp-message-dispatch", @@ -6591,16 +6608,15 @@ dependencies = [ [[package]] name = "pallet-xcm" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-support", "frame-system", - "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", "serde", "sp-core", - "sp-io", "sp-runtime", "sp-std", "xcm", @@ -6610,6 +6626,7 @@ dependencies = [ [[package]] name = "pallet-xcm-benchmarks" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-benchmarking", "frame-support", @@ -6617,7 +6634,6 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-io", "sp-runtime", "sp-std", "xcm", @@ -7172,6 +7188,7 @@ dependencies = [ [[package]] name = "polkadot-approval-distribution" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "polkadot-node-network-protocol", @@ -7185,6 +7202,7 @@ dependencies = [ [[package]] name = "polkadot-availability-bitfield-distribution" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "polkadot-node-network-protocol", @@ -7197,6 +7215,7 @@ dependencies = [ [[package]] name = "polkadot-availability-distribution" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "derive_more", "fatality", @@ -7219,6 +7238,7 @@ dependencies = [ [[package]] name = "polkadot-availability-recovery" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "fatality", "futures 0.3.21", @@ -7239,6 +7259,7 @@ dependencies = [ [[package]] name = "polkadot-cli" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "clap 3.1.6", "frame-benchmarking-cli", @@ -7261,6 +7282,7 @@ dependencies = [ [[package]] name = "polkadot-client" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "beefy-primitives", "frame-benchmarking", @@ -7366,6 +7388,7 @@ dependencies = [ [[package]] name = "polkadot-collator-protocol" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "always-assert", "fatality", @@ -7386,6 +7409,7 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "parity-scale-codec", "parity-util-mem", @@ -7398,6 +7422,7 @@ dependencies = [ [[package]] name = "polkadot-dispute-distribution" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "derive_more", "fatality", @@ -7420,6 +7445,7 @@ dependencies = [ [[package]] name = "polkadot-erasure-coding" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "parity-scale-codec", "polkadot-node-primitives", @@ -7433,6 +7459,7 @@ dependencies = [ [[package]] name = "polkadot-gossip-support" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7452,6 +7479,7 @@ dependencies = [ [[package]] name = "polkadot-network-bridge" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-trait", "futures 0.3.21", @@ -7470,6 +7498,7 @@ dependencies = [ [[package]] name = "polkadot-node-collation-generation" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "parity-scale-codec", @@ -7487,6 +7516,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-approval-voting" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bitvec", "derive_more", @@ -7514,6 +7544,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-av-store" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bitvec", "futures 0.3.21", @@ -7533,6 +7564,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-backing" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bitvec", "futures 0.3.21", @@ -7550,6 +7582,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-bitfield-signing" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "polkadot-node-subsystem", @@ -7564,6 +7597,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-candidate-validation" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-trait", "futures 0.3.21", @@ -7581,6 +7615,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-api" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "polkadot-node-subsystem", @@ -7595,6 +7630,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-selection" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7611,6 +7647,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-coordinator" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "fatality", "futures 0.3.21", @@ -7629,6 +7666,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-parachains-inherent" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-trait", "futures 0.3.21", @@ -7645,6 +7683,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-provisioner" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bitvec", "futures 0.3.21", @@ -7661,6 +7700,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "always-assert", "assert_matches", @@ -7690,6 +7730,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf-checker" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "polkadot-node-primitives", @@ -7705,6 +7746,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-runtime-api" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "memory-lru", @@ -7722,6 +7764,7 @@ dependencies = [ [[package]] name = "polkadot-node-jaeger" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-std", "lazy_static", @@ -7739,6 +7782,7 @@ dependencies = [ [[package]] name = "polkadot-node-metrics" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bs58", "futures 0.3.21", @@ -7757,6 +7801,7 @@ dependencies = [ [[package]] name = "polkadot-node-network-protocol" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-trait", "fatality", @@ -7774,6 +7819,7 @@ dependencies = [ [[package]] name = "polkadot-node-primitives" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bounded-vec", "futures 0.3.21", @@ -7795,6 +7841,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "polkadot-node-jaeger", "polkadot-node-subsystem-types", @@ -7804,6 +7851,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-test-helpers" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-trait", "futures 0.3.21", @@ -7821,6 +7869,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-types" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "derive_more", "futures 0.3.21", @@ -7839,6 +7888,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-util" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-trait", "derive_more", @@ -7871,6 +7921,7 @@ dependencies = [ [[package]] name = "polkadot-overseer" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "futures 0.3.21", "futures-timer", @@ -7891,6 +7942,7 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-trait", "futures 0.3.21", @@ -7907,6 +7959,7 @@ dependencies = [ [[package]] name = "polkadot-overseer-gen-proc-macro" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "expander 0.0.6", "proc-macro-crate 1.1.3", @@ -7918,6 +7971,7 @@ dependencies = [ [[package]] name = "polkadot-parachain" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "derive_more", "frame-support", @@ -7934,6 +7988,7 @@ dependencies = [ [[package]] name = "polkadot-performance-test" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "env_logger", "kusama-runtime", @@ -7948,6 +8003,7 @@ dependencies = [ [[package]] name = "polkadot-primitives" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bitvec", "frame-system", @@ -7977,6 +8033,7 @@ dependencies = [ [[package]] name = "polkadot-rpc" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "beefy-gadget", "beefy-gadget-rpc", @@ -8007,6 +8064,7 @@ dependencies = [ [[package]] name = "polkadot-runtime" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "beefy-primitives", "bitvec", @@ -8090,6 +8148,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "beefy-primitives", "bitvec", @@ -8136,6 +8195,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-constants" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-support", "polkadot-primitives", @@ -8147,6 +8207,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-metrics" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bs58", "parity-scale-codec", @@ -8158,6 +8219,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-parachains" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "bitflags", "bitvec", @@ -8199,6 +8261,7 @@ dependencies = [ [[package]] name = "polkadot-service" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "async-trait", "beefy-gadget", @@ -8299,6 +8362,7 @@ dependencies = [ [[package]] name = "polkadot-statement-distribution" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "arrayvec 0.5.2", "fatality", @@ -8319,6 +8383,7 @@ dependencies = [ [[package]] name = "polkadot-statement-table" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "parity-scale-codec", "polkadot-primitives", @@ -8328,6 +8393,7 @@ dependencies = [ [[package]] name = "polkadot-test-client" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "parity-scale-codec", "polkadot-node-subsystem", @@ -8352,6 +8418,7 @@ dependencies = [ [[package]] name = "polkadot-test-runtime" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "beefy-primitives", "bitvec", @@ -8413,6 +8480,7 @@ dependencies = [ [[package]] name = "polkadot-test-service" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-benchmarking", "frame-system", @@ -8428,6 +8496,7 @@ dependencies = [ "polkadot-parachain", "polkadot-primitives", "polkadot-rpc", + "polkadot-runtime-common", "polkadot-runtime-parachains", "polkadot-service", "polkadot-test-runtime", @@ -9071,6 +9140,7 @@ dependencies = [ [[package]] name = "rococo-runtime" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "beefy-primitives", "bp-messages", @@ -9145,6 +9215,7 @@ dependencies = [ [[package]] name = "rococo-runtime-constants" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-support", "polkadot-primitives", @@ -10733,6 +10804,7 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "slot-range-helper" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "enumn", "parity-scale-codec", @@ -11985,6 +12057,7 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-runtime-constants" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-support", "polkadot-primitives", @@ -12982,6 +13055,7 @@ dependencies = [ [[package]] name = "westend-runtime" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "beefy-primitives", "bitvec", @@ -13067,6 +13141,7 @@ dependencies = [ [[package]] name = "westend-runtime-constants" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-support", "polkadot-primitives", @@ -13286,23 +13361,23 @@ dependencies = [ [[package]] name = "xcm" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "derivative", "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", - "sp-io", "xcm-procedural", ] [[package]] name = "xcm-builder" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-support", "frame-system", - "impl-trait-for-tuples", "log", "pallet-transaction-payment", "parity-scale-codec", @@ -13319,6 +13394,7 @@ dependencies = [ [[package]] name = "xcm-executor" version = "0.9.18" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "frame-benchmarking", "frame-support", @@ -13336,6 +13412,7 @@ dependencies = [ [[package]] name = "xcm-procedural" version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot?branch=master#5c3067e9d58487e866fc8e4ad594fd22e9132c93" dependencies = [ "Inflector", "proc-macro2", @@ -13406,71 +13483,3 @@ dependencies = [ "cc", "libc", ] - -[[patch.unused]] -name = "polkadot" -version = "0.9.18" - -[[patch.unused]] -name = "polkadot-primitives-test-helpers" -version = "0.9.18" - -[[patch.unused]] -name = "polkadot-test-malus" -version = "0.9.18" - -[[patch.unused]] -name = "polkadot-voter-bags" -version = "0.9.18" - -[[patch.unused]] -name = "remote-ext-tests-bags-list" -version = "0.9.18" - -[[patch.unused]] -name = "staking-miner" -version = "0.9.18" - -[[patch.unused]] -name = "test-parachain-adder" -version = "0.9.18" - -[[patch.unused]] -name = "test-parachain-adder-collator" -version = "0.9.18" - -[[patch.unused]] -name = "test-parachain-halt" -version = "0.9.18" - -[[patch.unused]] -name = "test-parachain-undying" -version = "0.9.18" - -[[patch.unused]] -name = "test-parachain-undying-collator" -version = "0.9.18" - -[[patch.unused]] -name = "test-parachains" -version = "0.9.18" - -[[patch.unused]] -name = "xcm-executor-integration-tests" -version = "0.9.18" - -[[patch.unused]] -name = "xcm-simulator" -version = "0.9.18" - -[[patch.unused]] -name = "xcm-simulator-example" -version = "0.9.18" - -[[patch.unused]] -name = "xcm-simulator-fuzzer" -version = "0.9.18" - -[[patch.unused]] -name = "zombienet-backchannel" -version = "0.9.18" diff --git a/pallets/xcmp-queue/Cargo.toml b/pallets/xcmp-queue/Cargo.toml index d457e322bd4..f3d871e1b1d 100644 --- a/pallets/xcmp-queue/Cargo.toml +++ b/pallets/xcmp-queue/Cargo.toml @@ -18,6 +18,7 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", default-features sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } # Polkadot +polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } @@ -48,6 +49,7 @@ std = [ "frame-support/std", "frame-system/std", "log/std", + "polkadot-runtime-common/std", "sp-io/std", "sp-runtime/std", "sp-std/std", diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index a1ba81b6d73..f7e52a67567 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -47,6 +47,7 @@ use frame_support::{ traits::{EnsureOrigin, Get}, weights::{constants::WEIGHT_PER_MILLIS, Weight}, }; +use polkadot_runtime_common::xcm_sender::ConstantPrice; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaChaRng, @@ -1090,7 +1091,6 @@ impl PriceForSiblingDelivery for () { } } -pub struct ConstantPrice(sp_std::marker::PhantomData); impl> PriceForSiblingDelivery for ConstantPrice { fn price_for_sibling_delivery(_: ParaId, _: &Xcm<()>) -> MultiAssets { T::get() diff --git a/primitives/utility/Cargo.toml b/primitives/utility/Cargo.toml index 2cdb62f45c2..334e7294617 100644 --- a/primitives/utility/Cargo.toml +++ b/primitives/utility/Cargo.toml @@ -16,6 +16,7 @@ sp-trie = { git = "https://github.com/paritytech/substrate", default-features = # Polkadot polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } polkadot-primitives = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } @@ -32,6 +33,7 @@ std = [ "sp-std/std", "sp-trie/std", "polkadot-core-primitives/std", + "polkadot-runtime-common/std", "polkadot-parachain/std", "polkadot-primitives/std", "cumulus-primitives-core/std", diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index 621506c146b..525b94c0b09 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -22,6 +22,7 @@ use codec::Encode; use cumulus_primitives_core::{MessageSendError, UpwardMessageSender}; use frame_support::traits::Get; +use polkadot_runtime_common::xcm_sender::ConstantPrice; use sp_std::{marker::PhantomData, prelude::*}; use xcm::{latest::prelude::*, WrapVersion}; @@ -35,7 +36,6 @@ impl PriceForParentDelivery for () { } } -pub struct ConstantPrice(PhantomData); impl> PriceForParentDelivery for ConstantPrice { fn price_for_parent_delivery(_: &Xcm<()>) -> MultiAssets { T::get() From 667273a7be384d3cbcf6d57ea642be27647eef9c Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sun, 14 Aug 2022 09:50:01 +0800 Subject: [PATCH 27/91] Fix naming --- pallets/dmp-queue/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 608bc3bfebf..a50b3df1cfc 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -239,7 +239,7 @@ pub mod pallet { Ok(0) }, Ok(Ok(x)) => { - let outcome = T::XcmExecutor::execute_xcm(Parent, x, id, limit); + let outcome = T::XcmExecutor::execute_xcm(Parent, x, message_id, limit); match outcome { Outcome::Error(XcmError::WeightLimitReached(required)) => Err((message_id, required)), From 0e6fd2dcc773455ec680f7ad84ac503085f0dafb Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sun, 14 Aug 2022 09:52:40 +0800 Subject: [PATCH 28/91] cargo fmt --- parachains/pallets/ping/src/lib.rs | 8 +++++++- primitives/utility/src/lib.rs | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/parachains/pallets/ping/src/lib.rs b/parachains/pallets/ping/src/lib.rs index 518a9e43ecb..f6e85a7bb89 100644 --- a/parachains/pallets/ping/src/lib.rs +++ b/parachains/pallets/ping/src/lib.rs @@ -118,7 +118,13 @@ pub mod pallet { ) { Ok((hash, cost)) => { Pings::::insert(seq, n); - Self::deposit_event(Event::PingSent(para, seq, payload.to_vec(), hash, cost)); + Self::deposit_event(Event::PingSent( + para, + seq, + payload.to_vec(), + hash, + cost, + )); }, Err(e) => { Self::deposit_event(Event::ErrorSendingPing( diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index f4aa8b7546f..6e2362667ea 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -22,7 +22,10 @@ use codec::Encode; use cumulus_primitives_core::{MessageSendError, UpwardMessageSender}; use frame_support::{ - traits::{Get, tokens::{fungibles, fungibles::Inspect}}, + traits::{ + tokens::{fungibles, fungibles::Inspect}, + Get, + }, weights::Weight, }; use polkadot_runtime_common::xcm_sender::ConstantPrice; From 620c42cd0c1e261f6e7c411adcddc32d863ffe3b Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sun, 14 Aug 2022 10:11:01 +0800 Subject: [PATCH 29/91] Fixes --- primitives/utility/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index 6e2362667ea..38d08381723 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -291,7 +291,7 @@ impl< if let Some(receiver) = ReceiverAccount::get() { let ok = FungiblesMutateAdapter::deposit_asset( &revenue, - &(X1(AccountId32 { network: Any, id: receiver.into() }).into()), + &(X1(AccountId32 { network: None, id: receiver.into() }).into()), ) .is_ok(); From 4e53da1c7226b44d209933179c9cd4a36061806f Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 24 Aug 2022 22:30:41 +0800 Subject: [PATCH 30/91] Fixes --- primitives/utility/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index 38d08381723..2c19674bc77 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -292,6 +292,9 @@ impl< let ok = FungiblesMutateAdapter::deposit_asset( &revenue, &(X1(AccountId32 { network: None, id: receiver.into() }).into()), + // We aren't able to track the XCM that initiated the fee deposit, so we create a + // fake message hash here + &XcmContext::with_message_hash([0; 32]), ) .is_ok(); From 9ee4759fcf4bdae2d6dbeef6bb4b8cd29a9f3dbf Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 24 Aug 2022 22:42:49 +0800 Subject: [PATCH 31/91] Fixes --- parachains/common/src/xcm_config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parachains/common/src/xcm_config.rs b/parachains/common/src/xcm_config.rs index d56876d60ca..c0a71718bb4 100644 --- a/parachains/common/src/xcm_config.rs +++ b/parachains/common/src/xcm_config.rs @@ -23,7 +23,7 @@ where { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + message: &mut [Instruction], max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { @@ -37,7 +37,7 @@ pub struct DenyReserveTransferToRelayChain; impl ShouldExecute for DenyReserveTransferToRelayChain { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + message: &mut [Instruction], _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { From 187c47a6828ca674b46b913e4e60279853af6a2f Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 24 Aug 2022 22:45:57 +0800 Subject: [PATCH 32/91] Add CallDispatcher --- parachain-template/runtime/src/xcm_config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/parachain-template/runtime/src/xcm_config.rs b/parachain-template/runtime/src/xcm_config.rs index 48b1e9ce708..007c1d55acf 100644 --- a/parachain-template/runtime/src/xcm_config.rs +++ b/parachain-template/runtime/src/xcm_config.rs @@ -188,6 +188,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// No local origins on this chain are allowed to dispatch XCM sends/executions. From bd3ce91d85338e694a490131ccf3773fd2362f5b Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 24 Aug 2022 23:03:03 +0800 Subject: [PATCH 33/91] Fixes --- parachain-template/runtime/src/xcm_config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parachain-template/runtime/src/xcm_config.rs b/parachain-template/runtime/src/xcm_config.rs index 007c1d55acf..ccbb6eb99d5 100644 --- a/parachain-template/runtime/src/xcm_config.rs +++ b/parachain-template/runtime/src/xcm_config.rs @@ -104,7 +104,7 @@ where { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + message: &mut [Instruction], max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { @@ -118,11 +118,11 @@ pub struct DenyReserveTransferToRelayChain; impl ShouldExecute for DenyReserveTransferToRelayChain { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + message: &mut [Instruction], _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { - if message.0.iter().any(|inst| { + if message.iter().any(|inst| { matches!( inst, InitiateReserveWithdraw { @@ -141,7 +141,7 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { // An unexpected reserve transfer has arrived from the Relay Chain. Generally, `IsReserve` // should not allow this, but we just log it here. if matches!(origin, MultiLocation { parents: 1, interior: Here }) && - message.0.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) + message.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) { log::warn!( target: "xcm::barriers", From ad1cbecabd70495aa2891785aba843cdf8907026 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 24 Aug 2022 23:15:56 +0800 Subject: [PATCH 34/91] Fixes --- parachains/common/src/xcm_config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parachains/common/src/xcm_config.rs b/parachains/common/src/xcm_config.rs index c0a71718bb4..bfcaa9d49e8 100644 --- a/parachains/common/src/xcm_config.rs +++ b/parachains/common/src/xcm_config.rs @@ -41,7 +41,7 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { - if message.0.iter().any(|inst| { + if message.iter().any(|inst| { matches!( inst, InitiateReserveWithdraw { @@ -60,7 +60,7 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { // An unexpected reserve transfer has arrived from the Relay Chain. Generally, `IsReserve` // should not allow this, but we just log it here. if matches!(origin, MultiLocation { parents: 1, interior: Here }) && - message.0.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) + message.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) { log::warn!( target: "xcm::barrier", From 5a34514967e0d9b6f82d75d8634f9d51f6f7aaaf Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 00:17:33 +0800 Subject: [PATCH 35/91] Fixes --- .../assets/statemine/src/xcm_config.rs | 1 + .../assets/statemint/src/xcm_config.rs | 1 + .../assets/westmint/src/xcm_config.rs | 1 + .../collectives-polkadot/src/xcm_config.rs | 26 ++++++++++---- .../contracts-rococo/src/xcm_config.rs | 1 + .../runtimes/starters/shell/src/xcm_config.rs | 1 + .../runtimes/testing/penpal/src/xcm_config.rs | 35 +++++++++++++------ 7 files changed, 49 insertions(+), 17 deletions(-) diff --git a/parachains/runtimes/assets/statemine/src/xcm_config.rs b/parachains/runtimes/assets/statemine/src/xcm_config.rs index 03aca7ded13..2b5598fce16 100644 --- a/parachains/runtimes/assets/statemine/src/xcm_config.rs +++ b/parachains/runtimes/assets/statemine/src/xcm_config.rs @@ -208,6 +208,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// Converts a local signed origin into an XCM multilocation. diff --git a/parachains/runtimes/assets/statemint/src/xcm_config.rs b/parachains/runtimes/assets/statemint/src/xcm_config.rs index 5d34e56b43b..ce6fd6c29b2 100644 --- a/parachains/runtimes/assets/statemint/src/xcm_config.rs +++ b/parachains/runtimes/assets/statemint/src/xcm_config.rs @@ -184,6 +184,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// Converts a local signed origin into an XCM multilocation. diff --git a/parachains/runtimes/assets/westmint/src/xcm_config.rs b/parachains/runtimes/assets/westmint/src/xcm_config.rs index 01d2c33ab79..c94feb50d75 100644 --- a/parachains/runtimes/assets/westmint/src/xcm_config.rs +++ b/parachains/runtimes/assets/westmint/src/xcm_config.rs @@ -204,6 +204,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. diff --git a/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs b/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs index 78f5b224f8d..1d927fd3f57 100644 --- a/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs +++ b/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ }; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing}, + traits::{ConstU32, Everything, Nothing}, weights::Weight, }; use pallet_xcm::XcmPassthrough; @@ -43,8 +43,8 @@ parameter_types! { pub const DotLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Polkadot; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); - pub const Local: MultiLocation = Here.into(); + pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + pub const Local: MultiLocation = Here.into_location(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); } @@ -102,6 +102,7 @@ parameter_types! { // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = 1_000_000_000; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } match_types! { @@ -141,7 +142,7 @@ impl xcm_executor::Config for XcmConfig { // where allowed (e.g. with the Relay Chain). type IsReserve = (); type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of DOT - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -150,6 +151,14 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// Converts a local signed origin into an XCM multilocation. @@ -160,7 +169,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -178,11 +187,16 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs b/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs index 8252800ac60..8b30214daf8 100644 --- a/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -157,6 +157,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// Converts a local signed origin into an XCM multilocation. diff --git a/parachains/runtimes/starters/shell/src/xcm_config.rs b/parachains/runtimes/starters/shell/src/xcm_config.rs index 370d6f9d8bb..af846f7181f 100644 --- a/parachains/runtimes/starters/shell/src/xcm_config.rs +++ b/parachains/runtimes/starters/shell/src/xcm_config.rs @@ -74,6 +74,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index 3fb2192cd7a..36ce4954838 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -30,7 +30,7 @@ use frame_support::{ log, match_types, parameter_types, traits::{ fungibles::{self, Balanced, CreditOf}, - Contains, Everything, Get, Nothing, + ConstU32, Contains, Everything, Get, Nothing, }, weights::Weight, }; @@ -43,11 +43,10 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteAssetId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, - FungiblesAdapter, IsConcrete, LocationInverter, NativeAsset, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, + ConvertedAssetId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, + IsConcrete, LocationInverter, NativeAsset, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::{ traits::{FilterAssetLocation, JustTry, ShouldExecute}, @@ -58,7 +57,7 @@ parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Any; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); } /// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used @@ -92,7 +91,7 @@ pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation: Assets, // Use this currency when it is a fungible asset matching the given location or name: - ConvertedConcreteAssetId< + ConvertedAssetId< AssetIdPalletAssets, Balance, AsPrefixedGeneralIndex, @@ -137,6 +136,7 @@ parameter_types! { // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = 1_000_000_000; pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; } match_types! { @@ -328,7 +328,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = MultiNativeAsset; // TODO: maybe needed to be replaced by Reserves type IsTeleporter = NativeAsset; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = @@ -337,6 +337,14 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// No local origins on this chain are allowed to dispatch XCM sends/executions. @@ -346,7 +354,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); @@ -363,13 +371,18 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type UniversalLocation = UniversalLocation; type Origin = Origin; type Call = Call; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; // ^ Override for AdvertisedXcmVersion default type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; } impl cumulus_pallet_xcm::Config for Runtime { From 67c99fdc4bf49df8e9e24f932eb239bdcbb26dc0 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 00:36:04 +0800 Subject: [PATCH 36/91] Fixes --- parachains/runtimes/testing/rococo-parachain/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 4b00ff86c41..94948ddc5cb 100644 --- a/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -79,7 +79,7 @@ use xcm_builder::{ SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; -use xcm_executor::{Config, XcmExecutor}; +use xcm_executor::XcmExecutor; pub type SessionHandlers = (); @@ -399,7 +399,7 @@ parameter_types! { pub type Reserves = (NativeAsset, AssetsFrom); pub struct XcmConfig; -impl Config for XcmConfig { +impl xcm_executor::Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; // How to withdraw and deposit an asset. @@ -422,6 +422,7 @@ impl Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. From 1fc2cb4bd2dcf7adc0ad8d973742a143d6580805 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 00:49:38 +0800 Subject: [PATCH 37/91] Fixes --- .../collectives/collectives-polkadot/src/xcm_config.rs | 4 ++-- parachains/runtimes/testing/penpal/src/xcm_config.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs b/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs index 1d927fd3f57..fd6a29476fd 100644 --- a/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs +++ b/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs @@ -32,8 +32,8 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, - FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index 36ce4954838..97b53a9b72f 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -44,9 +44,9 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedAssetId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, LocationInverter, NativeAsset, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::{ traits::{FilterAssetLocation, JustTry, ShouldExecute}, From 0f3f8b8623150ebf6b9410d6b05363dc2277acd8 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 01:01:51 +0800 Subject: [PATCH 38/91] Fixes --- .../runtimes/collectives/collectives-polkadot/src/impls.rs | 2 +- parachains/runtimes/collectives/collectives-polkadot/src/lib.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs b/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs index 4f821802172..da901ae0c33 100644 --- a/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs +++ b/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs @@ -65,7 +65,7 @@ where ::Origin::signed(temp_account.into()), Box::new(Parent.into()), Box::new( - Junction::AccountId32 { network: NetworkId::Any, id: treasury_acc.into() } + Junction::AccountId32 { network: None, id: treasury_acc.into() } .into() .into(), ), diff --git a/parachains/runtimes/collectives/collectives-polkadot/src/lib.rs b/parachains/runtimes/collectives/collectives-polkadot/src/lib.rs index cd947446e26..07a5239872d 100644 --- a/parachains/runtimes/collectives/collectives-polkadot/src/lib.rs +++ b/parachains/runtimes/collectives/collectives-polkadot/src/lib.rs @@ -367,6 +367,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = RootOrExecutiveSimpleMajority; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { From 57e44204d591c227dccdd8366d08cf12e680165e Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 01:16:01 +0800 Subject: [PATCH 39/91] Fixes --- .../runtimes/collectives/collectives-polkadot/src/impls.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs b/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs index da901ae0c33..34656f46e1a 100644 --- a/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs +++ b/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs @@ -21,7 +21,7 @@ use frame_support::{ }; use pallet_alliance::{ProposalIndex, ProposalProvider}; use sp_std::{boxed::Box, marker::PhantomData}; -use xcm::latest::{Fungibility, Junction, NetworkId, Parent}; +use xcm::latest::{Fungibility, Junction, Parent}; type AccountIdOf = ::AccountId; @@ -66,7 +66,7 @@ where Box::new(Parent.into()), Box::new( Junction::AccountId32 { network: None, id: treasury_acc.into() } - .into() + .into_location() .into(), ), Box::new((Parent, imbalance).into()), From d1230e3d77f8696a1928792bf7412380e5a982a8 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 01:39:43 +0800 Subject: [PATCH 40/91] Fixes --- parachains/runtimes/assets/westmint/src/xcm_config.rs | 2 +- parachains/runtimes/testing/penpal/src/xcm_config.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parachains/runtimes/assets/westmint/src/xcm_config.rs b/parachains/runtimes/assets/westmint/src/xcm_config.rs index c94feb50d75..3d5f6f9b804 100644 --- a/parachains/runtimes/assets/westmint/src/xcm_config.rs +++ b/parachains/runtimes/assets/westmint/src/xcm_config.rs @@ -179,7 +179,7 @@ impl xcm_executor::Config for XcmConfig { WeightToFee, pallet_assets::BalanceToAssetBalance, >, - ConvertedConcreteAssetId< + ConvertedConcreteId< AssetId, Balance, AsPrefixedGeneralIndex, diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index 97b53a9b72f..314faf38a64 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -43,7 +43,7 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedAssetId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -91,7 +91,7 @@ pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation: Assets, // Use this currency when it is a fungible asset matching the given location or name: - ConvertedAssetId< + ConvertedConcreteId< AssetIdPalletAssets, Balance, AsPrefixedGeneralIndex, From 3ba852b3bfb2f45c6b3d63f5215582b66f4412d7 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 02:11:44 +0800 Subject: [PATCH 41/91] Fixes --- parachains/runtimes/assets/statemine/src/xcm_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parachains/runtimes/assets/statemine/src/xcm_config.rs b/parachains/runtimes/assets/statemine/src/xcm_config.rs index 2b5598fce16..f79eca94b3a 100644 --- a/parachains/runtimes/assets/statemine/src/xcm_config.rs +++ b/parachains/runtimes/assets/statemine/src/xcm_config.rs @@ -183,7 +183,7 @@ impl xcm_executor::Config for XcmConfig { WeightToFee, pallet_assets::BalanceToAssetBalance, >, - ConvertedConcreteAssetId< + ConvertedConcreteId< AssetId, Balance, AsPrefixedGeneralIndex, From 31accbd975a6f858e633bec03cd441b7042cf8ed Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 16:38:08 +0800 Subject: [PATCH 42/91] Fixes --- .../runtimes/testing/penpal/src/xcm_config.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index 314faf38a64..c2ea08a8d3d 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -30,7 +30,7 @@ use frame_support::{ log, match_types, parameter_types, traits::{ fungibles::{self, Balanced, CreditOf}, - ConstU32, Contains, Everything, Get, Nothing, + ConstU32, Contains, ContainsPair, Everything, Get, Nothing, }, weights::Weight, }; @@ -49,7 +49,7 @@ use xcm_builder::{ SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::{ - traits::{FilterAssetLocation, JustTry, ShouldExecute}, + traits::{JustTry, ShouldExecute}, XcmExecutor, }; @@ -164,7 +164,7 @@ where { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + message: &mut [Instruction], max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { @@ -178,11 +178,11 @@ pub struct DenyReserveTransferToRelayChain; impl ShouldExecute for DenyReserveTransferToRelayChain { fn should_execute( origin: &MultiLocation, - message: &mut Xcm, + message: &mut [Instruction], _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { - if message.0.iter().any(|inst| { + if message.iter().any(|inst| { matches!( inst, InitiateReserveWithdraw { @@ -200,7 +200,7 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { // allow reserve transfers to arrive from relay chain if matches!(origin, MultiLocation { parents: 1, interior: Here }) && - message.0.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) + message.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) { log::warn!( target: "xcm::barriers", @@ -233,8 +233,8 @@ pub type AccountIdOf = ::AccountId; /// Asset filter that allows all assets from a certain location. pub struct AssetsFrom(PhantomData); -impl> FilterAssetLocation for AssetsFrom { - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl> ContainsPair for AssetsFrom { + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { let loc = T::get(); &loc == origin && matches!(asset, MultiAsset { id: AssetId::Concrete(asset_loc), fun: Fungible(_a) } @@ -297,7 +297,7 @@ impl Reserve for MultiAsset { /// A `FilterAssetLocation` implementation. Filters multi native assets whose /// reserve is same with `origin`. pub struct MultiNativeAsset; -impl FilterAssetLocation for MultiNativeAsset { +impl ContainsPair for MultiNativeAsset { fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { if let Some(ref reserve) = asset.reserve() { if reserve == origin { From 004f0449f46664619095e8b5303dfeea95d72235 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 16:57:51 +0800 Subject: [PATCH 43/91] Fixes --- parachains/runtimes/testing/penpal/src/xcm_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index c2ea08a8d3d..c2ea98591b8 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -298,7 +298,7 @@ impl Reserve for MultiAsset { /// reserve is same with `origin`. pub struct MultiNativeAsset; impl ContainsPair for MultiNativeAsset { - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { if let Some(ref reserve) = asset.reserve() { if reserve == origin { return true From bee34ba3f96139a07d2147946ed241351a80017c Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 18:13:24 +0800 Subject: [PATCH 44/91] Fixes --- parachains/runtimes/testing/penpal/src/xcm_config.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index c2ea98591b8..25d72572012 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -55,7 +55,7 @@ use xcm_executor::{ parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Any; + pub const RelayNetwork: Option = None; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); } @@ -388,4 +388,5 @@ impl pallet_xcm::Config for Runtime { impl cumulus_pallet_xcm::Config for Runtime { type Event = Event; type XcmExecutor = XcmExecutor; + type PriceForSiblingDelivery = (); } From 5c54d38b428f02e9137924bdf32efd155810dfec Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 18:50:29 +0800 Subject: [PATCH 45/91] Fixes --- parachains/runtimes/testing/penpal/src/lib.rs | 1 + parachains/runtimes/testing/penpal/src/xcm_config.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/parachains/runtimes/testing/penpal/src/lib.rs b/parachains/runtimes/testing/penpal/src/lib.rs index f0f2cb2a767..4842cc6a75c 100644 --- a/parachains/runtimes/testing/penpal/src/lib.rs +++ b/parachains/runtimes/testing/penpal/src/lib.rs @@ -435,6 +435,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = (); + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index 25d72572012..31b5cc7848f 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -388,5 +388,4 @@ impl pallet_xcm::Config for Runtime { impl cumulus_pallet_xcm::Config for Runtime { type Event = Event; type XcmExecutor = XcmExecutor; - type PriceForSiblingDelivery = (); } From 516669814078a308e8de659979aa0f97e8d67eff Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 25 Aug 2022 21:18:07 +0800 Subject: [PATCH 46/91] Fixes --- pallets/xcmp-queue/src/mock.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 62d56bfbfb8..bd30f52fb27 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -162,6 +162,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } pub type XcmRouter = ( From 1dba83a9031c22994795971fac9b1a8638c9efb5 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 22 Sep 2022 22:26:02 +0800 Subject: [PATCH 47/91] Fixes --- parachain-template/runtime/src/xcm_config.rs | 4 ++-- parachains/common/src/xcm_config.rs | 4 ++-- parachains/runtimes/testing/penpal/src/xcm_config.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/parachain-template/runtime/src/xcm_config.rs b/parachain-template/runtime/src/xcm_config.rs index 61349391f85..65ac5c83053 100644 --- a/parachain-template/runtime/src/xcm_config.rs +++ b/parachain-template/runtime/src/xcm_config.rs @@ -103,7 +103,7 @@ where { fn should_execute( origin: &MultiLocation, - message: &mut [Instruction], + message: &mut [Instruction], max_weight: XCMWeight, weight_credit: &mut XCMWeight, ) -> Result<(), ()> { @@ -117,7 +117,7 @@ pub struct DenyReserveTransferToRelayChain; impl ShouldExecute for DenyReserveTransferToRelayChain { fn should_execute( origin: &MultiLocation, - message: &mut [Instruction], + message: &mut [Instruction], _max_weight: XCMWeight, _weight_credit: &mut XCMWeight, ) -> Result<(), ()> { diff --git a/parachains/common/src/xcm_config.rs b/parachains/common/src/xcm_config.rs index 4b78f214000..73e6d9e93b6 100644 --- a/parachains/common/src/xcm_config.rs +++ b/parachains/common/src/xcm_config.rs @@ -24,7 +24,7 @@ where { fn should_execute( origin: &MultiLocation, - message: &mut [Instruction], + message: &mut [Instruction], max_weight: XCMWeight, weight_credit: &mut XCMWeight, ) -> Result<(), ()> { @@ -38,7 +38,7 @@ pub struct DenyReserveTransferToRelayChain; impl ShouldExecute for DenyReserveTransferToRelayChain { fn should_execute( origin: &MultiLocation, - message: &mut [Instruction], + message: &mut [Instruction], _max_weight: XCMWeight, _weight_credit: &mut XCMWeight, ) -> Result<(), ()> { diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index 6d96e59d3b2..5df62e71d7c 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -164,7 +164,7 @@ where { fn should_execute( origin: &MultiLocation, - message: &mut [Instruction], + message: &mut [Instruction], max_weight: XCMWeight, weight_credit: &mut XCMWeight, ) -> Result<(), ()> { @@ -178,7 +178,7 @@ pub struct DenyReserveTransferToRelayChain; impl ShouldExecute for DenyReserveTransferToRelayChain { fn should_execute( origin: &MultiLocation, - message: &mut [Instruction], + message: &mut [Instruction], _max_weight: XCMWeight, _weight_credit: &mut XCMWeight, ) -> Result<(), ()> { From e02878e87a46a865aec00ddd11351403aaee3bae Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 22 Sep 2022 23:16:22 +0800 Subject: [PATCH 48/91] Fixes --- pallets/dmp-queue/src/lib.rs | 3 ++- pallets/parachain-system/src/validate_block/tests.rs | 3 ++- pallets/xcm/src/lib.rs | 3 ++- parachains/common/src/xcm_config.rs | 8 +++++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 5c3a0dc5724..3dfcae436fb 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -240,7 +240,8 @@ pub mod pallet { Ok(Weight::zero()) }, Ok(Ok(x)) => { - let outcome = T::XcmExecutor::execute_xcm(Parent, x, message_id, limit.ref_time()); + let outcome = + T::XcmExecutor::execute_xcm(Parent, x, message_id, limit.ref_time()); match outcome { Outcome::Error(XcmError::WeightLimitReached(required)) => Err((message_id, Weight::from_ref_time(required))), diff --git a/pallets/parachain-system/src/validate_block/tests.rs b/pallets/parachain-system/src/validate_block/tests.rs index e06c2cafdcf..6087e6a29a3 100644 --- a/pallets/parachain-system/src/validate_block/tests.rs +++ b/pallets/parachain-system/src/validate_block/tests.rs @@ -284,7 +284,8 @@ fn check_inherents_are_unsigned_and_before_all_other_extrinsics() { .expect("Runs the test"); assert!(output.status.success()); - assert!(String::from_utf8(output.stderr).unwrap() + assert!(String::from_utf8(output.stderr) + .unwrap() .contains("Could not find `set_validation_data` inherent")); } } diff --git a/pallets/xcm/src/lib.rs b/pallets/xcm/src/lib.rs index 1d87187cdef..bd015dce171 100644 --- a/pallets/xcm/src/lib.rs +++ b/pallets/xcm/src/lib.rs @@ -157,7 +157,8 @@ impl DmpMessageHandler for LimitAndDropDmpExecution { Ok(Err(())) => Pallet::::deposit_event(Event::UnsupportedVersion(id)), Ok(Ok(x)) => { let weight_limit = limit.saturating_sub(used); - let outcome = T::XcmExecutor::execute_xcm(Parent, x, id, weight_limit.ref_time()); + let outcome = + T::XcmExecutor::execute_xcm(Parent, x, id, weight_limit.ref_time()); used += Weight::from_ref_time(outcome.weight_used()); Pallet::::deposit_event(Event::ExecutedDownward(id, outcome)); }, diff --git a/parachains/common/src/xcm_config.rs b/parachains/common/src/xcm_config.rs index 73e6d9e93b6..91d1c46a2fa 100644 --- a/parachains/common/src/xcm_config.rs +++ b/parachains/common/src/xcm_config.rs @@ -2,7 +2,7 @@ use crate::impls::AccountIdOf; use core::marker::PhantomData; use frame_support::{ log, - traits::{fungibles::Inspect, tokens::BalanceConversion}, + traits::{fungibles::Inspect, tokens::BalanceConversion, ContainsPair}, weights::{Weight, WeightToFee, WeightToFeePolynomial}, }; use sp_runtime::traits::Get; @@ -111,8 +111,10 @@ where /// Accepts an asset if it is a native asset from a particular `MultiLocation`. pub struct ConcreteNativeAssetFrom(PhantomData); -impl> FilterAssetLocation for ConcreteNativeAssetFrom { - fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl> ContainsPair + for ConcreteNativeAssetFrom +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { log::trace!(target: "xcm::filter_asset_location", "ConcreteNativeAsset asset: {:?}, origin: {:?}, location: {:?}", asset, origin, Location::get()); From 25958aa09f29522f847781c870a943972f308dad Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Fri, 23 Sep 2022 20:27:31 +0800 Subject: [PATCH 49/91] Fixes --- parachains/common/src/xcm_config.rs | 2 +- .../runtimes/contracts/contracts-rococo/src/xcm_config.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parachains/common/src/xcm_config.rs b/parachains/common/src/xcm_config.rs index 91d1c46a2fa..9a9774ae94f 100644 --- a/parachains/common/src/xcm_config.rs +++ b/parachains/common/src/xcm_config.rs @@ -7,7 +7,7 @@ use frame_support::{ }; use sp_runtime::traits::Get; use xcm::latest::{prelude::*, Weight as XCMWeight}; -use xcm_executor::traits::{FilterAssetLocation, ShouldExecute}; +use xcm_executor::traits::ShouldExecute; //TODO: move DenyThenTry to polkadot's xcm module. /// Deny executing the XCM if it matches any of the Deny filter regardless of anything else. diff --git a/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs b/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs index a2bd6284f5c..fc0b834c4a3 100644 --- a/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -156,7 +156,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// Converts a local signed origin into an XCM multilocation. From 9e309844a8207a05223bcf9e8be83a6e4ff3eda4 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 27 Sep 2022 14:52:04 +0800 Subject: [PATCH 50/91] Fixes --- pallets/dmp-queue/src/lib.rs | 2 +- pallets/xcmp-queue/src/mock.rs | 2 +- parachain-template/runtime/src/xcm_config.rs | 2 +- .../runtimes/assets/statemine/src/xcm_config.rs | 11 +++++------ .../runtimes/assets/statemint/src/xcm_config.rs | 2 +- parachains/runtimes/assets/westmint/src/xcm_config.rs | 2 +- .../collectives-polkadot/src/xcm_config.rs | 2 +- parachains/runtimes/starters/shell/src/xcm_config.rs | 2 +- parachains/runtimes/testing/penpal/src/xcm_config.rs | 2 +- .../runtimes/testing/rococo-parachain/src/lib.rs | 2 +- 10 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 3dfcae436fb..6cddb956d52 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -434,7 +434,7 @@ mod tests { } pub struct MockExec; - impl ExecuteXcm for MockExec { + impl ExecuteXcm for MockExec { type Prepared = Weightless; fn prepare(message: Xcm) -> Result { diff --git a/pallets/xcmp-queue/src/mock.rs b/pallets/xcmp-queue/src/mock.rs index 17ca7129a37..bd71ba363e5 100644 --- a/pallets/xcmp-queue/src/mock.rs +++ b/pallets/xcmp-queue/src/mock.rs @@ -162,7 +162,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } pub type XcmRouter = ( diff --git a/parachain-template/runtime/src/xcm_config.rs b/parachain-template/runtime/src/xcm_config.rs index 65ac5c83053..c023a5a6e6d 100644 --- a/parachain-template/runtime/src/xcm_config.rs +++ b/parachain-template/runtime/src/xcm_config.rs @@ -187,7 +187,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// No local origins on this chain are allowed to dispatch XCM sends/executions. diff --git a/parachains/runtimes/assets/statemine/src/xcm_config.rs b/parachains/runtimes/assets/statemine/src/xcm_config.rs index ccf96219c40..ed04677be4c 100644 --- a/parachains/runtimes/assets/statemine/src/xcm_config.rs +++ b/parachains/runtimes/assets/statemine/src/xcm_config.rs @@ -34,11 +34,10 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, - WeightInfoBounds, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FungiblesAdapter, IsConcrete, + NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, WeightInfoBounds, }; use xcm_executor::{traits::JustTry, XcmExecutor}; @@ -210,7 +209,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// Converts a local signed origin into an XCM multilocation. diff --git a/parachains/runtimes/assets/statemint/src/xcm_config.rs b/parachains/runtimes/assets/statemint/src/xcm_config.rs index 3daa00b4f3f..16720f6fefc 100644 --- a/parachains/runtimes/assets/statemint/src/xcm_config.rs +++ b/parachains/runtimes/assets/statemint/src/xcm_config.rs @@ -186,7 +186,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// Converts a local signed origin into an XCM multilocation. diff --git a/parachains/runtimes/assets/westmint/src/xcm_config.rs b/parachains/runtimes/assets/westmint/src/xcm_config.rs index a46cbfa2e56..837a6522bf0 100644 --- a/parachains/runtimes/assets/westmint/src/xcm_config.rs +++ b/parachains/runtimes/assets/westmint/src/xcm_config.rs @@ -206,7 +206,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. diff --git a/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs b/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs index e3e4e93d5d9..1e7b17de09a 100644 --- a/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs +++ b/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs @@ -158,7 +158,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// Converts a local signed origin into an XCM multilocation. diff --git a/parachains/runtimes/starters/shell/src/xcm_config.rs b/parachains/runtimes/starters/shell/src/xcm_config.rs index 51f774f6d60..6706c6af6bb 100644 --- a/parachains/runtimes/starters/shell/src/xcm_config.rs +++ b/parachains/runtimes/starters/shell/src/xcm_config.rs @@ -74,7 +74,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index 5df62e71d7c..62ae7fc3a95 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -344,7 +344,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// No local origins on this chain are allowed to dispatch XCM sends/executions. diff --git a/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 7e81edaec3a..ec0fd38a3ed 100644 --- a/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -424,7 +424,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. From 013534a684a47c60fe44763e84b9722fd2f6a792 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 27 Sep 2022 16:39:24 +0800 Subject: [PATCH 51/91] Fixes --- pallets/dmp-queue/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index 6cddb956d52..c42024e9f8a 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -31,7 +31,10 @@ pub use pallet::*; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; use sp_std::{convert::TryFrom, prelude::*}; -use xcm::{latest::prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH}; +use xcm::{ + latest::{prelude::*, Weight as XCMWeight}, + VersionedXcm, MAX_XCM_DECODE_DEPTH, +}; #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ConfigData { @@ -428,7 +431,7 @@ mod tests { pub enum Weightless {} impl PreparedMessage for Weightless { - fn weight_of(&self) -> Weight { + fn weight_of(&self) -> XCMWeight { unreachable!() } } From 4984ef30bf1eac29bd2c885756cf15979534ff61 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 27 Sep 2022 16:53:10 +0800 Subject: [PATCH 52/91] Remove unused import --- pallets/dmp-queue/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pallets/dmp-queue/src/lib.rs b/pallets/dmp-queue/src/lib.rs index c42024e9f8a..107480e39be 100644 --- a/pallets/dmp-queue/src/lib.rs +++ b/pallets/dmp-queue/src/lib.rs @@ -31,10 +31,7 @@ pub use pallet::*; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; use sp_std::{convert::TryFrom, prelude::*}; -use xcm::{ - latest::{prelude::*, Weight as XCMWeight}, - VersionedXcm, MAX_XCM_DECODE_DEPTH, -}; +use xcm::{latest::prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH}; #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ConfigData { From 5efee4c47e2a7aaebdc2a3cde665093a0175e9f3 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 27 Sep 2022 16:56:01 +0800 Subject: [PATCH 53/91] Remove unused import --- parachains/runtimes/assets/westmint/src/xcm_config.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/parachains/runtimes/assets/westmint/src/xcm_config.rs b/parachains/runtimes/assets/westmint/src/xcm_config.rs index 837a6522bf0..a9d80012619 100644 --- a/parachains/runtimes/assets/westmint/src/xcm_config.rs +++ b/parachains/runtimes/assets/westmint/src/xcm_config.rs @@ -34,11 +34,10 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, - WeightInfoBounds, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FungiblesAdapter, IsConcrete, + NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, WeightInfoBounds, }; use xcm_executor::{traits::JustTry, XcmExecutor}; From 483b20c699a4bf0803f35c878a65c8dbc47541b8 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 3 Oct 2022 14:51:39 +0200 Subject: [PATCH 54/91] XCMv3 fixes (#1710) * Fixes XCMv3 related Fixes XCMv3 (removed query_holding) Fixes XCMv3 - should use _depositable_count? Fixes XCMv3 - removed TrustedReserve Fixes - missing weights for statemine/statemint/westmint [DO-NOT-CHERRY-PICK] tmp return query_holding to aviod conficts to master Fixes - missing functions for pallet_xcm_benchmarks::generic::Config Fixes for XCMv3 benchmarking Fix xcm - removed query_holding * ".git/.scripts/bench-bot.sh" xcm statemine assets pallet_xcm_benchmarks::generic * ".git/.scripts/bench-bot.sh" xcm statemint assets pallet_xcm_benchmarks::generic * ".git/.scripts/bench-bot.sh" xcm westmint assets pallet_xcm_benchmarks::generic * Fix imports --- parachain-template/runtime/src/xcm_config.rs | 6 +- .../runtimes/assets/statemine/src/lib.rs | 16 +++- .../assets/statemine/src/weights/xcm/mod.rs | 96 +++++++++++++++---- .../xcm/pallet_xcm_benchmarks_generic.rs | 91 ++++++++++++++---- .../assets/statemine/src/xcm_config.rs | 7 +- .../runtimes/assets/statemint/src/lib.rs | 16 +++- .../assets/statemint/src/weights/xcm/mod.rs | 96 +++++++++++++++---- .../xcm/pallet_xcm_benchmarks_generic.rs | 90 +++++++++++++---- .../assets/statemint/src/xcm_config.rs | 16 ++-- .../runtimes/assets/westmint/src/lib.rs | 16 +++- .../assets/westmint/src/weights/xcm/mod.rs | 96 +++++++++++++++---- .../xcm/pallet_xcm_benchmarks_generic.rs | 91 ++++++++++++++---- .../assets/westmint/src/xcm_config.rs | 7 +- .../collectives-polkadot/src/xcm_config.rs | 8 +- .../contracts-rococo/src/xcm_config.rs | 6 +- .../runtimes/starters/shell/src/xcm_config.rs | 7 +- .../runtimes/testing/penpal/src/xcm_config.rs | 8 +- .../testing/rococo-parachain/src/lib.rs | 2 +- 18 files changed, 515 insertions(+), 160 deletions(-) diff --git a/parachain-template/runtime/src/xcm_config.rs b/parachain-template/runtime/src/xcm_config.rs index c023a5a6e6d..03e090009e0 100644 --- a/parachain-template/runtime/src/xcm_config.rs +++ b/parachain-template/runtime/src/xcm_config.rs @@ -1,6 +1,6 @@ use super::{ - AccountId, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, }; use core::marker::PhantomData; use frame_support::{ @@ -180,7 +180,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = (); + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); diff --git a/parachains/runtimes/assets/statemine/src/lib.rs b/parachains/runtimes/assets/statemine/src/lib.rs index 44f96c1af66..d09394c8232 100644 --- a/parachains/runtimes/assets/statemine/src/lib.rs +++ b/parachains/runtimes/assets/statemine/src/lib.rs @@ -849,7 +849,7 @@ impl_runtime_apis! { fn valid_destination() -> Result { Ok(KsmLocation::get()) } - fn worst_case_holding() -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { // A mix of fungible, non-fungible, and concrete assets. const HOLDING_FUNGIBLES: u32 = 100; const HOLDING_NON_FUNGIBLES: u32 = 100; @@ -882,7 +882,6 @@ impl_runtime_apis! { KsmLocation::get(), MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(KsmLocation::get()) }, )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; pub const CheckedAccount: Option = None; } @@ -892,7 +891,6 @@ impl_runtime_apis! { type CheckedAccount = CheckedAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { MultiAsset { @@ -909,6 +907,14 @@ impl_runtime_apis! { (0u64, Response::Version(Default::default())) } + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result { + Err(BenchmarkError::Skip) + } + fn transact_origin() -> Result { Ok(KsmLocation::get()) } @@ -923,6 +929,10 @@ impl_runtime_apis! { let ticket = MultiLocation { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + Err(BenchmarkError::Skip) + } } type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; diff --git a/parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs b/parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs index 7972667cf06..0ca8a414e44 100644 --- a/parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs +++ b/parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs @@ -62,7 +62,12 @@ impl XcmWeightInfo for StatemineXcmWeight { fn receive_teleported_asset(assets: &MultiAssets) -> XCMWeight { assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) } - fn query_response(_query_id: &u64, _response: &Response, _max_weight: &u64) -> XCMWeight { + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &u64, + _querier: &Option, + ) -> XCMWeight { XcmGeneric::::query_response().ref_time() } fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> XCMWeight { @@ -104,19 +109,11 @@ impl XcmWeightInfo for StatemineXcmWeight { fn descend_origin(_who: &InteriorMultiLocation) -> XCMWeight { XcmGeneric::::descend_origin().ref_time() } - fn report_error( - _query_id: &QueryId, - _dest: &MultiLocation, - _max_response_weight: &u64, - ) -> XCMWeight { + fn report_error(_query_response_info: &QueryResponseInfo) -> XCMWeight { XcmGeneric::::report_error().ref_time() } - fn deposit_asset( - assets: &MultiAssetFilter, - _max_assets: &u32, - _dest: &MultiLocation, - ) -> XCMWeight { + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> XCMWeight { // Hardcoded till the XCM pallet is fixed let hardcoded_weight = Weight::from_ref_time(1_000_000_000 as u64).ref_time(); let weight = assets.weigh_multi_assets(XcmFungibleWeight::::deposit_asset()); @@ -124,13 +121,16 @@ impl XcmWeightInfo for StatemineXcmWeight { } fn deposit_reserve_asset( assets: &MultiAssetFilter, - _max_assets: &u32, _dest: &MultiLocation, _xcm: &Xcm<()>, ) -> XCMWeight { assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets) -> XCMWeight { + fn exchange_asset( + _give: &MultiAssetFilter, + _receive: &MultiAssets, + _maximal: &bool, + ) -> XCMWeight { Weight::MAX.ref_time() } fn initiate_reserve_withdraw( @@ -150,13 +150,8 @@ impl XcmWeightInfo for StatemineXcmWeight { let weight = assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()); cmp::min(hardcoded_weight, weight) } - fn query_holding( - _query_id: &u64, - _dest: &MultiLocation, - _assets: &MultiAssetFilter, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::query_holding().ref_time() + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> XCMWeight { + XcmGeneric::::report_holding().ref_time() } fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> XCMWeight { XcmGeneric::::buy_execution().ref_time() @@ -185,4 +180,65 @@ impl XcmWeightInfo for StatemineXcmWeight { fn unsubscribe_version() -> XCMWeight { XcmGeneric::::unsubscribe_version().ref_time() } + fn burn_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> XCMWeight { + XcmGeneric::::expect_origin().ref_time() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> XCMWeight { + XcmGeneric::::expect_error().ref_time() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::query_pallet().ref_time() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> XCMWeight { + XcmGeneric::::expect_pallet().ref_time() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::report_transact_status().ref_time() + } + fn clear_transact_status() -> XCMWeight { + XcmGeneric::::clear_transact_status().ref_time() + } + fn universal_origin(_: &Junction) -> XCMWeight { + Weight::MAX.ref_time() + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> XCMWeight { + Weight::MAX.ref_time() + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn set_fees_mode(_: &bool) -> XCMWeight { + XcmGeneric::::set_fees_mode().ref_time() + } + fn set_topic(_topic: &[u8; 32]) -> XCMWeight { + XcmGeneric::::set_topic().ref_time() + } + fn clear_topic() -> XCMWeight { + XcmGeneric::::clear_topic().ref_time() + } + fn alias_origin(_: &MultiLocation) -> XCMWeight { + // XCM Executor does not currently support alias origin operations + Weight::MAX.ref_time() + } } diff --git a/parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 6a9f36b7684..689c9a8220a 100644 --- a/parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -18,7 +18,8 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-08-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-09-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemine-dev"), DB CACHE: 1024 // Executed Command: @@ -36,7 +37,7 @@ // --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/cumulus/.git/.artifacts/bench.json // --header=./file_header.txt // --template=./templates/xcm-bench-template.hbs -// --output=./parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --output=./parachains/runtimes/assets/statemine/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,58 +55,59 @@ impl WeightInfo { // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - pub(crate) fn query_holding() -> Weight { - Weight::from_ref_time(694_466_000 as u64) + pub(crate) fn report_holding() -> Weight { + Weight::from_ref_time(1_303_495_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } pub(crate) fn buy_execution() -> Weight { - Weight::from_ref_time(7_095_000 as u64) + Weight::from_ref_time(8_667_000 as u64) } // Storage: PolkadotXcm Queries (r:1 w:0) pub(crate) fn query_response() -> Weight { - Weight::from_ref_time(13_270_000 as u64) + Weight::from_ref_time(19_292_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } pub(crate) fn transact() -> Weight { - Weight::from_ref_time(16_375_000 as u64) + Weight::from_ref_time(37_996_000 as u64) } pub(crate) fn refund_surplus() -> Weight { - Weight::from_ref_time(7_319_000 as u64) + Weight::from_ref_time(9_076_000 as u64) } pub(crate) fn set_error_handler() -> Weight { - Weight::from_ref_time(3_515_000 as u64) + Weight::from_ref_time(6_410_000 as u64) } pub(crate) fn set_appendix() -> Weight { - Weight::from_ref_time(3_501_000 as u64) + Weight::from_ref_time(6_412_000 as u64) } pub(crate) fn clear_error() -> Weight { - Weight::from_ref_time(3_459_000 as u64) + Weight::from_ref_time(6_311_000 as u64) } pub(crate) fn descend_origin() -> Weight { - Weight::from_ref_time(4_319_000 as u64) + Weight::from_ref_time(7_355_000 as u64) } pub(crate) fn clear_origin() -> Weight { - Weight::from_ref_time(3_511_000 as u64) + Weight::from_ref_time(6_389_000 as u64) } + // Storage: ParachainInfo ParachainId (r:1 w:0) // Storage: PolkadotXcm SupportedVersion (r:1 w:0) // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn report_error() -> Weight { - Weight::from_ref_time(13_284_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) + Weight::from_ref_time(23_020_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: PolkadotXcm AssetTraps (r:1 w:1) pub(crate) fn claim_asset() -> Weight { - Weight::from_ref_time(7_985_000 as u64) + Weight::from_ref_time(13_613_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } pub(crate) fn trap() -> Weight { - Weight::from_ref_time(3_515_000 as u64) + Weight::from_ref_time(6_457_000 as u64) } // Storage: PolkadotXcm VersionNotifyTargets (r:1 w:1) // Storage: PolkadotXcm SupportedVersion (r:1 w:0) @@ -114,13 +116,13 @@ impl WeightInfo { // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn subscribe_version() -> Weight { - Weight::from_ref_time(16_657_000 as u64) + Weight::from_ref_time(31_677_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: PolkadotXcm VersionNotifyTargets (r:0 w:1) pub(crate) fn unsubscribe_version() -> Weight { - Weight::from_ref_time(5_622_000 as u64) + Weight::from_ref_time(9_613_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: ParachainInfo ParachainId (r:1 w:0) @@ -130,8 +132,57 @@ impl WeightInfo { // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn initiate_reserve_withdraw() -> Weight { - Weight::from_ref_time(878_786_000 as u64) + Weight::from_ref_time(1_588_580_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn burn_asset() -> Weight { + Weight::from_ref_time(497_452_000 as u64) + } + pub(crate) fn expect_asset() -> Weight { + Weight::from_ref_time(38_502_000 as u64) + } + pub(crate) fn expect_origin() -> Weight { + Weight::from_ref_time(6_427_000 as u64) + } + pub(crate) fn expect_error() -> Weight { + Weight::from_ref_time(6_303_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn query_pallet() -> Weight { + Weight::from_ref_time(25_510_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } + pub(crate) fn expect_pallet() -> Weight { + Weight::from_ref_time(7_909_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn report_transact_status() -> Weight { + Weight::from_ref_time(22_949_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn clear_transact_status() -> Weight { + Weight::from_ref_time(6_491_000 as u64) + } + pub(crate) fn set_topic() -> Weight { + Weight::from_ref_time(6_527_000 as u64) + } + pub(crate) fn clear_topic() -> Weight { + Weight::from_ref_time(6_440_000 as u64) + } + pub(crate) fn set_fees_mode() -> Weight { + Weight::from_ref_time(6_426_000 as u64) + } } diff --git a/parachains/runtimes/assets/statemine/src/xcm_config.rs b/parachains/runtimes/assets/statemine/src/xcm_config.rs index ed04677be4c..4841ee7c518 100644 --- a/parachains/runtimes/assets/statemine/src/xcm_config.rs +++ b/parachains/runtimes/assets/statemine/src/xcm_config.rs @@ -14,8 +14,9 @@ // limitations under the License. use super::{ - AccountId, AssetId, Assets, Authorship, Balance, Balances, ParachainInfo, ParachainSystem, - PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, AssetId, Assets, Authorship, Balance, Balances, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, + XcmpQueue, }; use frame_support::{ match_types, parameter_types, @@ -202,7 +203,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = (); + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); diff --git a/parachains/runtimes/assets/statemint/src/lib.rs b/parachains/runtimes/assets/statemint/src/lib.rs index 151259f839a..df2425d3667 100644 --- a/parachains/runtimes/assets/statemint/src/lib.rs +++ b/parachains/runtimes/assets/statemint/src/lib.rs @@ -878,7 +878,7 @@ impl_runtime_apis! { fn valid_destination() -> Result { Ok(DotLocation::get()) } - fn worst_case_holding() -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { // A mix of fungible, non-fungible, and concrete assets. const HOLDING_FUNGIBLES: u32 = 100; const HOLDING_NON_FUNGIBLES: u32 = 100; @@ -911,7 +911,6 @@ impl_runtime_apis! { DotLocation::get(), MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(DotLocation::get()) }, )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; pub const CheckedAccount: Option = None; } @@ -920,7 +919,6 @@ impl_runtime_apis! { type CheckedAccount = CheckedAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { MultiAsset { @@ -937,6 +935,14 @@ impl_runtime_apis! { (0u64, Response::Version(Default::default())) } + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result { + Err(BenchmarkError::Skip) + } + fn transact_origin() -> Result { Ok(DotLocation::get()) } @@ -951,6 +957,10 @@ impl_runtime_apis! { let ticket = MultiLocation { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + Err(BenchmarkError::Skip) + } } type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; diff --git a/parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs b/parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs index 1a0ffcdb229..512be255779 100644 --- a/parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs +++ b/parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs @@ -62,7 +62,12 @@ impl XcmWeightInfo for StatemintXcmWeight { fn receive_teleported_asset(assets: &MultiAssets) -> XCMWeight { assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) } - fn query_response(_query_id: &u64, _response: &Response, _max_weight: &u64) -> XCMWeight { + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &u64, + _querier: &Option, + ) -> XCMWeight { XcmGeneric::::query_response().ref_time() } fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> XCMWeight { @@ -104,19 +109,11 @@ impl XcmWeightInfo for StatemintXcmWeight { fn descend_origin(_who: &InteriorMultiLocation) -> XCMWeight { XcmGeneric::::descend_origin().ref_time() } - fn report_error( - _query_id: &QueryId, - _dest: &MultiLocation, - _max_response_weight: &u64, - ) -> XCMWeight { + fn report_error(_query_response_info: &QueryResponseInfo) -> XCMWeight { XcmGeneric::::report_error().ref_time() } - fn deposit_asset( - assets: &MultiAssetFilter, - _max_assets: &u32, - _dest: &MultiLocation, - ) -> XCMWeight { + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> XCMWeight { // Hardcoded till the XCM pallet is fixed let hardcoded_weight = Weight::from_ref_time(1_000_000_000 as u64).ref_time(); let weight = assets.weigh_multi_assets(XcmFungibleWeight::::deposit_asset()); @@ -124,13 +121,16 @@ impl XcmWeightInfo for StatemintXcmWeight { } fn deposit_reserve_asset( assets: &MultiAssetFilter, - _max_assets: &u32, _dest: &MultiLocation, _xcm: &Xcm<()>, ) -> XCMWeight { assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets) -> XCMWeight { + fn exchange_asset( + _give: &MultiAssetFilter, + _receive: &MultiAssets, + _maximal: &bool, + ) -> XCMWeight { Weight::MAX.ref_time() } fn initiate_reserve_withdraw( @@ -150,13 +150,8 @@ impl XcmWeightInfo for StatemintXcmWeight { let weight = assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()); cmp::min(hardcoded_weight, weight) } - fn query_holding( - _query_id: &u64, - _dest: &MultiLocation, - _assets: &MultiAssetFilter, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::query_holding().ref_time() + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> XCMWeight { + XcmGeneric::::report_holding().ref_time() } fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> XCMWeight { XcmGeneric::::buy_execution().ref_time() @@ -185,4 +180,65 @@ impl XcmWeightInfo for StatemintXcmWeight { fn unsubscribe_version() -> XCMWeight { XcmGeneric::::unsubscribe_version().ref_time() } + fn burn_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> XCMWeight { + XcmGeneric::::expect_origin().ref_time() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> XCMWeight { + XcmGeneric::::expect_error().ref_time() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::query_pallet().ref_time() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> XCMWeight { + XcmGeneric::::expect_pallet().ref_time() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::report_transact_status().ref_time() + } + fn clear_transact_status() -> XCMWeight { + XcmGeneric::::clear_transact_status().ref_time() + } + fn universal_origin(_: &Junction) -> XCMWeight { + Weight::MAX.ref_time() + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> XCMWeight { + Weight::MAX.ref_time() + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn set_fees_mode(_: &bool) -> XCMWeight { + XcmGeneric::::set_fees_mode().ref_time() + } + fn set_topic(_topic: &[u8; 32]) -> XCMWeight { + XcmGeneric::::set_topic().ref_time() + } + fn clear_topic() -> XCMWeight { + XcmGeneric::::clear_topic().ref_time() + } + fn alias_origin(_: &MultiLocation) -> XCMWeight { + // XCM Executor does not currently support alias origin operations + Weight::MAX.ref_time() + } } diff --git a/parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 7af8b8b280b..6ed0d1adff2 100644 --- a/parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-08-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-09-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemint-dev"), DB CACHE: 1024 @@ -37,7 +37,7 @@ // --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/cumulus/.git/.artifacts/bench.json // --header=./file_header.txt // --template=./templates/xcm-bench-template.hbs -// --output=./parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --output=./parachains/runtimes/assets/statemint/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,58 +55,59 @@ impl WeightInfo { // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - pub(crate) fn query_holding() -> Weight { - Weight::from_ref_time(682_639_000 as u64) + pub(crate) fn report_holding() -> Weight { + Weight::from_ref_time(1_305_689_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } pub(crate) fn buy_execution() -> Weight { - Weight::from_ref_time(9_272_000 as u64) + Weight::from_ref_time(8_843_000 as u64) } // Storage: PolkadotXcm Queries (r:1 w:0) pub(crate) fn query_response() -> Weight { - Weight::from_ref_time(17_084_000 as u64) + Weight::from_ref_time(19_216_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } pub(crate) fn transact() -> Weight { - Weight::from_ref_time(20_265_000 as u64) + Weight::from_ref_time(22_708_000 as u64) } pub(crate) fn refund_surplus() -> Weight { - Weight::from_ref_time(9_422_000 as u64) + Weight::from_ref_time(9_040_000 as u64) } pub(crate) fn set_error_handler() -> Weight { - Weight::from_ref_time(5_545_000 as u64) + Weight::from_ref_time(6_222_000 as u64) } pub(crate) fn set_appendix() -> Weight { - Weight::from_ref_time(5_450_000 as u64) + Weight::from_ref_time(6_411_000 as u64) } pub(crate) fn clear_error() -> Weight { - Weight::from_ref_time(5_519_000 as u64) + Weight::from_ref_time(6_222_000 as u64) } pub(crate) fn descend_origin() -> Weight { - Weight::from_ref_time(6_398_000 as u64) + Weight::from_ref_time(7_112_000 as u64) } pub(crate) fn clear_origin() -> Weight { - Weight::from_ref_time(5_498_000 as u64) + Weight::from_ref_time(6_340_000 as u64) } + // Storage: ParachainInfo ParachainId (r:1 w:0) // Storage: PolkadotXcm SupportedVersion (r:1 w:0) // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn report_error() -> Weight { - Weight::from_ref_time(15_784_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) + Weight::from_ref_time(22_943_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: PolkadotXcm AssetTraps (r:1 w:1) pub(crate) fn claim_asset() -> Weight { - Weight::from_ref_time(11_861_000 as u64) + Weight::from_ref_time(13_178_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } pub(crate) fn trap() -> Weight { - Weight::from_ref_time(5_462_000 as u64) + Weight::from_ref_time(6_333_000 as u64) } // Storage: PolkadotXcm VersionNotifyTargets (r:1 w:1) // Storage: PolkadotXcm SupportedVersion (r:1 w:0) @@ -115,13 +116,13 @@ impl WeightInfo { // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn subscribe_version() -> Weight { - Weight::from_ref_time(18_997_000 as u64) + Weight::from_ref_time(31_798_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: PolkadotXcm VersionNotifyTargets (r:0 w:1) pub(crate) fn unsubscribe_version() -> Weight { - Weight::from_ref_time(8_684_000 as u64) + Weight::from_ref_time(9_728_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: ParachainInfo ParachainId (r:1 w:0) @@ -131,8 +132,57 @@ impl WeightInfo { // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn initiate_reserve_withdraw() -> Weight { - Weight::from_ref_time(883_121_000 as u64) + Weight::from_ref_time(1_583_652_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn burn_asset() -> Weight { + Weight::from_ref_time(497_448_000 as u64) + } + pub(crate) fn expect_asset() -> Weight { + Weight::from_ref_time(38_383_000 as u64) + } + pub(crate) fn expect_origin() -> Weight { + Weight::from_ref_time(6_308_000 as u64) + } + pub(crate) fn expect_error() -> Weight { + Weight::from_ref_time(6_327_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn query_pallet() -> Weight { + Weight::from_ref_time(26_011_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } + pub(crate) fn expect_pallet() -> Weight { + Weight::from_ref_time(8_008_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn report_transact_status() -> Weight { + Weight::from_ref_time(22_963_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn clear_transact_status() -> Weight { + Weight::from_ref_time(6_378_000 as u64) + } + pub(crate) fn set_topic() -> Weight { + Weight::from_ref_time(6_313_000 as u64) + } + pub(crate) fn clear_topic() -> Weight { + Weight::from_ref_time(6_324_000 as u64) + } + pub(crate) fn set_fees_mode() -> Weight { + Weight::from_ref_time(6_336_000 as u64) + } } diff --git a/parachains/runtimes/assets/statemint/src/xcm_config.rs b/parachains/runtimes/assets/statemint/src/xcm_config.rs index 16720f6fefc..bb9d82680fe 100644 --- a/parachains/runtimes/assets/statemint/src/xcm_config.rs +++ b/parachains/runtimes/assets/statemint/src/xcm_config.rs @@ -14,8 +14,9 @@ // limitations under the License. use super::{ - AccountId, AssetId, Assets, Authorship, Balance, Balances, ParachainInfo, ParachainSystem, - PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, AssetId, Assets, Authorship, Balance, Balances, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, + XcmpQueue, }; use frame_support::{ match_types, parameter_types, @@ -31,11 +32,10 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, - WeightInfoBounds, + ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FungiblesAdapter, IsConcrete, + NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, WeightInfoBounds, }; use xcm_executor::{traits::JustTry, XcmExecutor}; @@ -179,7 +179,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = (); + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); diff --git a/parachains/runtimes/assets/westmint/src/lib.rs b/parachains/runtimes/assets/westmint/src/lib.rs index 1f1d86e1e7e..a227572609a 100644 --- a/parachains/runtimes/assets/westmint/src/lib.rs +++ b/parachains/runtimes/assets/westmint/src/lib.rs @@ -838,7 +838,7 @@ impl_runtime_apis! { fn valid_destination() -> Result { Ok(WestendLocation::get()) } - fn worst_case_holding() -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { // A mix of fungible, non-fungible, and concrete assets. const HOLDING_FUNGIBLES: u32 = 100; const HOLDING_NON_FUNGIBLES: u32 = 100; @@ -871,7 +871,6 @@ impl_runtime_apis! { WestendLocation::get(), MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(WestendLocation::get()) }, )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; pub const CheckedAccount: Option = None; } @@ -881,7 +880,6 @@ impl_runtime_apis! { type CheckedAccount = CheckedAccount; type TrustedTeleporter = TrustedTeleporter; - type TrustedReserve = TrustedReserve; fn get_multi_asset() -> MultiAsset { MultiAsset { @@ -898,6 +896,14 @@ impl_runtime_apis! { (0u64, Response::Version(Default::default())) } + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result { + Err(BenchmarkError::Skip) + } + fn transact_origin() -> Result { Ok(WestendLocation::get()) } @@ -912,6 +918,10 @@ impl_runtime_apis! { let ticket = MultiLocation { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + Err(BenchmarkError::Skip) + } } type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; diff --git a/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs b/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs index 5f6bf034fdc..56852eee6ab 100644 --- a/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs +++ b/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs @@ -62,7 +62,12 @@ impl XcmWeightInfo for WestmintXcmWeight { fn receive_teleported_asset(assets: &MultiAssets) -> XCMWeight { assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) } - fn query_response(_query_id: &u64, _response: &Response, _max_weight: &u64) -> XCMWeight { + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &u64, + _querier: &Option, + ) -> XCMWeight { XcmGeneric::::query_response().ref_time() } fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> XCMWeight { @@ -104,19 +109,11 @@ impl XcmWeightInfo for WestmintXcmWeight { fn descend_origin(_who: &InteriorMultiLocation) -> XCMWeight { XcmGeneric::::descend_origin().ref_time() } - fn report_error( - _query_id: &QueryId, - _dest: &MultiLocation, - _max_response_weight: &u64, - ) -> XCMWeight { + fn report_error(_query_response_info: &QueryResponseInfo) -> XCMWeight { XcmGeneric::::report_error().ref_time() } - fn deposit_asset( - assets: &MultiAssetFilter, - _max_assets: &u32, - _dest: &MultiLocation, - ) -> XCMWeight { + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> XCMWeight { // Hardcoded till the XCM pallet is fixed let hardcoded_weight = Weight::from_ref_time(1_000_000_000 as u64).ref_time(); let weight = assets.weigh_multi_assets(XcmFungibleWeight::::deposit_asset()); @@ -124,13 +121,16 @@ impl XcmWeightInfo for WestmintXcmWeight { } fn deposit_reserve_asset( assets: &MultiAssetFilter, - _max_assets: &u32, _dest: &MultiLocation, _xcm: &Xcm<()>, ) -> XCMWeight { assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets) -> XCMWeight { + fn exchange_asset( + _give: &MultiAssetFilter, + _receive: &MultiAssets, + _maximal: &bool, + ) -> XCMWeight { Weight::MAX.ref_time() } fn initiate_reserve_withdraw( @@ -150,13 +150,8 @@ impl XcmWeightInfo for WestmintXcmWeight { let weight = assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()); cmp::min(hardcoded_weight, weight) } - fn query_holding( - _query_id: &u64, - _dest: &MultiLocation, - _assets: &MultiAssetFilter, - _max_response_weight: &u64, - ) -> XCMWeight { - XcmGeneric::::query_holding().ref_time() + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> XCMWeight { + XcmGeneric::::report_holding().ref_time() } fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> XCMWeight { XcmGeneric::::buy_execution().ref_time() @@ -185,4 +180,65 @@ impl XcmWeightInfo for WestmintXcmWeight { fn unsubscribe_version() -> XCMWeight { XcmGeneric::::unsubscribe_version().ref_time() } + fn burn_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> XCMWeight { + XcmGeneric::::expect_origin().ref_time() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> XCMWeight { + XcmGeneric::::expect_error().ref_time() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::query_pallet().ref_time() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> XCMWeight { + XcmGeneric::::expect_pallet().ref_time() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::report_transact_status().ref_time() + } + fn clear_transact_status() -> XCMWeight { + XcmGeneric::::clear_transact_status().ref_time() + } + fn universal_origin(_: &Junction) -> XCMWeight { + Weight::MAX.ref_time() + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> XCMWeight { + Weight::MAX.ref_time() + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn set_fees_mode(_: &bool) -> XCMWeight { + XcmGeneric::::set_fees_mode().ref_time() + } + fn set_topic(_topic: &[u8; 32]) -> XCMWeight { + XcmGeneric::::set_topic().ref_time() + } + fn clear_topic() -> XCMWeight { + XcmGeneric::::clear_topic().ref_time() + } + fn alias_origin(_: &MultiLocation) -> XCMWeight { + // XCM Executor does not currently support alias origin operations + Weight::MAX.ref_time() + } } diff --git a/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 80aff062d20..042e0b3a974 100644 --- a/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -18,7 +18,8 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-08-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-09-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westmint-dev"), DB CACHE: 1024 // Executed Command: @@ -36,7 +37,7 @@ // --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/cumulus/.git/.artifacts/bench.json // --header=./file_header.txt // --template=./templates/xcm-bench-template.hbs -// --output=./parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --output=./parachains/runtimes/assets/westmint/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,58 +55,59 @@ impl WeightInfo { // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - pub(crate) fn query_holding() -> Weight { - Weight::from_ref_time(676_316_000 as u64) + pub(crate) fn report_holding() -> Weight { + Weight::from_ref_time(1_324_853_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } pub(crate) fn buy_execution() -> Weight { - Weight::from_ref_time(7_030_000 as u64) + Weight::from_ref_time(8_533_000 as u64) } // Storage: PolkadotXcm Queries (r:1 w:0) pub(crate) fn query_response() -> Weight { - Weight::from_ref_time(12_574_000 as u64) + Weight::from_ref_time(19_435_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } pub(crate) fn transact() -> Weight { - Weight::from_ref_time(15_764_000 as u64) + Weight::from_ref_time(22_656_000 as u64) } pub(crate) fn refund_surplus() -> Weight { - Weight::from_ref_time(7_200_000 as u64) + Weight::from_ref_time(8_900_000 as u64) } pub(crate) fn set_error_handler() -> Weight { - Weight::from_ref_time(3_310_000 as u64) + Weight::from_ref_time(6_255_000 as u64) } pub(crate) fn set_appendix() -> Weight { - Weight::from_ref_time(3_260_000 as u64) + Weight::from_ref_time(6_268_000 as u64) } pub(crate) fn clear_error() -> Weight { - Weight::from_ref_time(3_277_000 as u64) + Weight::from_ref_time(6_304_000 as u64) } pub(crate) fn descend_origin() -> Weight { - Weight::from_ref_time(3_913_000 as u64) + Weight::from_ref_time(7_279_000 as u64) } pub(crate) fn clear_origin() -> Weight { - Weight::from_ref_time(3_354_000 as u64) + Weight::from_ref_time(6_297_000 as u64) } + // Storage: ParachainInfo ParachainId (r:1 w:0) // Storage: PolkadotXcm SupportedVersion (r:1 w:0) // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn report_error() -> Weight { - Weight::from_ref_time(13_028_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) + Weight::from_ref_time(23_025_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: PolkadotXcm AssetTraps (r:1 w:1) pub(crate) fn claim_asset() -> Weight { - Weight::from_ref_time(7_739_000 as u64) + Weight::from_ref_time(13_001_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } pub(crate) fn trap() -> Weight { - Weight::from_ref_time(3_351_000 as u64) + Weight::from_ref_time(6_266_000 as u64) } // Storage: PolkadotXcm VersionNotifyTargets (r:1 w:1) // Storage: PolkadotXcm SupportedVersion (r:1 w:0) @@ -114,13 +116,13 @@ impl WeightInfo { // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn subscribe_version() -> Weight { - Weight::from_ref_time(16_051_000 as u64) + Weight::from_ref_time(31_348_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } // Storage: PolkadotXcm VersionNotifyTargets (r:0 w:1) pub(crate) fn unsubscribe_version() -> Weight { - Weight::from_ref_time(5_477_000 as u64) + Weight::from_ref_time(9_534_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: ParachainInfo ParachainId (r:1 w:0) @@ -130,8 +132,57 @@ impl WeightInfo { // Storage: ParachainSystem HostConfiguration (r:1 w:0) // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) pub(crate) fn initiate_reserve_withdraw() -> Weight { - Weight::from_ref_time(874_435_000 as u64) + Weight::from_ref_time(1_558_814_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn burn_asset() -> Weight { + Weight::from_ref_time(496_802_000 as u64) + } + pub(crate) fn expect_asset() -> Weight { + Weight::from_ref_time(38_299_000 as u64) + } + pub(crate) fn expect_origin() -> Weight { + Weight::from_ref_time(6_354_000 as u64) + } + pub(crate) fn expect_error() -> Weight { + Weight::from_ref_time(6_234_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn query_pallet() -> Weight { + Weight::from_ref_time(25_150_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } + pub(crate) fn expect_pallet() -> Weight { + Weight::from_ref_time(7_969_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn report_transact_status() -> Weight { + Weight::from_ref_time(23_099_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn clear_transact_status() -> Weight { + Weight::from_ref_time(6_366_000 as u64) + } + pub(crate) fn set_topic() -> Weight { + Weight::from_ref_time(6_422_000 as u64) + } + pub(crate) fn clear_topic() -> Weight { + Weight::from_ref_time(6_405_000 as u64) + } + pub(crate) fn set_fees_mode() -> Weight { + Weight::from_ref_time(6_392_000 as u64) + } } diff --git a/parachains/runtimes/assets/westmint/src/xcm_config.rs b/parachains/runtimes/assets/westmint/src/xcm_config.rs index a9d80012619..4f55696c69e 100644 --- a/parachains/runtimes/assets/westmint/src/xcm_config.rs +++ b/parachains/runtimes/assets/westmint/src/xcm_config.rs @@ -14,8 +14,9 @@ // limitations under the License. use super::{ - AccountId, AssetId, Assets, Authorship, Balance, Balances, ParachainInfo, ParachainSystem, - PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, AssetId, Assets, Authorship, Balance, Balances, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, + XcmpQueue, }; use frame_support::{ match_types, parameter_types, @@ -198,7 +199,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = (); + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); diff --git a/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs b/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs index 1e7b17de09a..2cb82656c37 100644 --- a/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs +++ b/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs @@ -14,8 +14,8 @@ // limitations under the License. use super::{ - AccountId, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, }; use frame_support::{ match_types, parameter_types, @@ -31,7 +31,7 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, - FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, + FixedWeightBounds, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -151,7 +151,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = (); + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); diff --git a/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs b/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs index fc0b834c4a3..4f53eca6dbc 100644 --- a/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -14,8 +14,8 @@ // limitations under the License. use super::{ - AccountId, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, }; use frame_support::{ match_types, parameter_types, @@ -149,7 +149,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = super::AllPalletsWithSystem; + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = ConstU32<8>; type AssetLocker = (); type AssetExchanger = (); diff --git a/parachains/runtimes/starters/shell/src/xcm_config.rs b/parachains/runtimes/starters/shell/src/xcm_config.rs index 6706c6af6bb..acc02df42af 100644 --- a/parachains/runtimes/starters/shell/src/xcm_config.rs +++ b/parachains/runtimes/starters/shell/src/xcm_config.rs @@ -13,7 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{AccountId, ParachainInfo, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin}; +use super::{ + AccountId, AllPalletsWithSystem, ParachainInfo, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, +}; use frame_support::{match_types, parameter_types, traits::Nothing}; use xcm::latest::prelude::*; use xcm_builder::{ @@ -67,7 +70,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = (); // don't trap for now type AssetClaims = (); // don't claim for now type SubscriptionService = (); // don't handle subscriptions for now - type PalletInstancesInfo = (); + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index 62ae7fc3a95..e8f70c20e63 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -22,9 +22,9 @@ //! with statemine as the reserve. At present no derivative tokens are minted on receipt of a //! ReserveAssetTransferDeposited message but that will but the intension will be to support this soon. use super::{ - AccountId, AssetId as AssetIdPalletAssets, Assets, Balance, Balances, ParachainInfo, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, - XcmpQueue, + AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Balance, Balances, + ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + WeightToFee, XcmpQueue, }; use core::marker::PhantomData; use frame_support::{ @@ -337,7 +337,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = (); + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); diff --git a/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/parachains/runtimes/testing/rococo-parachain/src/lib.rs index ec0fd38a3ed..80d97def583 100644 --- a/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -417,7 +417,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = (); + type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); From 7290787bf307b9162d0698980cccd0f7191f6aae Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 25 Oct 2022 13:50:19 +0200 Subject: [PATCH 55/91] Avoid consuming XCM message for NotApplicable scenario (#1787) * Avoid consuming message for NotApplicable scenario * Avoid consuming message for NotApplicable scenario tests --- pallets/xcmp-queue/src/lib.rs | 5 +- pallets/xcmp-queue/src/tests.rs | 85 +++++++++++++++++++++++++ primitives/utility/src/lib.rs | 106 +++++++++++++++++++++++++++++++- 3 files changed, 192 insertions(+), 4 deletions(-) diff --git a/pallets/xcmp-queue/src/lib.rs b/pallets/xcmp-queue/src/lib.rs index 72f1c12011b..2fdedd3e306 100644 --- a/pallets/xcmp-queue/src/lib.rs +++ b/pallets/xcmp-queue/src/lib.rs @@ -1138,19 +1138,20 @@ impl SendXcm for Pallet { msg: &mut Option>, ) -> SendResult<(ParaId, VersionedXcm<()>)> { let d = dest.take().ok_or(SendError::MissingArgument)?; - let xcm = msg.take().ok_or(SendError::MissingArgument)?; match &d { // An HRMP message for a sibling parachain. MultiLocation { parents: 1, interior: X1(Parachain(id)) } => { + let xcm = msg.take().ok_or(SendError::MissingArgument)?; let id = ParaId::from(*id); let price = T::PriceForSiblingDelivery::price_for_sibling_delivery(id, &xcm); let versioned_xcm = T::VersionWrapper::wrap_version(&d, xcm) .map_err(|()| SendError::DestinationUnsupported)?; Ok(((id, versioned_xcm), price)) }, - // Anything else is unhandled. This includes a message this is meant for us. _ => { + // Anything else is unhandled. This includes a message that is not meant for us. + // We need to make sure that dest/msg is not consumed here. *dest = Some(d); Err(SendError::NotApplicable) }, diff --git a/pallets/xcmp-queue/src/tests.rs b/pallets/xcmp-queue/src/tests.rs index 1b6303ddaf1..595bd8ca33b 100644 --- a/pallets/xcmp-queue/src/tests.rs +++ b/pallets/xcmp-queue/src/tests.rs @@ -247,3 +247,88 @@ fn update_xcmp_max_individual_weight() { assert_eq!(data.xcmp_max_individual_weight, 30u64 * WEIGHT_PER_MILLIS); }); } + +/// Validates [`validate`] for required Some(destination) and Some(message) +struct OkFixedXcmHashWithAssertingRequiredInputsSender; +impl OkFixedXcmHashWithAssertingRequiredInputsSender { + const FIXED_XCM_HASH: [u8; 32] = [9; 32]; + + fn fixed_delivery_asset() -> MultiAssets { + MultiAssets::new() + } + + fn expected_delivery_result() -> Result<(XcmHash, MultiAssets), SendError> { + Ok((Self::FIXED_XCM_HASH, Self::fixed_delivery_asset())) + } +} +impl SendXcm for OkFixedXcmHashWithAssertingRequiredInputsSender { + type Ticket = (); + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + assert!(destination.is_some()); + assert!(message.is_some()); + Ok(((), OkFixedXcmHashWithAssertingRequiredInputsSender::fixed_delivery_asset())) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(Self::FIXED_XCM_HASH) + } +} + +#[test] +fn xcmp_queue_does_not_consume_dest_or_msg_on_not_applicable() { + // dummy message + let message = Xcm(vec![Trap(5)]); + + // XcmpQueue - check dest is really not applicable + let dest = (Parent, Parent, Parent); + let mut dest_wrapper = Some(dest.clone().into()); + let mut msg_wrapper = Some(message.clone()); + assert_eq!( + Err(SendError::NotApplicable), + ::validate(&mut dest_wrapper, &mut msg_wrapper) + ); + + // check wrapper were not consumed + assert_eq!(Some(dest.clone().into()), dest_wrapper.take()); + assert_eq!(Some(message.clone()), msg_wrapper.take()); + + // another try with router chain with asserting sender + assert_eq!( + OkFixedXcmHashWithAssertingRequiredInputsSender::expected_delivery_result(), + send_xcm::<(XcmpQueue, OkFixedXcmHashWithAssertingRequiredInputsSender)>( + dest.into(), + message + ) + ); +} + +#[test] +fn xcmp_queue_consumes_dest_and_msg_on_ok_validate() { + // dummy message + let message = Xcm(vec![Trap(5)]); + + // XcmpQueue - check dest/msg is valid + let dest = (Parent, X1(Parachain(5555))); + let mut dest_wrapper = Some(dest.clone().into()); + let mut msg_wrapper = Some(message.clone()); + assert!(::validate(&mut dest_wrapper, &mut msg_wrapper).is_ok()); + + // check wrapper were consumed + assert_eq!(None, dest_wrapper.take()); + assert_eq!(None, msg_wrapper.take()); + + new_test_ext().execute_with(|| { + // another try with router chain with asserting sender + assert_eq!( + Err(SendError::Transport("NoChannel")), + send_xcm::<(XcmpQueue, OkFixedXcmHashWithAssertingRequiredInputsSender)>( + dest.into(), + message + ) + ); + }); +} diff --git a/primitives/utility/src/lib.rs b/primitives/utility/src/lib.rs index 1e93aa7d792..5cededd7b46 100644 --- a/primitives/utility/src/lib.rs +++ b/primitives/utility/src/lib.rs @@ -75,10 +75,10 @@ where msg: &mut Option>, ) -> SendResult> { let d = dest.take().ok_or(SendError::MissingArgument)?; - let xcm = msg.take().ok_or(SendError::MissingArgument)?; if d.contains_parents_only(1) { // An upward message for the relay chain. + let xcm = msg.take().ok_or(SendError::MissingArgument)?; let price = P::price_for_parent_delivery(&xcm); let versioned_xcm = W::wrap_version(&d, xcm).map_err(|()| SendError::DestinationUnsupported)?; @@ -86,8 +86,9 @@ where Ok((data, price)) } else { + // Anything else is unhandled. This includes a message that is not meant for us. + // We need to make sure that dest/msg is not consumed here. *dest = Some(d); - // Anything else is unhandled. This includes a message this is meant for us. Err(SendError::NotApplicable) } } @@ -317,3 +318,104 @@ pub trait ChargeWeightInFungibles Result<>::Balance, XcmError>; } + +#[cfg(test)] +mod tests { + use super::*; + use cumulus_primitives_core::UpwardMessage; + + /// Validates [`validate`] for required Some(destination) and Some(message) + struct OkFixedXcmHashWithAssertingRequiredInputsSender; + impl OkFixedXcmHashWithAssertingRequiredInputsSender { + const FIXED_XCM_HASH: [u8; 32] = [9; 32]; + + fn fixed_delivery_asset() -> MultiAssets { + MultiAssets::new() + } + + fn expected_delivery_result() -> Result<(XcmHash, MultiAssets), SendError> { + Ok((Self::FIXED_XCM_HASH, Self::fixed_delivery_asset())) + } + } + impl SendXcm for OkFixedXcmHashWithAssertingRequiredInputsSender { + type Ticket = (); + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + assert!(destination.is_some()); + assert!(message.is_some()); + Ok(((), OkFixedXcmHashWithAssertingRequiredInputsSender::fixed_delivery_asset())) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(Self::FIXED_XCM_HASH) + } + } + + /// Impl [`UpwardMessageSender`] that return `Other` error + struct OtherErrorUpwardMessageSender; + impl UpwardMessageSender for OtherErrorUpwardMessageSender { + fn send_upward_message(_: UpwardMessage) -> Result { + Err(MessageSendError::Other) + } + } + + #[test] + fn parent_as_ump_does_not_consume_dest_or_msg_on_not_applicable() { + // dummy message + let message = Xcm(vec![Trap(5)]); + + // ParentAsUmp - check dest is really not applicable + let dest = (Parent, Parent, Parent); + let mut dest_wrapper = Some(dest.clone().into()); + let mut msg_wrapper = Some(message.clone()); + assert_eq!( + Err(SendError::NotApplicable), + as SendXcm>::validate(&mut dest_wrapper, &mut msg_wrapper) + ); + + // check wrapper were not consumed + assert_eq!(Some(dest.clone().into()), dest_wrapper.take()); + assert_eq!(Some(message.clone()), msg_wrapper.take()); + + // another try with router chain with asserting sender + assert_eq!( + OkFixedXcmHashWithAssertingRequiredInputsSender::expected_delivery_result(), + send_xcm::<(ParentAsUmp<(), (), ()>, OkFixedXcmHashWithAssertingRequiredInputsSender)>( + dest.into(), + message + ) + ); + } + + #[test] + fn parent_as_ump_consumes_dest_and_msg_on_ok_validate() { + // dummy message + let message = Xcm(vec![Trap(5)]); + + // ParentAsUmp - check dest/msg is valid + let dest = (Parent, Here); + let mut dest_wrapper = Some(dest.clone().into()); + let mut msg_wrapper = Some(message.clone()); + assert!( as SendXcm>::validate( + &mut dest_wrapper, + &mut msg_wrapper + ) + .is_ok()); + + // check wrapper were consumed + assert_eq!(None, dest_wrapper.take()); + assert_eq!(None, msg_wrapper.take()); + + // another try with router chain with asserting sender + assert_eq!( + Err(SendError::Transport("Other")), + send_xcm::<( + ParentAsUmp, + OkFixedXcmHashWithAssertingRequiredInputsSender + )>(dest.into(), message) + ); + } +} From 23f0bcdc7dc1848e37c0180770755adac6af8da2 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 8 Nov 2022 13:48:15 +0100 Subject: [PATCH 56/91] Fix for FungiblesAdapter - trait changes: Contains -> AssetChecking --- Cargo.lock | 1 + parachains/common/Cargo.toml | 1 + parachains/common/src/impls.rs | 14 ++++++++----- .../runtimes/testing/penpal/src/xcm_config.rs | 21 +++++++++++-------- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b263e588f9d..35b9b3e4225 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6525,6 +6525,7 @@ dependencies = [ "sp-std", "substrate-wasm-builder", "xcm", + "xcm-builder", "xcm-executor", ] diff --git a/parachains/common/Cargo.toml b/parachains/common/Cargo.toml index a01b925b1ee..540c4c21ca7 100644 --- a/parachains/common/Cargo.toml +++ b/parachains/common/Cargo.toml @@ -28,6 +28,7 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", d # Polkadot polkadot-primitives = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } # Cumulus diff --git a/parachains/common/src/impls.rs b/parachains/common/src/impls.rs index c1158263da2..284a431835a 100644 --- a/parachains/common/src/impls.rs +++ b/parachains/common/src/impls.rs @@ -18,12 +18,13 @@ use frame_support::traits::{ fungibles::{self, Balanced, CreditOf}, - Contains, ContainsPair, Currency, Get, Imbalance, OnUnbalanced, + ContainsPair, Currency, Get, Imbalance, OnUnbalanced, }; use pallet_asset_tx_payment::HandleCredit; use sp_runtime::traits::Zero; use sp_std::marker::PhantomData; use xcm::latest::{AssetId, Fungibility::Fungible, MultiAsset, MultiLocation}; +use xcm_builder::{AssetChecking, MintLocation}; /// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type. pub type NegativeImbalance = as Currency< @@ -87,13 +88,16 @@ where /// Allow checking in assets that have issuance > 0. pub struct NonZeroIssuance(PhantomData<(AccountId, Assets)>); -impl Contains<>::AssetId> - for NonZeroIssuance +impl AssetChecking for NonZeroIssuance where Assets: fungibles::Inspect, { - fn contains(id: &>::AssetId) -> bool { - !Assets::total_issuance(*id).is_zero() + fn asset_checking(id: &Assets::AssetId) -> Option { + if Assets::total_issuance(*id).is_zero() { + None + } else { + Some(MintLocation::Local) + } } } diff --git a/parachains/runtimes/testing/penpal/src/xcm_config.rs b/parachains/runtimes/testing/penpal/src/xcm_config.rs index e8f70c20e63..99cbaf0478f 100644 --- a/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -31,7 +31,7 @@ use frame_support::{ log, match_types, parameter_types, traits::{ fungibles::{self, Balanced, CreditOf}, - ConstU32, Contains, ContainsPair, Everything, Get, Nothing, + ConstU32, ContainsPair, Everything, Get, Nothing, }, }; use pallet_asset_tx_payment::HandleCredit; @@ -43,10 +43,10 @@ use xcm::latest::{prelude::*, Weight as XCMWeight}; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + AssetChecking, ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, + FungiblesAdapter, IsConcrete, MintLocation, NativeAsset, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::{ traits::{JustTry, ShouldExecute}, @@ -244,13 +244,16 @@ impl> ContainsPair for AssetsFr /// Allow checking in assets that have issuance > 0. pub struct NonZeroIssuance(PhantomData<(AccountId, Assets)>); -impl Contains<>::AssetId> - for NonZeroIssuance +impl AssetChecking for NonZeroIssuance where Assets: fungibles::Inspect, { - fn contains(id: &>::AssetId) -> bool { - !Assets::total_issuance(*id).is_zero() + fn asset_checking(id: &Assets::AssetId) -> Option { + if Assets::total_issuance(*id).is_zero() { + None + } else { + Some(MintLocation::Local) + } } } From a6dd610d7472ba2145c08fdaa9d325ee424a5fbb Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 8 Nov 2022 14:57:49 +0100 Subject: [PATCH 57/91] Fix for missing weight for `fn unpaid_execution()` --- parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs | 3 +++ .../statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs | 1 + parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs | 3 +++ .../statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs | 1 + parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs | 3 +++ .../westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs | 1 + 6 files changed, 12 insertions(+) diff --git a/parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs b/parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs index 0ca8a414e44..93e12e826fd 100644 --- a/parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs +++ b/parachains/runtimes/assets/statemine/src/weights/xcm/mod.rs @@ -241,4 +241,7 @@ impl XcmWeightInfo for StatemineXcmWeight { // XCM Executor does not currently support alias origin operations Weight::MAX.ref_time() } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> XCMWeight { + XcmGeneric::::unpaid_execution().ref_time() + } } diff --git a/parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 20fdd109ef7..76c40db7914 100644 --- a/parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/parachains/runtimes/assets/statemine/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -185,4 +185,5 @@ impl WeightInfo { pub(crate) fn set_fees_mode() -> Weight { Weight::from_ref_time(6_426_000 as u64) } + pub(crate) fn unpaid_execution() -> Weight { Weight::from_ref_time(3_111_000 as u64) } } diff --git a/parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs b/parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs index 512be255779..bd6c9bbc097 100644 --- a/parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs +++ b/parachains/runtimes/assets/statemint/src/weights/xcm/mod.rs @@ -241,4 +241,7 @@ impl XcmWeightInfo for StatemintXcmWeight { // XCM Executor does not currently support alias origin operations Weight::MAX.ref_time() } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> XCMWeight { + XcmGeneric::::unpaid_execution().ref_time() + } } diff --git a/parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 5e35b4084d0..79066a2b9e5 100644 --- a/parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -185,4 +185,5 @@ impl WeightInfo { pub(crate) fn set_fees_mode() -> Weight { Weight::from_ref_time(6_336_000 as u64) } + pub(crate) fn unpaid_execution() -> Weight { Weight::from_ref_time(3_111_000 as u64) } } diff --git a/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs b/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs index 56852eee6ab..8429b74f2ec 100644 --- a/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs +++ b/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs @@ -241,4 +241,7 @@ impl XcmWeightInfo for WestmintXcmWeight { // XCM Executor does not currently support alias origin operations Weight::MAX.ref_time() } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> XCMWeight { + XcmGeneric::::unpaid_execution().ref_time() + } } diff --git a/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 18761aa0bf2..e6f01884049 100644 --- a/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -185,4 +185,5 @@ impl WeightInfo { pub(crate) fn set_fees_mode() -> Weight { Weight::from_ref_time(6_392_000 as u64) } + pub(crate) fn unpaid_execution() -> Weight { Weight::from_ref_time(3_111_000 as u64) } } From d2827e417453aac16aa744862b4843a127c9260d Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 8 Nov 2022 15:18:49 +0100 Subject: [PATCH 58/91] Patched dependencies (substrate + polkadot:gav-xcm-v3) --- Cargo.lock | 565 +++++++++++++++++++++++++++++------------------------ Cargo.toml | 237 ++++++++++++++++++++++ 2 files changed, 552 insertions(+), 250 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35b9b3e4225..c908727eac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,7 +469,7 @@ dependencies = [ [[package]] name = "beefy-gadget" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "async-trait", @@ -506,7 +506,7 @@ dependencies = [ [[package]] name = "beefy-gadget-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "beefy-gadget", "beefy-primitives", @@ -526,7 +526,7 @@ dependencies = [ [[package]] name = "beefy-merkle-tree" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "beefy-primitives", "sp-api", @@ -536,7 +536,7 @@ dependencies = [ [[package]] name = "beefy-primitives" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "scale-info", @@ -2847,7 +2847,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", ] @@ -2870,7 +2870,7 @@ checksum = "85dcb89d2b10c5f6133de2efd8c11959ce9dbb46a2f7a4cab208c4eeda6ce1ab" [[package]] name = "frame-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -2893,7 +2893,7 @@ dependencies = [ [[package]] name = "frame-benchmarking-cli" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "Inflector", "array-bytes", @@ -2944,7 +2944,7 @@ dependencies = [ [[package]] name = "frame-election-provider-solution-type" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2955,7 +2955,7 @@ dependencies = [ [[package]] name = "frame-election-provider-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-election-provider-solution-type", "frame-support", @@ -2971,7 +2971,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -3000,7 +3000,7 @@ dependencies = [ [[package]] name = "frame-support" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "bitflags", "frame-metadata", @@ -3032,7 +3032,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "Inflector", "cfg-expr", @@ -3046,7 +3046,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate", @@ -3058,7 +3058,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "proc-macro2", "quote", @@ -3068,7 +3068,7 @@ dependencies = [ [[package]] name = "frame-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "log", @@ -3086,7 +3086,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -3101,7 +3101,7 @@ dependencies = [ [[package]] name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "sp-api", @@ -3110,7 +3110,7 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "parity-scale-codec", @@ -3966,7 +3966,7 @@ checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" [[package]] name = "kusama-runtime" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "beefy-primitives", "bitvec", @@ -3992,7 +3992,7 @@ dependencies = [ "pallet-conviction-voting", "pallet-democracy", "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking", + "pallet-election-provider-support-benchmarking 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=master)", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-gilt", @@ -4027,7 +4027,7 @@ dependencies = [ "pallet-vesting", "pallet-whitelist", "pallet-xcm", - "pallet-xcm-benchmarks", + "pallet-xcm-benchmarks 0.9.31 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", "parity-scale-codec", "polkadot-primitives", "polkadot-runtime-common", @@ -4064,7 +4064,7 @@ dependencies = [ [[package]] name = "kusama-runtime-constants" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "frame-support", "polkadot-primitives", @@ -5324,7 +5324,7 @@ dependencies = [ [[package]] name = "pallet-aura" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -5340,7 +5340,7 @@ dependencies = [ [[package]] name = "pallet-authority-discovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -5356,7 +5356,7 @@ dependencies = [ [[package]] name = "pallet-authorship" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -5371,7 +5371,7 @@ dependencies = [ [[package]] name = "pallet-babe" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5395,7 +5395,7 @@ dependencies = [ [[package]] name = "pallet-bags-list" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5415,7 +5415,7 @@ dependencies = [ [[package]] name = "pallet-balances" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5430,7 +5430,7 @@ dependencies = [ [[package]] name = "pallet-beefy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "beefy-primitives", "frame-support", @@ -5446,7 +5446,7 @@ dependencies = [ [[package]] name = "pallet-beefy-mmr" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "beefy-merkle-tree", @@ -5469,7 +5469,7 @@ dependencies = [ [[package]] name = "pallet-bounties" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5487,7 +5487,7 @@ dependencies = [ [[package]] name = "pallet-child-bounties" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5531,7 +5531,7 @@ dependencies = [ [[package]] name = "pallet-collective" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5548,7 +5548,7 @@ dependencies = [ [[package]] name = "pallet-contracts" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "bitflags", "frame-benchmarking", @@ -5577,7 +5577,7 @@ dependencies = [ [[package]] name = "pallet-contracts-primitives" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "bitflags", "parity-scale-codec", @@ -5589,7 +5589,7 @@ dependencies = [ [[package]] name = "pallet-contracts-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "proc-macro2", "quote", @@ -5599,7 +5599,7 @@ dependencies = [ [[package]] name = "pallet-conviction-voting" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "assert_matches", "frame-benchmarking", @@ -5616,7 +5616,7 @@ dependencies = [ [[package]] name = "pallet-democracy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5634,14 +5634,14 @@ dependencies = [ [[package]] name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-election-provider-support", "frame-support", "frame-system", "log", - "pallet-election-provider-support-benchmarking", + "pallet-election-provider-support-benchmarking 4.0.0-dev (git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges)", "parity-scale-codec", "rand 0.7.3", "scale-info", @@ -5668,10 +5668,23 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-election-provider-support-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-system", + "parity-scale-codec", + "sp-npos-elections", + "sp-runtime", +] + [[package]] name = "pallet-elections-phragmen" version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5689,7 +5702,7 @@ dependencies = [ [[package]] name = "pallet-fast-unstake" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -5707,7 +5720,7 @@ dependencies = [ [[package]] name = "pallet-gilt" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5722,7 +5735,7 @@ dependencies = [ [[package]] name = "pallet-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5745,7 +5758,7 @@ dependencies = [ [[package]] name = "pallet-identity" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "enumflags2", "frame-benchmarking", @@ -5761,7 +5774,7 @@ dependencies = [ [[package]] name = "pallet-im-online" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5781,7 +5794,7 @@ dependencies = [ [[package]] name = "pallet-indices" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5798,7 +5811,7 @@ dependencies = [ [[package]] name = "pallet-membership" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5815,7 +5828,7 @@ dependencies = [ [[package]] name = "pallet-mmr" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "ckb-merkle-mountain-range", "frame-benchmarking", @@ -5833,7 +5846,7 @@ dependencies = [ [[package]] name = "pallet-mmr-rpc" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -5848,7 +5861,7 @@ dependencies = [ [[package]] name = "pallet-multisig" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5864,7 +5877,7 @@ dependencies = [ [[package]] name = "pallet-nomination-pools" version = "1.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -5901,7 +5914,7 @@ dependencies = [ [[package]] name = "pallet-nomination-pools-runtime-api" version = "1.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "sp-api", @@ -5911,7 +5924,7 @@ dependencies = [ [[package]] name = "pallet-offences" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -5951,7 +5964,7 @@ dependencies = [ [[package]] name = "pallet-preimage" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5968,7 +5981,7 @@ dependencies = [ [[package]] name = "pallet-proxy" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5983,7 +5996,7 @@ dependencies = [ [[package]] name = "pallet-randomness-collective-flip" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -5997,7 +6010,7 @@ dependencies = [ [[package]] name = "pallet-ranked-collective" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6015,7 +6028,7 @@ dependencies = [ [[package]] name = "pallet-recovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6030,7 +6043,7 @@ dependencies = [ [[package]] name = "pallet-referenda" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "assert_matches", "frame-benchmarking", @@ -6048,7 +6061,7 @@ dependencies = [ [[package]] name = "pallet-scheduler" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6064,7 +6077,7 @@ dependencies = [ [[package]] name = "pallet-session" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -6101,7 +6114,7 @@ dependencies = [ [[package]] name = "pallet-society" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -6115,7 +6128,7 @@ dependencies = [ [[package]] name = "pallet-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -6138,7 +6151,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-curve" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -6149,7 +6162,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-fn" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "log", "sp-arithmetic", @@ -6158,7 +6171,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -6187,7 +6200,7 @@ dependencies = [ [[package]] name = "pallet-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6205,7 +6218,7 @@ dependencies = [ [[package]] name = "pallet-tips" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6224,7 +6237,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -6240,7 +6253,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -6255,7 +6268,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -6266,7 +6279,7 @@ dependencies = [ [[package]] name = "pallet-treasury" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6283,7 +6296,7 @@ dependencies = [ [[package]] name = "pallet-uniques" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6298,7 +6311,7 @@ dependencies = [ [[package]] name = "pallet-utility" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6314,7 +6327,7 @@ dependencies = [ [[package]] name = "pallet-vesting" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6329,7 +6342,7 @@ dependencies = [ [[package]] name = "pallet-whitelist" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -6344,7 +6357,7 @@ dependencies = [ [[package]] name = "pallet-xcm" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "frame-support", "frame-system", @@ -6353,12 +6366,32 @@ dependencies = [ "scale-info", "serde", "sp-core", + "sp-io", "sp-runtime", "sp-std", "xcm", "xcm-executor", ] +[[package]] +name = "pallet-xcm-benchmarks" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "pallet-xcm-benchmarks" version = "0.9.31" @@ -6909,7 +6942,7 @@ dependencies = [ [[package]] name = "polkadot-approval-distribution" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "polkadot-node-network-protocol", @@ -6924,7 +6957,7 @@ dependencies = [ [[package]] name = "polkadot-availability-bitfield-distribution" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "polkadot-node-network-protocol", @@ -6938,7 +6971,7 @@ dependencies = [ [[package]] name = "polkadot-availability-distribution" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "derive_more", "fatality", @@ -6961,7 +6994,7 @@ dependencies = [ [[package]] name = "polkadot-availability-recovery" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "fatality", "futures", @@ -6982,7 +7015,7 @@ dependencies = [ [[package]] name = "polkadot-cli" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "clap 4.0.18", "frame-benchmarking-cli", @@ -7008,7 +7041,7 @@ dependencies = [ [[package]] name = "polkadot-client" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "beefy-primitives", "frame-benchmarking", @@ -7049,7 +7082,7 @@ dependencies = [ [[package]] name = "polkadot-collator-protocol" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "always-assert", "bitvec", @@ -7071,7 +7104,7 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "parity-scale-codec", "parity-util-mem", @@ -7084,7 +7117,7 @@ dependencies = [ [[package]] name = "polkadot-dispute-distribution" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "derive_more", "fatality", @@ -7109,7 +7142,7 @@ dependencies = [ [[package]] name = "polkadot-erasure-coding" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "parity-scale-codec", "polkadot-node-primitives", @@ -7123,7 +7156,7 @@ dependencies = [ [[package]] name = "polkadot-gossip-support" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "futures-timer", @@ -7143,7 +7176,7 @@ dependencies = [ [[package]] name = "polkadot-network-bridge" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "always-assert", "async-trait", @@ -7167,7 +7200,7 @@ dependencies = [ [[package]] name = "polkadot-node-collation-generation" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "parity-scale-codec", @@ -7185,7 +7218,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-approval-voting" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bitvec", "derive_more", @@ -7214,7 +7247,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-av-store" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bitvec", "futures", @@ -7234,7 +7267,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-backing" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bitvec", "fatality", @@ -7253,7 +7286,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-bitfield-signing" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "polkadot-node-subsystem", @@ -7268,7 +7301,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-candidate-validation" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "async-trait", "futures", @@ -7286,7 +7319,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-api" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "polkadot-node-subsystem", @@ -7301,7 +7334,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-selection" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "futures-timer", @@ -7318,7 +7351,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-coordinator" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "fatality", "futures", @@ -7337,7 +7370,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-parachains-inherent" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "async-trait", "futures", @@ -7354,7 +7387,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-provisioner" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bitvec", "fatality", @@ -7372,7 +7405,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "always-assert", "assert_matches", @@ -7404,7 +7437,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf-checker" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "polkadot-node-primitives", @@ -7420,7 +7453,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-runtime-api" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "futures", "memory-lru", @@ -7436,7 +7469,7 @@ dependencies = [ [[package]] name = "polkadot-node-jaeger" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "async-std", "lazy_static", @@ -7454,7 +7487,7 @@ dependencies = [ [[package]] name = "polkadot-node-metrics" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bs58", "futures", @@ -7473,7 +7506,7 @@ dependencies = [ [[package]] name = "polkadot-node-network-protocol" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "async-trait", "derive_more", @@ -7496,7 +7529,7 @@ dependencies = [ [[package]] name = "polkadot-node-primitives" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bounded-vec", "futures", @@ -7518,7 +7551,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "polkadot-node-jaeger", "polkadot-node-subsystem-types", @@ -7546,7 +7579,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-types" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "async-trait", "derive_more", @@ -7569,7 +7602,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-util" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "async-trait", "derive_more", @@ -7602,7 +7635,7 @@ dependencies = [ [[package]] name = "polkadot-overseer" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "async-trait", "futures", @@ -7625,7 +7658,7 @@ dependencies = [ [[package]] name = "polkadot-parachain" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "derive_more", "frame-support", @@ -7723,7 +7756,7 @@ dependencies = [ [[package]] name = "polkadot-performance-test" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "env_logger", "kusama-runtime", @@ -7738,7 +7771,7 @@ dependencies = [ [[package]] name = "polkadot-primitives" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bitvec", "frame-system", @@ -7768,7 +7801,7 @@ dependencies = [ [[package]] name = "polkadot-rpc" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "beefy-gadget", "beefy-gadget-rpc", @@ -7800,7 +7833,7 @@ dependencies = [ [[package]] name = "polkadot-runtime" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "beefy-primitives", "bitvec", @@ -7824,7 +7857,7 @@ dependencies = [ "pallet-collective", "pallet-democracy", "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking", + "pallet-election-provider-support-benchmarking 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=master)", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-grandpa", @@ -7889,7 +7922,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "beefy-primitives", "bitvec", @@ -7936,7 +7969,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-constants" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "frame-support", "polkadot-primitives", @@ -7948,7 +7981,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-metrics" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bs58", "parity-scale-codec", @@ -7960,7 +7993,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-parachains" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "bitflags", "bitvec", @@ -8003,7 +8036,7 @@ dependencies = [ [[package]] name = "polkadot-service" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "async-trait", "beefy-gadget", @@ -8108,7 +8141,7 @@ dependencies = [ [[package]] name = "polkadot-statement-distribution" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "arrayvec 0.5.2", "fatality", @@ -8129,7 +8162,7 @@ dependencies = [ [[package]] name = "polkadot-statement-table" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "parity-scale-codec", "polkadot-primitives", @@ -8164,7 +8197,7 @@ dependencies = [ [[package]] name = "polkadot-test-runtime" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "beefy-primitives", "bitvec", @@ -8216,7 +8249,7 @@ dependencies = [ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", - "test-runtime-constants", + "test-runtime-constants 0.9.31 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", "xcm", "xcm-builder", "xcm-executor", @@ -8271,7 +8304,7 @@ dependencies = [ "sp-state-machine", "substrate-test-client", "tempfile", - "test-runtime-constants", + "test-runtime-constants 0.9.31 (git+https://github.com/paritytech/polkadot?branch=master)", "tokio", "tracing-gum", ] @@ -8799,7 +8832,7 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remote-externalities" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "env_logger", "log", @@ -8918,7 +8951,7 @@ dependencies = [ [[package]] name = "rococo-runtime" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "beefy-merkle-tree", "beefy-primitives", @@ -8966,7 +8999,7 @@ dependencies = [ "pallet-utility", "pallet-vesting", "pallet-xcm", - "pallet-xcm-benchmarks", + "pallet-xcm-benchmarks 0.9.31 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", "parity-scale-codec", "polkadot-parachain 0.9.31", "polkadot-primitives", @@ -9002,7 +9035,7 @@ dependencies = [ [[package]] name = "rococo-runtime-constants" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "frame-support", "polkadot-primitives", @@ -9163,7 +9196,7 @@ dependencies = [ [[package]] name = "sc-allocator" version = "4.1.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "log", "sp-core", @@ -9174,7 +9207,7 @@ dependencies = [ [[package]] name = "sc-authority-discovery" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures", @@ -9201,7 +9234,7 @@ dependencies = [ [[package]] name = "sc-basic-authorship" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "futures-timer", @@ -9224,7 +9257,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "sc-client-api", @@ -9240,7 +9273,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "impl-trait-for-tuples", "memmap2", @@ -9257,7 +9290,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -9268,7 +9301,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "chrono", @@ -9308,7 +9341,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "fnv", "futures", @@ -9336,7 +9369,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "hash-db", "kvdb", @@ -9361,7 +9394,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures", @@ -9385,7 +9418,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures", @@ -9414,7 +9447,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "fork-tree", @@ -9456,7 +9489,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "jsonrpsee", @@ -9478,7 +9511,7 @@ dependencies = [ [[package]] name = "sc-consensus-epochs" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "fork-tree", "parity-scale-codec", @@ -9491,7 +9524,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures", @@ -9515,7 +9548,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "lazy_static", "lru 0.7.8", @@ -9542,7 +9575,7 @@ dependencies = [ [[package]] name = "sc-executor-common" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "environmental", "parity-scale-codec", @@ -9558,7 +9591,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmi" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "log", "parity-scale-codec", @@ -9573,7 +9606,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmtime" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "cfg-if 1.0.0", "libc", @@ -9593,7 +9626,7 @@ dependencies = [ [[package]] name = "sc-finality-grandpa" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "ahash", "array-bytes", @@ -9634,7 +9667,7 @@ dependencies = [ [[package]] name = "sc-finality-grandpa-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "finality-grandpa", "futures", @@ -9655,7 +9688,7 @@ dependencies = [ [[package]] name = "sc-informant" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "ansi_term", "futures", @@ -9672,7 +9705,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "async-trait", @@ -9687,7 +9720,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "async-trait", @@ -9734,7 +9767,7 @@ dependencies = [ [[package]] name = "sc-network-bitswap" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "cid", "futures", @@ -9754,7 +9787,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "bitflags", @@ -9780,7 +9813,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "ahash", "futures", @@ -9798,7 +9831,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "futures", @@ -9819,7 +9852,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "fork-tree", @@ -9849,7 +9882,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "futures", @@ -9868,7 +9901,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "bytes", @@ -9898,7 +9931,7 @@ dependencies = [ [[package]] name = "sc-peerset" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "libp2p", @@ -9911,7 +9944,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -9920,7 +9953,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "hash-db", @@ -9950,7 +9983,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "jsonrpsee", @@ -9973,7 +10006,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "jsonrpsee", @@ -9986,7 +10019,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "hex", @@ -10005,7 +10038,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "directories", @@ -10076,7 +10109,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "log", "parity-scale-codec", @@ -10090,7 +10123,7 @@ dependencies = [ [[package]] name = "sc-sync-state-rpc" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -10109,7 +10142,7 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "6.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "libc", @@ -10128,7 +10161,7 @@ dependencies = [ [[package]] name = "sc-telemetry" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "chrono", "futures", @@ -10146,7 +10179,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "ansi_term", "atty", @@ -10177,7 +10210,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -10188,7 +10221,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures", @@ -10215,7 +10248,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures", @@ -10229,7 +10262,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "futures-timer", @@ -10655,7 +10688,7 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "slot-range-helper" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "enumn", "parity-scale-codec", @@ -10731,7 +10764,7 @@ dependencies = [ [[package]] name = "sp-api" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "hash-db", "log", @@ -10749,7 +10782,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "blake2", "proc-macro-crate", @@ -10761,7 +10794,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "scale-info", @@ -10774,7 +10807,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "integer-sqrt", "num-traits", @@ -10789,7 +10822,7 @@ dependencies = [ [[package]] name = "sp-authority-discovery" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "scale-info", @@ -10802,7 +10835,7 @@ dependencies = [ [[package]] name = "sp-authorship" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "parity-scale-codec", @@ -10814,7 +10847,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "sp-api", @@ -10826,7 +10859,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures", "log", @@ -10844,7 +10877,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures", @@ -10863,7 +10896,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "parity-scale-codec", @@ -10881,7 +10914,7 @@ dependencies = [ [[package]] name = "sp-consensus-babe" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "merlin", @@ -10904,7 +10937,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "scale-info", @@ -10918,7 +10951,7 @@ dependencies = [ [[package]] name = "sp-consensus-vrf" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "scale-info", @@ -10931,7 +10964,7 @@ dependencies = [ [[package]] name = "sp-core" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "base58", @@ -10977,7 +11010,7 @@ dependencies = [ [[package]] name = "sp-core-hashing" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "blake2", "byteorder", @@ -10991,7 +11024,7 @@ dependencies = [ [[package]] name = "sp-core-hashing-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "proc-macro2", "quote", @@ -11002,7 +11035,7 @@ dependencies = [ [[package]] name = "sp-database" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "kvdb", "parking_lot 0.12.1", @@ -11011,7 +11044,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "proc-macro2", "quote", @@ -11021,7 +11054,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "environmental", "parity-scale-codec", @@ -11032,7 +11065,7 @@ dependencies = [ [[package]] name = "sp-finality-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "finality-grandpa", "log", @@ -11050,7 +11083,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -11064,7 +11097,7 @@ dependencies = [ [[package]] name = "sp-io" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "bytes", "futures", @@ -11090,7 +11123,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "lazy_static", "sp-core", @@ -11101,7 +11134,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures", @@ -11118,7 +11151,7 @@ dependencies = [ [[package]] name = "sp-maybe-compressed-blob" version = "4.1.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "thiserror", "zstd", @@ -11127,7 +11160,7 @@ dependencies = [ [[package]] name = "sp-mmr-primitives" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "log", "parity-scale-codec", @@ -11143,7 +11176,7 @@ dependencies = [ [[package]] name = "sp-npos-elections" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "scale-info", @@ -11157,7 +11190,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "sp-api", "sp-core", @@ -11167,7 +11200,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "backtrace", "lazy_static", @@ -11177,7 +11210,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "rustc-hash", "serde", @@ -11187,7 +11220,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "either", "hash256-std-hasher", @@ -11210,7 +11243,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -11228,7 +11261,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "Inflector", "proc-macro-crate", @@ -11240,7 +11273,7 @@ dependencies = [ [[package]] name = "sp-sandbox" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "log", "parity-scale-codec", @@ -11263,7 +11296,7 @@ dependencies = [ [[package]] name = "sp-session" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "scale-info", @@ -11277,7 +11310,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "scale-info", @@ -11288,7 +11321,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.12.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "hash-db", "log", @@ -11310,12 +11343,12 @@ dependencies = [ [[package]] name = "sp-std" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" [[package]] name = "sp-storage" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "impl-serde", "parity-scale-codec", @@ -11328,7 +11361,7 @@ dependencies = [ [[package]] name = "sp-tasks" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "log", "sp-core", @@ -11341,7 +11374,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "futures-timer", @@ -11357,7 +11390,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "sp-std", @@ -11369,7 +11402,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "sp-api", "sp-runtime", @@ -11378,7 +11411,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "log", @@ -11394,7 +11427,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "ahash", "hash-db", @@ -11417,7 +11450,7 @@ dependencies = [ [[package]] name = "sp-version" version = "5.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "impl-serde", "parity-scale-codec", @@ -11434,7 +11467,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -11445,7 +11478,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "6.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "impl-trait-for-tuples", "log", @@ -11458,7 +11491,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "4.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -11547,7 +11580,7 @@ dependencies = [ "pallet-uniques", "pallet-utility", "pallet-xcm", - "pallet-xcm-benchmarks", + "pallet-xcm-benchmarks 0.9.31 (git+https://github.com/paritytech/polkadot?branch=master)", "parachain-info", "parachains-common", "parity-scale-codec", @@ -11611,7 +11644,7 @@ dependencies = [ "pallet-uniques", "pallet-utility", "pallet-xcm", - "pallet-xcm-benchmarks", + "pallet-xcm-benchmarks 0.9.31 (git+https://github.com/paritytech/polkadot?branch=master)", "parachain-info", "parachains-common", "parity-scale-codec", @@ -11754,7 +11787,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "3.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "platforms", ] @@ -11762,7 +11795,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-system-rpc-runtime-api", "futures", @@ -11783,7 +11816,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "futures-util", "hyper", @@ -11796,7 +11829,7 @@ dependencies = [ [[package]] name = "substrate-rpc-client" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "async-trait", "jsonrpsee", @@ -11809,7 +11842,7 @@ dependencies = [ [[package]] name = "substrate-state-trie-migration-rpc" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "jsonrpsee", "log", @@ -11877,7 +11910,7 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "ansi_term", "build-helper", @@ -11982,6 +12015,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" +[[package]] +name = "test-runtime-constants" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "frame-support", + "polkadot-primitives", + "polkadot-runtime-common", + "smallvec", + "sp-runtime", +] + [[package]] name = "test-runtime-constants" version = "0.9.31" @@ -12252,7 +12297,7 @@ dependencies = [ [[package]] name = "tracing-gum" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "polkadot-node-jaeger", "polkadot-primitives", @@ -12263,7 +12308,7 @@ dependencies = [ [[package]] name = "tracing-gum-proc-macro" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "expander 0.0.6", "proc-macro-crate", @@ -12390,7 +12435,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "try-runtime-cli" version = "0.10.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "clap 4.0.18", "frame-try-runtime", @@ -13009,7 +13054,7 @@ dependencies = [ [[package]] name = "westend-runtime" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "beefy-primitives", "bitvec", @@ -13031,7 +13076,7 @@ dependencies = [ "pallet-collective", "pallet-democracy", "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking", + "pallet-election-provider-support-benchmarking 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=master)", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-grandpa", @@ -13062,7 +13107,7 @@ dependencies = [ "pallet-utility", "pallet-vesting", "pallet-xcm", - "pallet-xcm-benchmarks", + "pallet-xcm-benchmarks 0.9.31 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", "parity-scale-codec", "polkadot-parachain 0.9.31", "polkadot-primitives", @@ -13090,12 +13135,24 @@ dependencies = [ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", - "westend-runtime-constants", + "westend-runtime-constants 0.9.31 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", "xcm", "xcm-builder", "xcm-executor", ] +[[package]] +name = "westend-runtime-constants" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "frame-support", + "polkadot-primitives", + "polkadot-runtime-common", + "smallvec", + "sp-runtime", +] + [[package]] name = "westend-runtime-constants" version = "0.9.31" @@ -13146,7 +13203,7 @@ dependencies = [ "pallet-uniques", "pallet-utility", "pallet-xcm", - "pallet-xcm-benchmarks", + "pallet-xcm-benchmarks 0.9.31 (git+https://github.com/paritytech/polkadot?branch=master)", "parachain-info", "parachains-common", "parity-scale-codec", @@ -13167,7 +13224,7 @@ dependencies = [ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", - "westend-runtime-constants", + "westend-runtime-constants 0.9.31 (git+https://github.com/paritytech/polkadot?branch=master)", "xcm", "xcm-builder", "xcm-executor", @@ -13339,13 +13396,15 @@ dependencies = [ [[package]] name = "xcm" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "derivative", "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", + "serde", + "sp-io", "sp-runtime", "xcm-procedural", ] @@ -13353,10 +13412,11 @@ dependencies = [ [[package]] name = "xcm-builder" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "pallet-transaction-payment", "parity-scale-codec", @@ -13373,7 +13433,7 @@ dependencies = [ [[package]] name = "xcm-executor" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "frame-benchmarking", "frame-support", @@ -13391,7 +13451,7 @@ dependencies = [ [[package]] name = "xcm-procedural" version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ "Inflector", "proc-macro2", @@ -13462,3 +13522,8 @@ dependencies = [ "cc", "libc", ] + +[[patch.unused]] +name = "node-inspect" +version = "0.9.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" diff --git a/Cargo.toml b/Cargo.toml index cacb7bea7f1..365ec722a6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,3 +53,240 @@ opt-level = 3 inherits = "release" lto = true codegen-units = 1 + +# we need to be able to work with XCMv3, but it is not yet in Polkadot master +# => manual patch is required. Because of https://github.com/rust-lang/cargo/issues/5478 +# we need to use double slash in the repo name. +# +# Once XCMv3 PR is merged, we may remove both Substrate and Polkadot patch section. + +[patch."https://github.com/paritytech/substrate"] +beefy-gadget = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +beefy-gadget-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +beefy-merkle-tree = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +beefy-primitives = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +fork-tree = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-benchmarking-cli = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-election-provider-solution-type = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-election-provider-support = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-executive = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-support = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-support-procedural = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-support-procedural-tools = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-support-procedural-tools-derive = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-system = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-system-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-try-runtime = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +node-inspect = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-aura = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-authority-discovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-authorship = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-babe = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-bags-list = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-balances = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-beefy = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-beefy-mmr = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-bounties = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-child-bounties = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-collective = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-contracts = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-contracts-primitives = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-conviction-voting = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-democracy = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-election-provider-multi-phase = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-elections-phragmen = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-fast-unstake = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-gilt = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-grandpa = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-identity = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-im-online = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-indices = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-membership = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-mmr = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-mmr-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-multisig = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-nomination-pools = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-nomination-pools-runtime-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-offences = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-preimage = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-proxy = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-randomness-collective-flip = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-ranked-collective = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-recovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-referenda = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-scheduler = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-session = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-society = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-staking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-staking-reward-curve = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-staking-reward-fn = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-sudo = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-timestamp = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-tips = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-transaction-payment = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-transaction-payment-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-treasury = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-utility = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-uniques = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-vesting = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-whitelist = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +remote-externalities = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-allocator = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-authority-discovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-basic-authorship = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-block-builder = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-chain-spec = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-chain-spec-derive = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-cli = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-client-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-client-db = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-aura = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-babe = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-babe-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-epochs = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-slots = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-executor = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-executor-common = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-executor-wasmi = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-executor-wasmtime = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-finality-grandpa = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-finality-grandpa-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-informant = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-keystore = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network-common = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network-gossip = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network-light = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network-sync = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-offchain = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-peerset = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-proposer-metrics = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-rpc-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-rpc-server = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-service = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-state-db = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-sync-state-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-sysinfo = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-telemetry = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-tracing = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-tracing-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-transaction-pool = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-transaction-pool-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-utils = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-api-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-application-crypto = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-arithmetic = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-authority-discovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-authorship = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-block-builder = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-blockchain = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus-aura = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus-babe = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus-slots = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus-vrf = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-core = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-core-hashing = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-core-hashing-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-database = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-debug-derive = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-externalities = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-finality-grandpa = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-inherents = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-io = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-keyring = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-keystore = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-maybe-compressed-blob = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-mmr-primitives = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-npos-elections = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-offchain = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-panic-handler = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-runtime = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-runtime-interface = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-runtime-interface-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-session = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-staking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-state-machine = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-std = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-storage = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-tasks = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-timestamp = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-tracing = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-transaction-pool = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-transaction-storage-proof = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-trie = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-version = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-version-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-wasm-interface = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-build-script-utils = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-frame-rpc-system = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-prometheus-endpoint = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-state-trie-migration-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-wasm-builder = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +try-runtime-cli = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } + +[patch."https://github.com/paritytech/polkadot"] +kusama-runtime = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +kusama-runtime-constants = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +pallet-xcm = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-approval-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-availability-bitfield-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-availability-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-availability-recovery = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-cli = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-client = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-collator-protocol = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-core-primitives = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-dispute-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-erasure-coding = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-gossip-support = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-network-bridge = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-collation-generation = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-approval-voting = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-av-store = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-backing = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-bitfield-signing = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-candidate-validation = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-chain-api = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-chain-selection = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-dispute-coordinator = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-parachains-inherent = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-provisioner = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-pvf = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-pvf-checker = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-runtime-api = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-jaeger = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-metrics = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-network-protocol = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-primitives = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-subsystem = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-subsystem-types = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-subsystem-util = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-overseer = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-parachain = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-performance-test = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-primitives = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-rpc = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime-common = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime-constants = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime-metrics = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime-parachains = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-service = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-statement-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-statement-table = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-test-runtime = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +slot-range-helper = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +tracing-gum = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +tracing-gum-proc-macro = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +xcm = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +xcm-builder = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +xcm-executor = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +xcm-procedural = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } From e48282b840a8c997bb354bf8fe1c192de0f5dbf9 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 28 Jul 2022 18:18:59 +0200 Subject: [PATCH 59/91] [BridgeHub] Setup Rococo backbone parachain --- Cargo.lock | 58 ++ Cargo.toml | 1 + README.md | 4 + parachains/runtimes/bridge-hubs/README.md | 65 ++ .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 139 ++++ .../bridge-hubs/bridge-hub-rococo/build.rs | 9 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 719 ++++++++++++++++++ .../src/weights/block_weights.rs | 46 ++ .../src/weights/extrinsic_weights.rs | 46 ++ .../bridge-hub-rococo/src/weights/mod.rs | 28 + .../src/weights/paritydb_weights.rs | 63 ++ .../src/weights/rocksdb_weights.rs | 63 ++ .../bridge-hub-rococo/src/xcm_config.rs | 221 ++++++ polkadot-parachain/Cargo.toml | 1 + .../src/chain_spec/bridge_hubs.rs | 182 +++++ polkadot-parachain/src/chain_spec/mod.rs | 1 + polkadot-parachain/src/command.rs | 42 + 17 files changed, 1688 insertions(+) create mode 100644 parachains/runtimes/bridge-hubs/README.md create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/build.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs create mode 100644 polkadot-parachain/src/chain_spec/bridge_hubs.rs diff --git a/Cargo.lock b/Cargo.lock index c908727eac7..74fe22a8dcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -713,6 +713,63 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bridge-hub-rococo-runtime" +version = "0.1.0" +dependencies = [ + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-timestamp", + "cumulus-primitives-utility", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal", + "log", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "pallet-session", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-xcm", + "parachain-info", + "parity-scale-codec", + "polkadot-parachain 0.9.27", + "polkadot-runtime-common", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", + "substrate-wasm-builder", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "bs58" version = "0.4.0" @@ -7678,6 +7735,7 @@ version = "0.9.300" dependencies = [ "assert_cmd", "async-trait", + "bridge-hub-rococo-runtime", "clap 4.0.18", "collectives-polkadot-runtime", "contracts-rococo-runtime", diff --git a/Cargo.toml b/Cargo.toml index 365ec722a6e..b2d34b8e3f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "parachains/runtimes/assets/statemint", "parachains/runtimes/assets/statemine", "parachains/runtimes/assets/westmint", + "parachains/runtimes/bridge-hubs/bridge-hub-rococo", "parachains/runtimes/collectives/collectives-polkadot", "parachains/runtimes/contracts/contracts-rococo", "parachains/runtimes/testing/penpal", diff --git a/README.md b/README.md index 3d6a47fbba6..7ac6ca2e518 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,10 @@ Refer to the [setup instructions below](#local-setup) to run a local network for See [the `contracts-rococo` readme](parachains/runtimes/contracts/contracts-rococo/README.md) for details. +## Bridge-hub 📝 + +See [the `bridge-hubs` readme](parachains/runtimes/bridge-hubs/README.md) for details. + ## Rococo 👑 [Rococo](https://polkadot.js.org/apps/?rpc=wss://rococo-rpc.polkadot.io) is becoming a [Community Parachain Testbed](https://polkadot.network/blog/rococo-revamp-becoming-a-community-parachain-testbed/) for parachain teams in the Polkadot ecosystem. It supports multiple parachains with the differentiation of long-term connections and recurring short-term connections, to see which parachains are currently connected and how long they will be connected for [see here](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frococo-rpc.polkadot.io#/parachains). diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md new file mode 100644 index 00000000000..9f89d42e2d2 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/README.md @@ -0,0 +1,65 @@ +# Bride-hubs Parachain + +Implementation of _BridgeHub_, a blockchain to support message passing between Substrate based chains like Polkadot and Kusama networks. + +_BridgeHub_ allows users to: + +- Passing arbitrary messages between different Substrate chains (Polkadot <-> Kusama). +-- Message passing is + +Every _BridgeHub_ is meant to be **_common good parachain_** with main responsibilities: +- sync finality proofs between relay chains +- sync finality proofs between BridgeHub parachains +- pass (XCM) messages between different BridgeHub parachains + +## How to test locally Rococo <-> Wococo + +### Deploy +``` +cd +cargo build --release --locked -p polkadot-parachain@0.9.220 + +mkdir -p ~/local_bridge_testing/bin + +rm ~/local_bridge_testing/bin/polkadot-parachain +cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-parachain +ls -lrt ~/local_bridge_testing/bin/polkadot-parachain +``` + +### Run relay chain (Rococo) +``` + + +~/local_bridge_testing/bin/polkadot build-spec --chain rococo-local --disable-default-bootnode --raw > ~/local_bridge_testing/rococo-local-cfde.json +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --alice --tmp +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --bob --tmp --port 30334 +``` + +### Run Rococo BridgeHub parachain +#### Generate spec + genesis + wasm (paraId=1013) +``` +~/local_bridge_testing/bin/polkadot-parachain build-spec --chain bridge-hub-rococo-local --raw --disable-default-bootnode > ~/local_bridge_testing/bridge-hub-rococo-local-raw.json +~/local_bridge_testing/bin/polkadot-parachain export-genesis-state --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis +~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis-wasm +``` + +#### Run collators +``` +~/local_bridge_testing/bin/polkadot-parachain --collator --alice --force-authoring --tmp --port 40335 --ws-port 9946 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 30335 +~/local_bridge_testing/bin/polkadot-parachain --collator --bob --force-authoring --tmp --port 40336 --ws-port 9947 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 30336 +``` + +#### Run parachain node +``` +~/local_bridge_testing/bin/polkadot-parachain --tmp --port 40337 --ws-port 9948 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 30337 +``` + +#### Activate parachain (paraId=1013) +``` +https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/explorer +``` + +#### After parachain activation, we should see new blocks in collator's node +``` +https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/parachains +``` diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml new file mode 100644 index 00000000000..2bf9f9867cf --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -0,0 +1,139 @@ +[package] +name = "bridge-hub-rococo-runtime" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +description = "Rococo's BridgeHub parachain runtime" + +[build-dependencies] +substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +hex-literal = { version = "0.3.4", optional = true } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.137", optional = true, features = ["derive"] } +smallvec = "1.8.1" + +# Substrate +frame-benchmarking = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "master" } +frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-system-benchmarking = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "master" } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-try-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "master" } +pallet-aura = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-authorship = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-consensus-aura = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-offchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-transaction-pool = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-version = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } + +# Polkadot +pallet-xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } + +# Cumulus +cumulus-pallet-aura-ext = { path = "../../../../pallets/aura-ext", default-features = false } +cumulus-pallet-dmp-queue = { path = "../../../../pallets/dmp-queue", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false } +cumulus-pallet-session-benchmarking = {path = "../../../../pallets/session-benchmarking", default-features = false, version = "3.0.0"} +cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } +cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } +cumulus-primitives-timestamp = { path = "../../../../primitives/timestamp", default-features = false } +cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } +pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } +parachain-info = { path = "../../../../parachains/pallets/parachain-info", default-features = false } + +[features] +default = [ + "std", +] +std = [ + "codec/std", + "log/std", + "scale-info/std", + "serde", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-dmp-queue/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-xcm/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-core/std", + "cumulus-primitives-timestamp/std", + "cumulus-primitives-utility/std", + "frame-executive/std", + "frame-support/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "pallet-aura/std", + "pallet-authorship/std", + "pallet-balances/std", + "pallet-collator-selection/std", + "pallet-session/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "pallet-xcm/std", + "parachain-info/std", + "polkadot-parachain/std", + "polkadot-runtime-common/std", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-inherents/std", + "sp-io/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-transaction-pool/std", + "sp-version/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] + +runtime-benchmarks = [ + "hex-literal", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-collator-selection/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-xcmp-queue/runtime-benchmarks", +] + +try-runtime = [ + "frame-executive/try-runtime", + "frame-try-runtime", +] diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/build.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/build.rs new file mode 100644 index 00000000000..9b53d2457df --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/build.rs @@ -0,0 +1,9 @@ +use substrate_wasm_builder::WasmBuilder; + +fn main() { + WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build() +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs new file mode 100644 index 00000000000..9076081c28c --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -0,0 +1,719 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +mod weights; +pub mod xcm_config; + +use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use smallvec::smallvec; +use sp_api::impl_runtime_apis; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify}, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, MultiSignature, +}; + +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +use frame_support::{ + construct_runtime, parameter_types, + traits::Everything, + weights::{ + constants::WEIGHT_PER_SECOND, ConstantMultiplier, DispatchClass, Weight, + WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, + }, + PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; +pub use sp_runtime::{MultiAddress, Perbill, Permill}; +use xcm_config::{XcmConfig, XcmOriginToTransactDispatchOrigin}; + +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; + +// Polkadot imports +use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; + +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; + +// XCM Imports +use xcm::latest::prelude::BodyId; +use xcm_executor::XcmExecutor; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// Balance of an account. +pub type Balance = u128; + +/// Index of a transaction in the chain. +pub type Index = u32; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +/// An index to a block. +pub type BlockNumber = u32; + +/// The address format for describing accounts. +pub type Address = MultiAddress; + +/// Block header type as expected by this runtime. +pub type Header = generic::Header; + +/// Block type as expected by this runtime. +pub type Block = generic::Block; + +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; + +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; + +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); + +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = generic::CheckedExtrinsic; + +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, +>; + +/// Handles converting a weight scalar to a fee value, based on the scale and granularity of the +/// node's balance type. +/// +/// This should typically create a mapping between the following ranges: +/// - `[0, MAXIMUM_BLOCK_WEIGHT]` +/// - `[Balance::min, Balance::max]` +/// +/// Yet, it can be used for any other sort of change to weight-fee. Some examples being: +/// - Setting it to `0` will essentially disable the weight fee. +/// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. +pub struct WeightToFee; +impl WeightToFeePolynomial for WeightToFee { + type Balance = Balance; + fn polynomial() -> WeightToFeeCoefficients { + // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1 MILLIUNIT: + // in our template, we map to 1/10 of that, or 1/10 MILLIUNIT + let p = MILLIUNIT / 10; + let q = 100 * Balance::from(ExtrinsicBaseWeight::get()); + smallvec![WeightToFeeCoefficient { + degree: 1, + negative: false, + coeff_frac: Perbill::from_rational(p % q, q), + coeff_integer: p / q, + }] + } +} + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + use super::*; + use sp_runtime::{generic, traits::BlakeTwo256}; + + pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + /// Opaque block header type. + pub type Header = generic::Header; + /// Opaque block type. + pub type Block = generic::Block; + /// Opaque block identifier type. + pub type BlockId = generic::BlockId; +} + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + } +} + +#[sp_version::runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("bridge-hub-rococo"), + impl_name: create_runtime_str!("bridge-hub-rococo"), + authoring_version: 1, + spec_version: 1, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 1, +}; + +/// This determines the average expected block time that we are targeting. +/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. +/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked +/// up by `pallet_aura` to implement `fn slot_duration()`. +/// +/// Change this to adjust the block time. +pub const MILLISECS_PER_BLOCK: u64 = 12000; + +// NOTE: Currently it is not possible to change the slot duration after the chain has started. +// Attempting to do so will brick block production. +pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + +// Time is measured by number of blocks. +pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; + +// Unit = the base number of indivisible units for balances +pub const UNIT: Balance = 1_000_000_000_000; +pub const MILLIUNIT: Balance = 1_000_000_000; +pub const MICROUNIT: Balance = 1_000_000; + +/// The existential deposit. Set to 1/10 of the Connected Relay Chain. +pub const EXISTENTIAL_DEPOSIT: Balance = MILLIUNIT; + +/// We assume that ~5% of the block weight is consumed by `on_initialize` handlers. This is +/// used to limit the maximal weight of a single extrinsic. +const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(5); + +/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used by +/// `Operational` extrinsics. +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +/// We allow for 0.5 of a second of compute with a 12 second average block time. +const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + + // This part is copied from Substrate's `bin/node/runtime/src/lib.rs`. + // The `RuntimeBlockLength` and `RuntimeBlockWeights` exist here because the + // `DeletionWeightLimit` and `DeletionQueueDepth` depend on those to parameterize + // the lazy contract deletion. + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub const SS58Prefix: u16 = 42; +} + +// Configure FRAME pallets to include in runtime. + +impl frame_system::Config for Runtime { + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type Call = Call; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The header type. + type Header = generic::Header; + /// The ubiquitous event type. + type Event = Event; + /// The ubiquitous origin type. + type Origin = Origin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// Runtime version. + type Version = Version; + /// Converts a module to an index of this module in the runtime. + type PalletInfo = PalletInfo; + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// The basic call filter to use in dispatchable. + type BaseCallFilter = Everything; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// Block & extrinsics weights: base values and limits. + type BlockWeights = RuntimeBlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = RuntimeBlockLength; + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + /// The action to take on a Runtime Upgrade + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const MinimumPeriod: u64 = SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const UncleGenerations: u32 = 0; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type UncleGenerations = UncleGenerations; + type FilterUncle = (); + type EventHandler = (CollatorSelection,); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = pallet_balances::weights::SubstrateWeight; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + /// Relay Chain `TransactionByteFee` / 10 + pub const TransactionByteFee: Balance = 10 * MICROUNIT; + pub const OperationalFeeMultiplier: u8 = 5; +} + +impl pallet_transaction_payment::Config for Runtime { + type Event = Event; + type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; + type OperationalFeeMultiplier = OperationalFeeMultiplier; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type Event = Event; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type OutboundXcmpMessageSource = XcmpQueue; + type DmpMessageHandler = DmpQueue; + type ReservedDmpWeight = ReservedDmpWeight; + type XcmpMessageHandler = XcmpQueue; + type ReservedXcmpWeight = ReservedXcmpWeight; + type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; +} + +impl parachain_info::Config for Runtime {} + +impl cumulus_pallet_aura_ext::Config for Runtime {} + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; + type ChannelInfo = ParachainSystem; + type VersionWrapper = (); + type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; + type WeightInfo = (); +} + +impl cumulus_pallet_dmp_queue::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; + type ExecuteOverweightOrigin = EnsureRoot; +} + +parameter_types! { + pub const Period: u32 = 6 * HOURS; + pub const Offset: u32 = 0; + pub const MaxAuthorities: u32 = 100_000; +} + +impl pallet_session::Config for Runtime { + type Event = Event; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = CollatorSelection; + // Essentially just Aura, but lets be pedantic. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = (); +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = MaxAuthorities; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const MaxCandidates: u32 = 1000; + pub const MinCandidates: u32 = 5; + pub const SessionLength: BlockNumber = 6 * HOURS; + pub const MaxInvulnerables: u32 = 100; + pub const ExecutiveBody: BodyId = BodyId::Executive; +} + +// We allow root only to execute privileged collator selection operations. +pub type CollatorSelectionUpdateOrigin = EnsureRoot; + +impl pallet_collator_selection::Config for Runtime { + type Event = Event; + type Currency = Balances; + type UpdateOrigin = CollatorSelectionUpdateOrigin; + type PotId = PotId; + type MaxCandidates = MaxCandidates; + type MinCandidates = MinCandidates; + type MaxInvulnerables = MaxInvulnerables; + // should be a multiple of session or things will get inconsistent + type KickThreshold = Period; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorRegistration = Session; + type WeightInfo = (); +} + +// Create the runtime by composing the FRAME pallets that were previously configured. +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = opaque::Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + // System support stuff. + System: frame_system::{Pallet, Call, Config, Storage, Event} = 0, + ParachainSystem: cumulus_pallet_parachain_system::{ + Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, + } = 1, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 2, + ParachainInfo: parachain_info::{Pallet, Storage, Config} = 3, + + // Monetary stuff. + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + + // Collator support. The order of these 4 are important and shall not change. + Authorship: pallet_authorship::{Pallet, Call, Storage} = 20, + CollatorSelection: pallet_collator_selection::{Pallet, Call, Storage, Event, Config} = 21, + Session: pallet_session::{Pallet, Call, Storage, Event, Config} = 22, + Aura: pallet_aura::{Pallet, Storage, Config} = 23, + AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config} = 24, + + // XCM helpers. + XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 30, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin, Config} = 31, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, + DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + } +); + +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + [frame_system, SystemBench::] + [pallet_balances, Balances] + [pallet_session, SessionBench::] + [pallet_timestamp, Timestamp] + [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_xcmp_queue, XcmpQueue] + ); +} + +impl_runtime_apis! { + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Index { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade() -> (Weight, Weight) { + log::info!("try-runtime::on_runtime_upgrade parachain-template."); + let weight = Executive::try_runtime_upgrade().unwrap(); + (weight, RuntimeBlockWeights::get().max_block) + } + + fn execute_block_no_check(block: Block) -> Weight { + Executive::execute_block_no_check(block) + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + return (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, TrackedStorageKey}; + + use frame_system_benchmarking::Pallet as SystemBench; + impl frame_system_benchmarking::Config for Runtime {} + + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + impl cumulus_pallet_session_benchmarking::Config for Runtime {} + + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } + Ok(batches) + } + } +} + +struct CheckInherents; + +impl cumulus_pallet_parachain_system::CheckInherents for CheckInherents { + fn check_inherents( + block: &Block, + relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof, + ) -> sp_inherents::CheckInherentsResult { + let relay_chain_slot = relay_state_proof + .read_slot() + .expect("Could not read the relay chain slot from the proof"); + + let inherent_data = + cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration( + relay_chain_slot, + sp_std::time::Duration::from_secs(6), + ) + .create_inherent_data() + .expect("Could not create the timestamp inherent data"); + + inherent_data.check_extrinsics(block) + } +} + +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, + CheckInherents = CheckInherents, +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs new file mode 100644 index 00000000000..4db90f0c020 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Importing a block with 0 Extrinsics. + pub const BlockExecutionWeight: Weight = 5_000_000 * constants::WEIGHT_PER_NANOS; + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::BlockExecutionWeight::get(); + + // At least 100 µs. + assert!(w >= 100 * constants::WEIGHT_PER_MICROS, "Weight should be at least 100 µs."); + // At most 50 ms. + assert!(w <= 50 * constants::WEIGHT_PER_MILLIS, "Weight should be at most 50 ms."); + } + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs new file mode 100644 index 00000000000..158ba99c6a4 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Executing a NO-OP `System::remarks` Extrinsic. + pub const ExtrinsicBaseWeight: Weight = 125_000 * constants::WEIGHT_PER_NANOS; + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::ExtrinsicBaseWeight::get(); + + // At least 10 µs. + assert!(w >= 10 * constants::WEIGHT_PER_MICROS, "Weight should be at least 10 µs."); + // At most 1 ms. + assert!(w <= constants::WEIGHT_PER_MILLIS, "Weight should be at most 1 ms."); + } + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs new file mode 100644 index 00000000000..ed0b4dbcd47 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -0,0 +1,28 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Expose the auto generated weight files. + +pub mod block_weights; +pub mod extrinsic_weights; +pub mod paritydb_weights; +pub mod rocksdb_weights; + +pub use block_weights::constants::BlockExecutionWeight; +pub use extrinsic_weights::constants::ExtrinsicBaseWeight; +pub use paritydb_weights::constants::ParityDbWeight; +pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs new file mode 100644 index 00000000000..843823c1bf3 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights + /// are available for brave runtime engineers who may want to try this out as default. + pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 8_000 * constants::WEIGHT_PER_NANOS, + write: 50_000 * constants::WEIGHT_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::ParityDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1) >= constants::WEIGHT_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1) >= constants::WEIGHT_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1) <= constants::WEIGHT_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1) <= constants::WEIGHT_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs new file mode 100644 index 00000000000..05e06b0eabe --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout + /// the runtime. + pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 25_000 * constants::WEIGHT_PER_NANOS, + write: 100_000 * constants::WEIGHT_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::RocksDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1) >= constants::WEIGHT_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1) >= constants::WEIGHT_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1) <= constants::WEIGHT_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1) <= constants::WEIGHT_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs new file mode 100644 index 00000000000..2ec84d18871 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -0,0 +1,221 @@ +use super::{ + AccountId, Balances, Call, Event, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, + WeightToFee, XcmpQueue, +}; +use core::marker::PhantomData; +use frame_support::{ + log, match_types, parameter_types, + traits::{Everything, Nothing}, + weights::Weight, +}; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::Sibling; +use polkadot_runtime_common::impls::ToAuthor; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, + EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + UsingComponents, +}; +use xcm_executor::{traits::ShouldExecute, XcmExecutor}; + +parameter_types! { + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: NetworkId = NetworkId::Any; + pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); + pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); +} + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the parent `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, +); + +/// Means for transacting assets on this chain. +pub type LocalAssetTransactor = CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports. + (), +>; + +/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can +/// biases the kind of local `Origin` it will become. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when + // recognized. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognized. + SiblingParachainAsNative, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `Origin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. + XcmPassthrough, +); + +parameter_types! { + // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. + pub UnitWeightCost: Weight = 1_000_000_000; + pub const MaxInstructions: u32 = 100; +} + +match_types! { + pub type ParentOrParentsExecutivePlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Executive, .. }) } + }; +} + +//TODO: move DenyThenTry to polkadot's xcm module. +/// Deny executing the xcm message if it matches any of the Deny filter regardless of anything else. +/// If it passes the Deny, and matches one of the Allow cases then it is let through. +pub struct DenyThenTry(PhantomData, PhantomData) +where + Deny: ShouldExecute, + Allow: ShouldExecute; + +impl ShouldExecute for DenyThenTry +where + Deny: ShouldExecute, + Allow: ShouldExecute, +{ + fn should_execute( + origin: &MultiLocation, + message: &mut Xcm, + max_weight: Weight, + weight_credit: &mut Weight, + ) -> Result<(), ()> { + Deny::should_execute(origin, message, max_weight, weight_credit)?; + Allow::should_execute(origin, message, max_weight, weight_credit) + } +} + +// See issue #5233 +pub struct DenyReserveTransferToRelayChain; +impl ShouldExecute for DenyReserveTransferToRelayChain { + fn should_execute( + origin: &MultiLocation, + message: &mut Xcm, + _max_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + if message.0.iter().any(|inst| { + matches!( + inst, + InitiateReserveWithdraw { + reserve: MultiLocation { parents: 1, interior: Here }, + .. + } | DepositReserveAsset { dest: MultiLocation { parents: 1, interior: Here }, .. } | + TransferReserveAsset { + dest: MultiLocation { parents: 1, interior: Here }, + .. + } + ) + }) { + return Err(()) // Deny + } + + // An unexpected reserve transfer has arrived from the Relay Chain. Generally, `IsReserve` + // should not allow this, but we just log it here. + if matches!(origin, MultiLocation { parents: 1, interior: Here }) && + message.0.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) + { + log::warn!( + target: "xcm::barriers", + "Unexpected ReserveAssetDeposited from the Relay Chain", + ); + } + // Permit everything else + Ok(()) + } +} + +pub type Barrier = DenyThenTry< + DenyReserveTransferToRelayChain, + ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + AllowUnpaidExecutionFrom, + // ^^^ Parent and its exec plurality get free execution + ), +>; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type Call = Call; + type XcmSender = XcmRouter; + // How to withdraw and deposit an asset. + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + type IsReserve = NativeAsset; + type IsTeleporter = (); // Teleporting is disabled. + type LocationInverter = LocationInverter; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = + UsingComponents>; + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; +} + +/// No local origins on this chain are allowed to dispatch XCM sends/executions. +pub type LocalOriginToLocation = SignedToAccountId32; + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = ( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +); + +impl pallet_xcm::Config for Runtime { + type Event = Event; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Nothing; + // ^ Disable dispatchable execute on the XCM pallet. + // Needs to be `Everything` for local testing. + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Nothing; + type Weigher = FixedWeightBounds; + type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; + + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + // ^ Override for AdvertisedXcmVersion default + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; +} + +impl cumulus_pallet_xcm::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; +} diff --git a/polkadot-parachain/Cargo.toml b/polkadot-parachain/Cargo.toml index 2d1a51f0967..7e9d931b680 100644 --- a/polkadot-parachain/Cargo.toml +++ b/polkadot-parachain/Cargo.toml @@ -24,6 +24,7 @@ statemine-runtime = { path = "../parachains/runtimes/assets/statemine" } westmint-runtime = { path = "../parachains/runtimes/assets/westmint" } collectives-polkadot-runtime = { path = "../parachains/runtimes/collectives/collectives-polkadot" } contracts-rococo-runtime = { path = "../parachains/runtimes/contracts/contracts-rococo" } +bridge-hub-rococo-runtime = { path = "../parachains/runtimes/bridge-hubs/bridge-hub-rococo" } penpal-runtime = { path = "../parachains/runtimes/testing/penpal" } jsonrpsee = { version = "0.15.1", features = ["server"] } parachains-common = { path = "../parachains/common" } diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs new file mode 100644 index 00000000000..9091fb82c88 --- /dev/null +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -0,0 +1,182 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +use polkadot_service::ParaId; +use sc_chain_spec::ChainSpec; +use sc_cli::RuntimeVersion; +use std::{path::PathBuf, str::FromStr}; + +/// Collects all supported BridgeHub configurations +pub enum BridgeHubRuntimeType { + RococoLocal, +} + +impl FromStr for BridgeHubRuntimeType { + type Err = String; + + fn from_str(value: &str) -> Result { + match value { + rococo::BRIDGE_HUB_ROCOCO_LOCAL => Ok(BridgeHubRuntimeType::RococoLocal), + _ => Err(format!("Value '{}' is not configured yet", value)), + } + } +} + +impl BridgeHubRuntimeType { + pub const ID_PREFIX: &'static str = "bridge-hub"; + + pub fn chain_spec_from_json_file(&self, path: PathBuf) -> Result, String> { + Ok(Box::new(match self { + BridgeHubRuntimeType::RococoLocal => rococo::BridgeHubChainSpec::from_json_file(path)?, + })) + } + + pub fn load_config(&self) -> Box { + Box::new(match self { + BridgeHubRuntimeType::RococoLocal => + rococo::local_config("rococo-local", ParaId::new(1013)), + }) + } + + pub fn runtime_version(&self) -> &'static RuntimeVersion { + match self { + BridgeHubRuntimeType::RococoLocal => &bridge_hub_rococo_runtime::VERSION, + } + } +} + +/// Check if 'id' satisfy BridgeHub-like format +fn ensure_id(id: &str) -> Result<&str, String> { + if id.starts_with(BridgeHubRuntimeType::ID_PREFIX) { + Ok(id) + } else { + Err(format!( + "Invalid 'id' attribute ({}), should start with prefix: {}", + id, + BridgeHubRuntimeType::ID_PREFIX + )) + } +} + +pub mod rococo { + use crate::chain_spec::{ + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, + }; + use bridge_hub_rococo_runtime::{AccountId, AuraId}; + use cumulus_primitives_core::ParaId; + use sc_chain_spec::ChainType; + use sp_core::sr25519; + + pub const BRIDGE_HUB_ROCOCO_LOCAL: &str = "bridge-hub-rococo-local"; + + /// Specialized `ChainSpec` for the normal parachain runtime. + pub type BridgeHubChainSpec = + sc_service::GenericChainSpec; + + pub fn local_config(relay_chain: &str, para_id: ParaId) -> BridgeHubChainSpec { + let properties = sc_chain_spec::Properties::new(); + // TODO: check + // properties.insert("ss58Format".into(), 2.into()); + // properties.insert("tokenSymbol".into(), "ROC".into()); + // properties.insert("tokenDecimals".into(), 12.into()); + + BridgeHubChainSpec::from_genesis( + // Name + "Rococo BrideHub Local", + // ID + super::ensure_id(BRIDGE_HUB_ROCOCO_LOCAL).expect("invalid id"), + ChainType::Local, + move || { + genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + para_id, + ) + }, + Vec::new(), + None, + None, + None, + Some(properties), + Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, + ) + } + + fn genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + id: ParaId, + ) -> bridge_hub_rococo_runtime::GenesisConfig { + bridge_hub_rococo_runtime::GenesisConfig { + system: bridge_hub_rococo_runtime::SystemConfig { + code: bridge_hub_rococo_runtime::WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + }, + balances: bridge_hub_rococo_runtime::BalancesConfig { + balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), + }, + parachain_info: bridge_hub_rococo_runtime::ParachainInfoConfig { parachain_id: id }, + collator_selection: bridge_hub_rococo_runtime::CollatorSelectionConfig { + invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), + // TODO: check + candidacy_bond: 10, + ..Default::default() + }, + session: bridge_hub_rococo_runtime::SessionConfig { + keys: invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + bridge_hub_rococo_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + aura: Default::default(), + aura_ext: Default::default(), + parachain_system: Default::default(), + polkadot_xcm: bridge_hub_rococo_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + }, + } + } +} diff --git a/polkadot-parachain/src/chain_spec/mod.rs b/polkadot-parachain/src/chain_spec/mod.rs index 30cff43b57c..3637e915406 100644 --- a/polkadot-parachain/src/chain_spec/mod.rs +++ b/polkadot-parachain/src/chain_spec/mod.rs @@ -20,6 +20,7 @@ use serde::{Deserialize, Serialize}; use sp_core::{Pair, Public}; use sp_runtime::traits::{IdentifyAccount, Verify}; +pub mod bridge_hubs; pub mod collectives; pub mod contracts; pub mod penpal; diff --git a/polkadot-parachain/src/command.rs b/polkadot-parachain/src/command.rs index c436a98dc69..55c6400f55a 100644 --- a/polkadot-parachain/src/command.rs +++ b/polkadot-parachain/src/command.rs @@ -53,6 +53,7 @@ enum Runtime { ContractsRococo, CollectivesPolkadot, CollectivesWestend, + BridgeHub(chain_spec::bridge_hubs::BridgeHubRuntimeType), } trait RuntimeResolver { @@ -104,6 +105,11 @@ fn runtime(id: &str) -> Runtime { Runtime::CollectivesPolkadot } else if id.starts_with("collectives-westend") { Runtime::CollectivesWestend + } else if id.starts_with(chain_spec::bridge_hubs::BridgeHubRuntimeType::ID_PREFIX) { + Runtime::BridgeHub( + id.parse::() + .expect("Invalid value"), + ) } else { log::warn!("No specific runtime was recognized for ChainSpec's id: '{}', so Runtime::default() will be used", id); Runtime::default() @@ -188,6 +194,15 @@ fn load_spec(id: &str) -> std::result::Result, String> { &include_bytes!("../../parachains/chain-specs/contracts-rococo.json")[..], )?), + // -- BridgeHub + bridge_like_id + if bridge_like_id + .starts_with(chain_spec::bridge_hubs::BridgeHubRuntimeType::ID_PREFIX) => + bridge_like_id + .parse::() + .expect("invalid value") + .load_config(), + // -- Penpall "penpal-kusama" => Box::new(chain_spec::penpal::get_penpal_chain_spec( para_id.expect("Must specify parachain id"), @@ -223,6 +238,8 @@ fn load_spec(id: &str) -> std::result::Result, String> { Box::new(chain_spec::seedling::SeedlingChainSpec::from_json_file(path)?), Runtime::ContractsRococo => Box::new(chain_spec::contracts::ContractsRococoChainSpec::from_json_file(path)?), + Runtime::BridgeHub(bridge_hub_runtime_type) => + bridge_hub_runtime_type.chain_spec_from_json_file(path.into())?, Runtime::Penpal(_para_id) => Box::new(chain_spec::penpal::PenpalChainSpec::from_json_file(path)?), Runtime::Default => Box::new( @@ -300,6 +317,8 @@ impl SubstrateCli for Cli { Runtime::Shell => &shell_runtime::VERSION, Runtime::Seedling => &seedling_runtime::VERSION, Runtime::ContractsRococo => &contracts_rococo_runtime::VERSION, + Runtime::BridgeHub(bridge_hub_runtime_type) => + bridge_hub_runtime_type.runtime_version(), Runtime::Penpal(_) => &penpal_runtime::VERSION, Runtime::Default => &rococo_parachain_runtime::VERSION, } @@ -457,6 +476,19 @@ macro_rules! construct_async_run { { $( $code )* }.map(|v| (v, task_manager)) }) }, + Runtime::BridgeHub(bridge_hub_runtime_type) => { + runner.async_run(|$config| { + let $components = match bridge_hub_runtime_type { + chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => new_partial::( + &$config, + crate::service::aura_build_import_queue::<_, AuraId>, + )?, + }; + + let task_manager = $components.task_manager; + { $( $code )* }.map(|v| (v, task_manager)) + }) + }, Runtime::Penpal(_) | Runtime::Default => { runner.async_run(|$config| { let $components = new_partial::< @@ -740,6 +772,16 @@ pub fn run() -> Result<()> { .await .map(|r| r.0) .map_err(Into::into), + Runtime::BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { + chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => + crate::service::start_generic_aura_node::< + bridge_hub_rococo_runtime::RuntimeApi, + AuraId, + >(config, polkadot_config, collator_options, id, hwbench), + } + .await + .map(|r| r.0) + .map_err(Into::into), Runtime::Penpal(_) | Runtime::Default => crate::service::start_rococo_parachain_node( config, From 170c908a9f44529f7e806d5b65a7ce3290d04cce Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 2 Aug 2022 11:54:35 +0200 Subject: [PATCH 60/91] [BridgeHub] Setup wococo bridge grandpa/parachain pallets --- Cargo.lock | 283 ++++++++++++++++++ Cargo.toml | 9 + README.md | 4 + parachains/runtimes/bridge-hubs/README.md | 18 ++ .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 10 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 48 ++- .../bridge-hub-rococo/src/xcm_config.rs | 241 +++++++++------ 7 files changed, 525 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74fe22a8dcd..8edcc339e66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -713,10 +713,288 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bp-header-chain" +version = "0.1.0" +dependencies = [ + "assert_matches", + "bp-runtime 0.1.0", + "bp-test-utils 0.1.0", + "finality-grandpa", + "frame-support", + "hex", + "hex-literal", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-header-chain" +version = "0.1.0" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" +dependencies = [ + "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "finality-grandpa", + "frame-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-message-dispatch" +version = "0.1.0" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" +dependencies = [ + "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "frame-support", + "parity-scale-codec", + "scale-info", + "sp-std", +] + +[[package]] +name = "bp-messages" +version = "0.1.0" +dependencies = [ + "bitvec", + "bp-runtime 0.1.0", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-std", +] + +[[package]] +name = "bp-messages" +version = "0.1.0" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" +dependencies = [ + "bitvec", + "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-std", +] + +[[package]] +name = "bp-parachains" +version = "0.1.0" +dependencies = [ + "bp-polkadot-core 0.1.0", + "bp-runtime 0.1.0", + "frame-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", +] + +[[package]] +name = "bp-polkadot-core" +version = "0.1.0" +dependencies = [ + "bp-messages 0.1.0", + "bp-runtime 0.1.0", + "frame-support", + "frame-system", + "hex", + "parity-scale-codec", + "parity-util-mem", + "scale-info", + "serde", + "sp-api", + "sp-core", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-polkadot-core" +version = "0.1.0" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" +dependencies = [ + "bp-messages 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-relayers" +version = "0.1.0" +dependencies = [ + "frame-support", + "hex", + "hex-literal", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-rococo" +version = "0.1.0" +dependencies = [ + "bp-messages 0.1.0", + "bp-polkadot-core 0.1.0", + "bp-runtime 0.1.0", + "frame-support", + "parity-scale-codec", + "smallvec", + "sp-api", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-rococo" +version = "0.1.0" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" +dependencies = [ + "bp-messages 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-polkadot-core 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "frame-support", + "parity-scale-codec", + "smallvec", + "sp-api", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "bp-runtime" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "hash-db", + "hex-literal", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + +[[package]] +name = "bp-runtime" +version = "0.1.0" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" +dependencies = [ + "frame-support", + "hash-db", + "num-traits", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + +[[package]] +name = "bp-test-utils" +version = "0.1.0" +dependencies = [ + "bp-header-chain 0.1.0", + "ed25519-dalek", + "finality-grandpa", + "parity-scale-codec", + "sp-application-crypto", + "sp-finality-grandpa", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-test-utils" +version = "0.1.0" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" +dependencies = [ + "bp-header-chain 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "ed25519-dalek", + "finality-grandpa", + "parity-scale-codec", + "sp-application-crypto", + "sp-finality-grandpa", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-wococo" +version = "0.1.0" +dependencies = [ + "bp-messages 0.1.0", + "bp-polkadot-core 0.1.0", + "bp-rococo 0.1.0", + "bp-runtime 0.1.0", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-wococo" +version = "0.1.0" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" +dependencies = [ + "bp-messages 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-polkadot-core 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-rococo 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-std", +] + [[package]] name = "bridge-hub-rococo-runtime" version = "0.1.0" dependencies = [ + "bp-polkadot-core 0.1.0", + "bp-rococo 0.1.0", + "bp-wococo 0.1.0", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", @@ -9013,6 +9291,11 @@ source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm- dependencies = [ "beefy-merkle-tree", "beefy-primitives", + "bp-messages 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-rococo 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-wococo 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bridge-runtime-common", "frame-benchmarking", "frame-executive", "frame-support", diff --git a/Cargo.toml b/Cargo.toml index b2d34b8e3f3..4ba7acb605c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,14 @@ [workspace] members = [ + "bridges/modules/grandpa", + "bridges/modules/messages", + "bridges/modules/parachains", + "bridges/modules/relayers", + "bridges/modules/shift-session-manager", + "bridges/primitives/polkadot-core", + "bridges/primitives/runtime", + "bridges/primitives/chain-rococo", + "bridges/primitives/chain-wococo", "client/cli", "client/consensus/aura", "client/consensus/common", diff --git a/README.md b/README.md index 7ac6ca2e518..852b493ed61 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,10 @@ See [the `contracts-rococo` readme](parachains/runtimes/contracts/contracts-roco See [the `bridge-hubs` readme](parachains/runtimes/bridge-hubs/README.md) for details. +**_Important:_** + +BridgeHub stuff uses external dependencies from repo `https://github.com/paritytech/parity-bridges-common.git`, which are mirrored to `./bridges` directory with `git subtree` feature. + ## Rococo 👑 [Rococo](https://polkadot.js.org/apps/?rpc=wss://rococo-rpc.polkadot.io) is becoming a [Community Parachain Testbed](https://polkadot.network/blog/rococo-revamp-becoming-a-community-parachain-testbed/) for parachain teams in the Polkadot ecosystem. It supports multiple parachains with the differentiation of long-term connections and recurring short-term connections, to see which parachains are currently connected and how long they will be connected for [see here](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frococo-rpc.polkadot.io#/parachains). diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index 9f89d42e2d2..30832db75cb 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -38,6 +38,7 @@ ls -lrt ~/local_bridge_testing/bin/polkadot-parachain ### Run Rococo BridgeHub parachain #### Generate spec + genesis + wasm (paraId=1013) ``` +rm ~/local_bridge_testing/bridge-hub-rococo-local-raw.json ~/local_bridge_testing/bin/polkadot-parachain build-spec --chain bridge-hub-rococo-local --raw --disable-default-bootnode > ~/local_bridge_testing/bridge-hub-rococo-local-raw.json ~/local_bridge_testing/bin/polkadot-parachain export-genesis-state --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis ~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis-wasm @@ -63,3 +64,20 @@ https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/explorer ``` https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/parachains ``` + + +## Git subtree `./bridges` + +Add Bridges repo as a local remote and synchronize it with latest `master` from bridges repo: +``` +git remote add -f bridges git@github.com:paritytech/parity-bridges-common.git +# (ran just only first time, when subtree was initialized) +# git subtree add --prefix=bridges bridges master --squash +git subtree pull --prefix=bridges bridges master --squash +```` +We use `--squash` to avoid adding individual commits and rather squashing them +all into one. + +Now we use `master` branch, but in future, it could change to some release branch/tag. + +Original `./bridges/Cargo.toml` was renamed to `./bridges/Cargo.toml_removed_for_bridges_subtree_feature` to avoid confusion for `Cargo` having multiple workspaces. diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 2bf9f9867cf..4ebdf070607 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -66,6 +66,16 @@ cumulus-primitives-utility = { path = "../../../../primitives/utility", default- pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } parachain-info = { path = "../../../../parachains/pallets/parachain-info", default-features = false } +# Bridges +bp-polkadot-core = { path = "../../../../bridges/primitives/polkadot-core", default-features = false } +bp-rococo = { path = "../../../../bridges/primitives/chain-rococo", default-features = false } +bp-wococo = { path = "../../../../bridges/primitives/chain-wococo", default-features = false } +pallet-bridge-grandpa = { path = "../../../../bridges/modules/grandpa", default-features = false } +pallet-bridge-messages = { path = "../../../../bridges/modules/messages", default-features = false } +pallet-bridge-parachains = { path = "../../../../bridges/modules/parachains", default-features = false } +pallet-bridge-relayers = { path = "../../../../bridges/modules/relayers", default-features = false } +pallet-shift-session-manager = { path = "../../../../bridges/modules/shift-session-manager", default-features = false } + [features] default = [ "std", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 9076081c28c..7fe076ee297 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -50,6 +50,7 @@ use frame_support::{ }, PalletId, }; +use frame_support::traits::IsInVec; use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, @@ -58,6 +59,8 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; use xcm_config::{XcmConfig, XcmOriginToTransactDispatchOrigin}; +use bp_polkadot_core::parachains::ParaId; + #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -114,6 +117,8 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + // TODO: do we need? + // BridgeRejectObsoleteHeadersAndMessages, ); /// Unchecked extrinsic type as expected by this runtime. @@ -367,7 +372,8 @@ parameter_types! { } impl pallet_transaction_payment::Config for Runtime { - type Event = Event; + // TODO: hacked + // type Event = Event; type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; type WeightToFee = WeightToFee; type LengthToFee = ConstantMultiplier; @@ -405,6 +411,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = (); + type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { @@ -467,6 +474,37 @@ impl pallet_collator_selection::Config for Runtime { type WeightInfo = (); } +/// Add bridge pallets (GPA) +parameter_types! { + pub const MaxRequests: u32 = 50; + pub const HeadersToKeep: u32 = 1024; +} + +/// Add granda bridge pallet to track Wococo relay chain +pub type WococoGrandpaInstance = (); +impl pallet_bridge_grandpa::Config for Runtime { + type BridgedChain = bp_wococo::Wococo; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type WeightInfo = (); +} + +pub const PARAS_PALLET_NAME: &str = "WococoBridgeHubParachain"; +parameter_types! { + pub const ParachainHeadsToKeep: u32 = 50; + pub const ParasPalletName: &'static str = PARAS_PALLET_NAME; + pub GetTenFirstParachains: Vec = (0..10).map(ParaId).collect(); +} + +/// Add parachain bridge pallet to track Wococo bridge hub parachain +impl pallet_bridge_parachains::Config for Runtime { + type WeightInfo = (); + type BridgesGrandpaPalletInstance = WococoGrandpaInstance; + type ParasPalletName = ParasPalletName; + type TrackedParachains = IsInVec; + type HeadsToKeep = ParachainHeadsToKeep; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime where @@ -484,7 +522,9 @@ construct_runtime!( // Monetary stuff. Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + // TODO: hacked + // TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 11, // Collator support. The order of these 4 are important and shall not change. Authorship: pallet_authorship::{Pallet, Call, Storage} = 20, @@ -498,6 +538,10 @@ construct_runtime!( PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin, Config} = 31, CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + + // Bridge pallets + BridgeWococoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage}, + BridgeWococoParachains: pallet_bridge_parachains::{Pallet, Call, Storage}, } ); diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index 2ec84d18871..28a8600d2ea 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -1,5 +1,5 @@ use super::{ - AccountId, Balances, Call, Event, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, + AccountId, Balance, Balances, Call, Event, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, WeightToFee, XcmpQueue, }; use core::marker::PhantomData; @@ -8,13 +8,14 @@ use frame_support::{ traits::{Everything, Nothing}, weights::Weight, }; +use frame_support::weights::IdentityFee; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use polkadot_runtime_common::impls::ToAuthor; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentIsPreset, + EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -23,9 +24,11 @@ use xcm_executor::{traits::ShouldExecute, XcmExecutor}; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Any; + // TODO: hack: hardcoded Polkadot? + pub const RelayNetwork: NetworkId = NetworkId::Rococo; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); } /// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used @@ -79,6 +82,7 @@ parameter_types! { // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = 1_000_000_000; pub const MaxInstructions: u32 = 100; + pub MaxAssetsIntoHolding: u32 = 64; } match_types! { @@ -89,97 +93,140 @@ match_types! { } //TODO: move DenyThenTry to polkadot's xcm module. -/// Deny executing the xcm message if it matches any of the Deny filter regardless of anything else. -/// If it passes the Deny, and matches one of the Allow cases then it is let through. -pub struct DenyThenTry(PhantomData, PhantomData) -where - Deny: ShouldExecute, - Allow: ShouldExecute; - -impl ShouldExecute for DenyThenTry -where - Deny: ShouldExecute, - Allow: ShouldExecute, -{ - fn should_execute( - origin: &MultiLocation, - message: &mut Xcm, - max_weight: Weight, - weight_credit: &mut Weight, - ) -> Result<(), ()> { - Deny::should_execute(origin, message, max_weight, weight_credit)?; - Allow::should_execute(origin, message, max_weight, weight_credit) - } -} +// /// Deny executing the xcm message if it matches any of the Deny filter regardless of anything else. +// /// If it passes the Deny, and matches one of the Allow cases then it is let through. +// pub struct DenyThenTry(PhantomData, PhantomData) +// where +// Deny: ShouldExecute, +// Allow: ShouldExecute; +// +// impl ShouldExecute for DenyThenTry +// where +// Deny: ShouldExecute, +// Allow: ShouldExecute, +// { +// fn should_execute( +// origin: &MultiLocation, +// message: &mut Xcm, +// max_weight: Weight, +// weight_credit: &mut Weight, +// ) -> Result<(), ()> { +// Deny::should_execute(origin, message, max_weight, weight_credit)?; +// Allow::should_execute(origin, message, max_weight, weight_credit) +// } +// } +// TODO: hacked // See issue #5233 -pub struct DenyReserveTransferToRelayChain; -impl ShouldExecute for DenyReserveTransferToRelayChain { - fn should_execute( - origin: &MultiLocation, - message: &mut Xcm, - _max_weight: Weight, - _weight_credit: &mut Weight, - ) -> Result<(), ()> { - if message.0.iter().any(|inst| { - matches!( - inst, - InitiateReserveWithdraw { - reserve: MultiLocation { parents: 1, interior: Here }, - .. - } | DepositReserveAsset { dest: MultiLocation { parents: 1, interior: Here }, .. } | - TransferReserveAsset { - dest: MultiLocation { parents: 1, interior: Here }, - .. - } - ) - }) { - return Err(()) // Deny - } - - // An unexpected reserve transfer has arrived from the Relay Chain. Generally, `IsReserve` - // should not allow this, but we just log it here. - if matches!(origin, MultiLocation { parents: 1, interior: Here }) && - message.0.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) - { - log::warn!( - target: "xcm::barriers", - "Unexpected ReserveAssetDeposited from the Relay Chain", - ); - } - // Permit everything else - Ok(()) - } +// pub struct DenyReserveTransferToRelayChain; +// impl ShouldExecute for DenyReserveTransferToRelayChain { +// fn should_execute( +// origin: &MultiLocation, +// message: &mut Xcm, +// _max_weight: Weight, +// _weight_credit: &mut Weight, +// ) -> Result<(), ()> { +// if message.0.iter().any(|inst| { +// matches!( +// inst, +// InitiateReserveWithdraw { +// reserve: MultiLocation { parents: 1, interior: Here }, +// .. +// } | DepositReserveAsset { dest: MultiLocation { parents: 1, interior: Here }, .. } | +// TransferReserveAsset { +// dest: MultiLocation { parents: 1, interior: Here }, +// .. +// } +// ) +// }) { +// return Err(()) // Deny +// } +// +// // An unexpected reserve transfer has arrived from the Relay Chain. Generally, `IsReserve` +// // should not allow this, but we just log it here. +// if matches!(origin, MultiLocation { parents: 1, interior: Here }) && +// message.0.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) +// { +// log::warn!( +// target: "xcm::barriers", +// "Unexpected ReserveAssetDeposited from the Relay Chain", +// ); +// } +// // Permit everything else +// Ok(()) +// } +// } + +match_types! { + pub type ParentOrParentsUnitPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) } + }; } -pub type Barrier = DenyThenTry< - DenyReserveTransferToRelayChain, - ( - TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, - AllowUnpaidExecutionFrom, - // ^^^ Parent and its exec plurality get free execution - ), ->; +// TOOD: hacked +// pub type Barrier = DenyThenTry< +// DenyReserveTransferToRelayChain, +// ( +// TakeWeightCredit, +// AllowTopLevelPaidExecutionFrom, +// AllowUnpaidExecutionFrom, +// // ^^^ Parent and its exec plurality get free execution +// ), +// >; +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + AllowUnpaidExecutionFrom, + // ^^^ Parent & its unit plurality gets free execution +); +/// XCM weigher type. +pub type XcmWeigher = FixedWeightBounds; + +// TODO: hacked +// pub struct XcmConfig; +// impl xcm_executor::Config for XcmConfig { +// type Call = Call; +// type XcmSender = XcmRouter; +// // How to withdraw and deposit an asset. +// type AssetTransactor = LocalAssetTransactor; +// type OriginConverter = XcmOriginToTransactDispatchOrigin; +// type IsReserve = NativeAsset; +// type IsTeleporter = (); // Teleporting is disabled. +// type LocationInverter = LocationInverter; +// type Barrier = Barrier; +// type Weigher = FixedWeightBounds; +// type Trader = +// UsingComponents>; +// type ResponseHandler = PolkadotXcm; +// type AssetTrap = PolkadotXcm; +// type AssetClaims = PolkadotXcm; +// type SubscriptionService = PolkadotXcm; +// } pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; - // How to withdraw and deposit an asset. type AssetTransactor = LocalAssetTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; - type IsTeleporter = (); // Teleporting is disabled. - type LocationInverter = LocationInverter; + type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of UNIT + type UniversalLocation = UniversalLocation; type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = - UsingComponents>; + type Weigher = XcmWeigher; + type Trader = UsingComponents, RelayLocation, AccountId, Balances, ()>; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; } /// No local origins on this chain are allowed to dispatch XCM sends/executions. @@ -189,30 +236,52 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); +// TODO: hacked +// impl pallet_xcm::Config for Runtime { +// type Event = Event; +// type SendXcmOrigin = EnsureXcmOrigin; +// type XcmRouter = XcmRouter; +// type ExecuteXcmOrigin = EnsureXcmOrigin; +// type XcmExecuteFilter = Nothing; +// // ^ Disable dispatchable execute on the XCM pallet. +// // Needs to be `Everything` for local testing. +// type XcmExecutor = XcmExecutor; +// type XcmTeleportFilter = Everything; +// type XcmReserveTransferFilter = Nothing; +// type Weigher = FixedWeightBounds; +// type LocationInverter = LocationInverter; +// type Origin = Origin; +// type Call = Call; +// +// const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; +// // ^ Override for AdvertisedXcmVersion default +// type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; +// } impl pallet_xcm::Config for Runtime { type Event = Event; type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = XcmRouter; type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = Nothing; - // ^ Disable dispatchable execute on the XCM pallet. - // Needs to be `Everything` for local testing. + type XcmExecuteFilter = Everything; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Nothing; - type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; + type XcmReserveTransferFilter = Everything; + type Weigher = XcmWeigher; type Origin = Origin; type Call = Call; - const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - // ^ Override for AdvertisedXcmVersion default type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = (); + type MaxLockers = frame_support::traits::ConstU32<8>; + type UniversalLocation = UniversalLocation; } impl cumulus_pallet_xcm::Config for Runtime { From 2def98a1ef3330cebc8f0a759687550a188f7408 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 2 Aug 2022 15:53:09 +0200 Subject: [PATCH 61/91] [BridgeHub] Setup both rococo/wococo bridge grandpa/parachain pallets --- README.md | 2 + parachains/runtimes/bridge-hubs/README.md | 5 ++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 58 ++++++++++++++---- .../bridge-hub-rococo/src/xcm_config.rs | 8 +-- .../docs/bridge-hub-parachain-design.jpg | Bin 0 -> 88324 bytes 5 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 parachains/runtimes/bridge-hubs/docs/bridge-hub-parachain-design.jpg diff --git a/README.md b/README.md index 852b493ed61..6eb828f5b5f 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ See [the `bridge-hubs` readme](parachains/runtimes/bridge-hubs/README.md) for de BridgeHub stuff uses external dependencies from repo `https://github.com/paritytech/parity-bridges-common.git`, which are mirrored to `./bridges` directory with `git subtree` feature. +See [readme](parachains/runtimes/bridge-hubs/README.md#git-subtree-bridges) for details + ## Rococo 👑 [Rococo](https://polkadot.js.org/apps/?rpc=wss://rococo-rpc.polkadot.io) is becoming a [Community Parachain Testbed](https://polkadot.network/blog/rococo-revamp-becoming-a-community-parachain-testbed/) for parachain teams in the Polkadot ecosystem. It supports multiple parachains with the differentiation of long-term connections and recurring short-term connections, to see which parachains are currently connected and how long they will be connected for [see here](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frococo-rpc.polkadot.io#/parachains). diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index 30832db75cb..d6c5b20f500 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -12,6 +12,8 @@ Every _BridgeHub_ is meant to be **_common good parachain_** with main responsib - sync finality proofs between BridgeHub parachains - pass (XCM) messages between different BridgeHub parachains +![](/home/bparity/parity/cumulus/cumulus/parachains/runtimes/bridge-hubs/docs/bridge-hub-parachain-design.jpg "Basic deployment setup") + ## How to test locally Rococo <-> Wococo ### Deploy @@ -73,6 +75,9 @@ Add Bridges repo as a local remote and synchronize it with latest `master` from git remote add -f bridges git@github.com:paritytech/parity-bridges-common.git # (ran just only first time, when subtree was initialized) # git subtree add --prefix=bridges bridges master --squash + +# Synchro bridges repo +git fetch bridges --prune git subtree pull --prefix=bridges bridges master --squash ```` We use `--squash` to avoid adding individual commits and rather squashing them diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 7fe076ee297..7008fb385e2 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -474,37 +474,62 @@ impl pallet_collator_selection::Config for Runtime { type WeightInfo = (); } -/// Add bridge pallets (GPA) +// Add bridge pallets (GPA) parameter_types! { - pub const MaxRequests: u32 = 50; + pub const MaxRequests: u32 = 64; pub const HeadersToKeep: u32 = 1024; } /// Add granda bridge pallet to track Wococo relay chain -pub type WococoGrandpaInstance = (); -impl pallet_bridge_grandpa::Config for Runtime { +pub type BridgeGrandpaWococoInstance = pallet_bridge_grandpa::Instance1; +impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_wococo::Wococo; type MaxRequests = MaxRequests; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } -pub const PARAS_PALLET_NAME: &str = "WococoBridgeHubParachain"; +/// Add granda bridge pallet to track Wococo relay chain +pub type BridgeGrandpaRococoInstance = pallet_bridge_grandpa::Instance2; +impl pallet_bridge_grandpa::Config for Runtime { + type BridgedChain = bp_rococo::Rococo; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type WeightInfo = (); +} + +pub const ROCOCO_BRIDGE_PARA_PALLET_NAME: &str = "RococoBridgeHubParachainPallet"; +pub const WOCOCO_BRIDGE_PARA_PALLET_NAME: &str = "WococoBridgeHubParachainPallet"; parameter_types! { - pub const ParachainHeadsToKeep: u32 = 50; - pub const ParasPalletName: &'static str = PARAS_PALLET_NAME; + pub const ParachainHeadsToKeep: u32 = 64; + pub const RococoBridgeParachainPalletName: &'static str = ROCOCO_BRIDGE_PARA_PALLET_NAME; + pub const WococoBridgeParachainPalletName: &'static str = WOCOCO_BRIDGE_PARA_PALLET_NAME; pub GetTenFirstParachains: Vec = (0..10).map(ParaId).collect(); } /// Add parachain bridge pallet to track Wococo bridge hub parachain -impl pallet_bridge_parachains::Config for Runtime { +pub type BridgeParachainWococoInstance = pallet_bridge_parachains::Instance1; +impl pallet_bridge_parachains::Config for Runtime { + type WeightInfo = (); + type BridgesGrandpaPalletInstance = BridgeGrandpaWococoInstance; + type ParasPalletName = RococoBridgeParachainPalletName; + type TrackedParachains = IsInVec; + type HeadsToKeep = ParachainHeadsToKeep; +} + +/// Add parachain bridge pallet to track Rococo bridge hub parachain +pub type BridgeParachainRococoInstance = pallet_bridge_parachains::Instance2; +impl pallet_bridge_parachains::Config for Runtime { type WeightInfo = (); - type BridgesGrandpaPalletInstance = WococoGrandpaInstance; - type ParasPalletName = ParasPalletName; + type BridgesGrandpaPalletInstance = BridgeGrandpaRococoInstance; + type ParasPalletName = WococoBridgeParachainPalletName; type TrackedParachains = IsInVec; type HeadsToKeep = ParachainHeadsToKeep; } +/// Add shift session manager +impl pallet_shift_session_manager::Config for Runtime {} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime where @@ -539,9 +564,16 @@ construct_runtime!( CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, - // Bridge pallets - BridgeWococoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage}, - BridgeWococoParachains: pallet_bridge_parachains::{Pallet, Call, Storage}, + // Consensus support. + ShiftSessionManager: pallet_shift_session_manager::{Pallet}, + + // Wococo bridge modules + BridgeWococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage}, + BridgeWococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage}, + + // Rococo bridge modules + BridgeRococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage}, + BridgeRococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage}, } ); diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index 28a8600d2ea..216997bdf2e 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -1,17 +1,15 @@ use super::{ AccountId, Balance, Balances, Call, Event, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, - WeightToFee, XcmpQueue, + XcmpQueue, }; -use core::marker::PhantomData; use frame_support::{ - log, match_types, parameter_types, + match_types, parameter_types, traits::{Everything, Nothing}, weights::Weight, }; use frame_support::weights::IdentityFee; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; -use polkadot_runtime_common::impls::ToAuthor; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, @@ -20,7 +18,7 @@ use xcm_builder::{ SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; -use xcm_executor::{traits::ShouldExecute, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); diff --git a/parachains/runtimes/bridge-hubs/docs/bridge-hub-parachain-design.jpg b/parachains/runtimes/bridge-hubs/docs/bridge-hub-parachain-design.jpg new file mode 100644 index 0000000000000000000000000000000000000000..02feb11e10175b1357835e12b6d3e756032c9aa6 GIT binary patch literal 88324 zcmd431yr5A(kQ%fclSb(qQxohUfkW?y*LF5#ogWAt++#RcPs7`ibH|Bsl7+ObME=> z_usqz?6vkYnM{&-CdtfXlJz{zKYalpONfYz06;)M0J^{*;AsgU2!Mu!go1>ChJu2E zfq{mFM@N8%gM-IGMMFX-z$GFiz{SUZK}tvQf`sNJKK?5CfWW}Oz{9~~BOqWi65|sy{Vx041@##iUI%-{JwLsO4`UmJnD;}@)9_BQdF5p5d$dy3C9=4WC!un9TP=FEUcz^MX zmC74)q+rW)#Z7nSnkDjL%8$Q-A1X-L{XAK)X+FTr^j6EW*gfj=-0WJ72INU?bByV3 zwKI`xf}}SBiHgkFJS)X`y6ms^e>}QVAe~b7OMRX*xL#FOxQPzBq3tH?WySK>VVcR> zgV+V?Ec0i0oQrBXgh+`N7S42zs~L5{TA>a#3o`v>!eb>xbwsQewzDTzG`$4eNqMxe zO(=(i_;I2dxh5`+nOqQ@;|39}oQDo1X%MjNePkuz*Q=u8n*Dn*#Vm3Y?9I_I7$DAdC^RK^=y)0AUJlmb%@n7WoT@&0*q2aC6 z8Lce;LNTdpHL+18#j7B7ZtU^J!Ekry>;Y`+ys(CrYNV16A6na9%+BMko*v;mi~e@p z;>GVqfKQkUidbOV3mZNF5WlU_w`%r;-0{9XzbRZNvY2y!XRyOWw5opy?r+5POO(Gc z%xoR-IHx9~ermsKJwIi#xH17~HCs%8-f)w-;U|DhhGgbn1R%TP<7+8mzYySeXoG*1 z0RV*DUq%LwDuE;QC;bo_l%Gg=_;C8E+i3dVvJ51$7^%xNwNRVnF&Sxc`&mh5nszKO zc7OSpc@6eYGk|AS)y|AkN+0g|WMqNM_7a5qpMZ)g)<|HL3;|a@m}vuX$+-$sEWW?m z0778>RXMFch=7^N71UP;z)U%<&D(-E~_(Z;i~h!~I1y%0-i+?G=rs+Sq+_xVAywT&I%4W6Bo*_#Mht zRUq@BgO&ds+TNe`(e`9Ae`XlAa5<;Ue!!rfFx!79fN~rCc%I;>JLbpZxY4XlwNv|W z%(1u>7n@a-Ai{dZz8Wx$cX~bhm@tguyTuvrVDDUSWZ1d2G{5VHNAEE;q3t&$wp=rO zF_7#DbI1pPx7Y7%D3nb*TxoOhraN_h0_aT!KQ`jlJHGp3pk08(6uVb_{zf`pq^+^h zJNm*&9o4R1G9{qML=p^h@lIuXv_vOqGQ5A9h*dRjz^V8rpOD;E3wbZFFM=umvN`@& zje5WJ-LsZkVDbT~T;mPiu0giwrL<*MJL_#8Pmpjfld}`WJJd@m9?#z|ji6a`5cFfo zX;7Jv-l197T|1Q!a{ITqqeHuvz_>N|>%{jcWkK0TriB+K_|ETM-i4Cq$8CttXR0PP zfeS5VRwL|Rp#S2H%TLnTio}Thhe3J9fw0CG)pjQWx#8?W8cK$2S@pBsHMc=VhlIbo zneABR8^8Uh>HTNI?~ea4DY*Bzx8z>t9yyKxw7uW+2sl>jSpt7vgF$yW*)416hyV-| zXoJWOdUx_yfMbu!68@g!Bg;sbRhq%7B|C5en5Im%$uYZh0P7>Z<-{JhdGXBV&-A>Y zz`)a4s^&n)0aCetspUeLo8mFppDOxUuXrc({jQ@w_y$+4M2I)m!`$Lw={C(&cXiBu zyr6`Jnw?gKw#a=^H_cl_b*8>xAtNW`gY9kc3$v9e#*MmCk#%0nNv`eH3*a($`C$#7 z)yie;->Mbhl;tv23BikUG0SgD_iQ$Ax#I)A-ftcKn}_FT#&20n&tCxmkoBWgUs>@x zeo%lx$|TP6w!XVE%B3LT5SaOU;tQYjk^6ON+a8|ttr|9D~Fa3=C)+@W1 zSXf|3?5cRp)eL6gzd&<63RW&|G=uf^we@_~`oaEBMfVH*>jD07d%^ct_LtYAe=}LA zJboRot?^p0$YI2=qDmb%o?)l&@HV}E)TK}A@_b}r)O=*m@$DNr5+Utiqm*=)kfQK# zUq{!<9p#L*d>SQ+q$@!k108)sI-lp}A=^4aSWkkAA~H|)N_NNp>hULpM7T?M?eiBF z$`3OgPpxJeH?pm_49oLuN!f8vdv+?R&q>?REDcc{N9qY6M~7gQZdrF7mhXgUf-_?f zm+x4v;89oha-coIf}y1L*#v@AR8>?l{!;5dY~o*i{DdIunVemQA>!lL|Ex9u7|NCO z=&Vz!#S42ZPuXytF~xER?#>|#X91eWLZO06Vv|z*0_AXTBC&D}jcOE=OTo~RcMd#m zVN9%+uE5nL^9qb;&F!2M^ym2hbOrvQV=xgNA$AGZy&gBU*gr)qV@<0K0gxW1Y>^a( zE2sLNL`0(f6K5A5{{Td&2k`yq=lT7ZDu0Uj2jWjtzX9AOrm)g4zjGjgI?9F$XN21- z45DEXgxd&wze|A6AJbIwI$8!{jrD--+1veLHf_v^@SXq&m`?ydDL$F$hR`XSTjp4X zWOH1ozOO(1hK9CeSWtHdVxGW(4}Y~tVYtBfr+Hk8HLrRAa60$Zgl+x7{_Zs+1F|QQ z*-lx@Dvc|TG`U{)wjjF|Dn2S%+oS4`c`-);QA_H(`uTYYZnC0NYGM z8~IKz4-egm{cPz-LP~MlsqqNi4z!esjYGzs(VmIyxC96v*p^jA^|||9|&9`u=grY4+o|%3Wst887+ccR53><%$w$7Ug{?h@8E407US3*i4)JEk0%&uL}OvE+&od?%B|f z#nrbX?Sr5&o<%-i^OhxQ&gyMxD!?1sfR1q8CJNao^d?p?s?NvGM8-? zi~nVB(hFn40Rw+_UyspsBKssI^M~&R$jE?zo}8#JcVL--I-UR{5+QM`4p;H^UNIYK zuS}h53nr~$WAjpm`^1pjg2gR~#G`e{eE4==^)5|BU-KkZ@Y)D9?~3E=4in7cm2y<&^RZB`fw^I_g5fJSs-x10&f$DajFG)PE&$f1X0WA24b5&o4`Q;!zs695%!<+#Y{ zqSURLNVH(z_)AU=nuBMNEGu=mbXZlNa+6px0lCqRm*a*&xb9EA30$^6^u_MZ(ik!u znFH)F@-9-FlH7=IZdd-Hu$4C&Q8lkUax6F^WG=u$#440?Uw`#x1&!+f!J zC%y1KkH8vHqo>u_`t?fMkf3wxk44F2uaYM!el6)d`kLDtZmisGWGc-B@!MF|j3g9h zpMr)?aTNa5?C3)@D`Vd6ct^YVvx(m=c}H@xL>i1+s|a(CTbs*r1*nDM9`B3iEl)tK zse!WZY3W}|H(;aIaUM0U@0%Ffs;jLoSQykRqHD36sWYACntAAHab%?lnGt9K^#S@$ zALxN=54T+^5&%A8Q>|Z7gifvB&M7-F^qO4|@?9?%E{< z2#Jrh2biewJNL;F4+lxE7fro>Bit{r3eCag&{Nw#ASy)gf9H1u(hG>}1eQ5dK6sTW7?TGsMKmD!7 zd4|CV`3D@2NmBYNf0o?(u^DJp(c$-HYl+v(3x8JD9|VQ~>~dNI75`O4V$()}{L9>Y zH;qYY0>_${EHjnaaY`p2*bYLdy`lafL?(a%$BEu5Io8YPXCKf1ga%W)elI~SI02&_ zOqK9QV4P63pKq@^GAXu}J51hYyYUdIdYP!rdyzYX?&{(^>fL#mh~W2Ss)9uvK~a<~ zj=kb?|Eo11^zQo2K+`iu?>+&2R2lvNGr4JZIpxn*_qhlv!n8YPQg#{$x?Y58JLGkY zYQk@Vz3I$SjbZaLi*5UUUv$0?!uL!IKs|Uj@yZ9J*oyq_Uk6ntR{+wBr&d2IaOo{% zkk4kM^;qf+KksP&t0X;ES8hoz&*Sr>5TvY{#kt@F2aqP>$_E)=r5dWnzxs-$cCgGc zK$+`3mDyC*6su~0a($L8+I6KHldlubG#{`gl;3K7{Xm)EI8es^$&WRzGE#PxXD8#p zx$@5md{I!vLPPb}E*HEhT}FIb=y?meS??C~fyel&3QMOv4#jkLsTwhE&3@mBt6k27 zlQj)uB4e&2+#^lBOC9|GN)*LW}t zMrT2+xQ_|+wg1cnaQc}nszcJa;X%5jeLAbk6Bu6S1Cn)?@ySd!_w<&~7pUm(u!a;i z*-f#lv#L304C!FT7un)!Z+zyYP1l)r990gBw>zI#DG}^an;LI1Ke7Y>rY9>&jLNc2 z_4D|qmVb3h=!xLmWujGyU{^VZ_xy)*>Nf0p@!R?P!y42;ZC_hoKI+FG7%1YoF|L6F zDC&$H6T%kR2z6 zK-Pp9kH4ctV6r!lD)^_x{&zILGhFUyY}NaCWeBGLfGO(;%il%_q_cnQuDgq3yBmSK zuKy^3L zEe=LpKzhOCP$k;|V{b2=@BZBIIz|TP*Ioq)SNm@_VZY-(U@{JbNk9?{OP_zwgiIEX z5BR5h9sM~@o;aJ6^EF9TW+npYXRjAn(fs(?=vbwD?C}AkeNfNZl@O|)CKD>cxUCMQ zsG*=#>EAXFG|LAskQ+>aLUie&A$YM#c>xX~llA6D+~eiJqQCL-P}++_)xow+M|J;< zxW}H>wA6b3YmKgvx(X4J-GMqon&uS7uDTF+g@m#8Xw{TvcRMT-7lNpo37X;9??Okc zm!*~H|CWdTthaw3G&=t2K;t$GSRtuH-nmTrjb;970l^XiK@(3T?e%ZOzis(vAp#Ne z56{}dQg4p!SCsWEFYVn2r+{s-y#$bcCc0165M1_B8I{(KGq1~ho!AfNyU zG;~yClo#~Od>AAznPiDc85kj1bcM-S1$`ocX9m!~lLQb5&?mrsv-`B;4x&TC+wCcK z$5Ab&|1%(wEAK$U3%1>1S^Xm4L3z}Of*mV$#n_>BV04$sDs`UKZXi`{l+*+@shiNY zH*3`l zjJwInaSBB0da!-*YZ-TQ`P(jyH&HnCBMx@OyNpq>WN?JGt;WdR@N#uQHYENyTv%3m zi~d;_>sbLB&d{|i_FI|v;)m_vM0H27c7Ys8bavF1nR^_Mga~F3=m>&X{=tcL2lFuw5EuL zZyh)i6Ao3^%Foo^M-%N6;}?F@$UL>=hocS8Ng%1mV_GpWzS2RSxw zE3jBxygXuh`I=>#@*Pa(#g4pXB6$;xIPOxoXXiV!=p|8nWO(AJ-8AM*EVAy1u zpZy|C`CZ-;d8&X)fCVC$wOMQxep|YR34Dxt;g~SfMJ81BkuD4? z0XacO7|F?y;I2WC_uJ@feB&$-r{A35;e!VYj0WVqc5&Lagj_^pd1rdPWLP(|s8LV| zUAbMpSY3OD%?7pY_xc%- z8h0MuX0I|TaXX;HPQ6R!@!v!Zt~Nvfo&Y)yw$MpkI&WGRs(VZv?9!zAYzUcRJhuBB zAW~;eMNwLO(e=XZlSVbzGe>pL<5GQhHkr~+q#fDVE7eBHD2U0j^+~FZaqg6_T^fV| ztu|6wJLBi3uC-i?gjPtbfhmF`|2e{t1^Ljlm6O~(Q*QZn=iEbJc!ttg$FNXW4NHV$ ze?HQJUKF+J^mNiUz_{9}j+~+q7{4(WiGF^&Zcw8p(-7z;nWP9ez1Nst+uZughro|p z3$U9DPzfzutL4^x)_pcAY$Yi(BL_Q|4sMODnycl?D{|XR7ZoST>2Y9JENaOM-D$yV z)3^es?UCQ$xNHWkaQ$!vJ6p?i$#>$0t z9aze^D>`;hy~=`e>ZCBWTv~rg4E}i5hOSy&9=|RvtF5dQ@s_A&UZ(2_u=g(LQvI8y z#2imvjd9XRGce z8opyh%@bhkM$)_TwBdZNm#o6ujy8_K?5h=uc?lE7HJnFJTz>tc32V|wzJpon{~7Qh z-)2ds@ka9Q2>|U?!UIEb*ueVv50dInfHKOd8)p?iqyYS;4cYJXE!O#ZFXXX?@B;SJxZ{l59SDGN%0bJ;8Tuf{m#}P=S=bM*?3!i$v3(0ZEovd>T-jEV zrqr>bP?O%%x%G2f^-J6Vf*0O z4M_#nP{aLS{I2X%Tf?|bVheT8oT13o_&%enaaP#T+;uS;k!mT6oGR$P?mCrnN4WRY ztIh3+I`9uMTOo0h$K93cUM)dop_H~NL|_YOhK+<(Q%+)0vU12ziZ$d_@4V-c^tOQ& zLypG}#g@@I5kBM&Y72S@D?=}KN-ntpMfEKkw3p8^rmh{Ok^m8N63-ym2hYSpo$m5C z!S@3C8ZgKb;xrQSE4hFX?u1;_4|zh70ez!WZ(4&IH_Z+Gx*rs-(B|%=+{O&t6#bO1 zqVA!*l^(tiE3!AzynCYt%g5r5B8B+16pAaG+>pABvE_ z6q~#ahaJF~Be#?)xMvQhZXny5nLNJW=`&g|T{yUnjVIN>Q0z4be=|T!?`%3VjQo&_ zRD7nhTa%uGaeG=$hbGk1O&(?5lrkq~Spy513eMo9d3E*d2&RrE3Yt=Gq{*X@`rxOxRi}}$I%{}^bx*B#GGlb_YVl{C4Gfa!j*pMPb2adL>*)jlyw46FypJa@AJm)!5M`q9SjwIj9PITCcBw13kXe^3RQ80Tn;?R=h`!gz9zY1_y(Aw zf_lPpO4-7d1CEY-2<|>XM)FpWCHIBrd{+?p~k0Xc^dANq~%4G2uW~KWM zZnKN(U8EFtfs&@qVN_Ey#b$g2IYQwn?j-WLxN~)ZyMenVt0>8{OIWNz!Unb8WzA^! zJnKm(vU1BbNX4i9qnhp$suKgy#x)b#E&7onwHNP5FjK&$rBhcvOf9l?f98}D)mvfwe660oINxBXe+zm0iMSV`X z0&~$09wf&tN%&nIzZVnXaJz8$P;_?w?bkG`T@BaSRi}o31R(K{@0LdE&&yg4Z8-i8 za0>nsCMX&`aWnKSq8-UvF@TtoBhYrN*orW+n+1E1>gKg|vV1G^QPM`rZG^Rkec3#b z(wEjIN{I^vqKpO7(txzw%f~Juc8bW?YDWVSu@>61gz{_)*9anOvGbNrLCt6Cq_VqC zJ=!SqFD732WLkdWV!I9cvJUn}e-yhWfMz}9KL-5q%k<-pfWpP9^e+q`Uc5g2xt>gQ zvPUVdxVw2`bxa28N+iCb->~d)x`I8JtOd^8P+W3Qcbj8Ym`AlAKChQV@hB61CYx-;a_T4UsYd#uc5rK6luagVj1Ay zd^W^45eN$(8wh(xPxijGSG5C(R2DTH;hnbbbsms9(QW0_B^VhGo%wTZ@p) z3kYg^#Pb7^Jy4y%?DU`&WZ+&!ZqY@03Rfy#kf-Eg<`5up-;D_j<2HYoNN(Yf;=P=< zLNR^k-c&z=Hp_vjJ4-h3$ZOkqIiE{&E_3h#W=SszWuWdeojn}Tqg#lG%@ft3o82!w zoRaB)nyEC93$jW}7le2TzJr&xJfov;vH)~kybyzzd!1CR3|<*jP*o3v7G*W%$_FcX z9b&*K3pHcCn(?%akxG>Ek&CpIu;R3lYs0WulLZZ@6;dSeo#0=ACzY0pwQHfOZ{AVg zng-;AD6Crw$kuHtaLfhxU8zpZm{1e2Twy)f+#ugI2bR4rl%E_@i=l$fY{R-2EEW_t z$Zs10M*B~J!^m|AQ>-mQJngz3mq6$>P730IQ3M3DMCfKEv3!v+VompM?OTCs?s<*^9 z8v;+D%g629pMbtuhuyO>y5JUnl!$2Sq+*Czr0APE@tzu6;Raz-q#&ix{xS2|kURkQND8Kiimprm zyVcPr7=V3ydG;M>--jj);&G!1Bw-9m#uJON&`A78SdL)2P-Bu^>|_Zf>FGb1>& z*)N1Ty274P!_~`&joTVrPx^fS`1ywuMzj&QkC{DSNrB)g^mb9^X78@T{q&)26-BKR zWH6TG#fjZl5Z$v`Pz|W^blus#-eRGO!EAhIf8v_5P2T$HMTs!O>I}+{uWC?^kR;(|PfnQJqwdimXf@s06Y=6@$OoliQ^xoRq2X1&Rm+%pM#Bu&*Amti9aSXm zyf#unDvl;{fXG@zP<)Xv3m_VHC=9`OG3%ZO#I3E{v%(bEm(OEP`rUQmzMIy^Q+m!Q z(YS3o^R#1z>bT1V6fEal&gy1JB{88xD%S9B*yUs==5b`fSKw~8p>23la=ts|O#Ha= z24ceQQ7rR}#R3#+a<$Ib%3D)K9s2u%p}3pN{8iBL9S<3RRujFKg^uaP?#!N0n_?aP zjPn-uTp(Qq7^vHU`Q+r>3ovr>epMB`3@tDZ?Y1jT3b~FE9k10Efw7i}3!<>N=Ac>y z%^}>QvoIyTqCy7P_e4#U=D6_6t*1J39TLmrB^FC;-6`uMr#m+e9_(%SYlt?1{~l2z zImcuCbsekpm}uYI7$}K%JXsIxVY1Ww4cb_j5g0`p#vL`fYLzten3iy?5GkM|@Bcj_ zj(~a-*xDoZmFXQ)KfP2YxI6JR87-K!E~(W4u`l%)SSSk|lh zzUUuU_&=}tUzm>jt_P9U*-XyWR!I5XH%r7d3@)2JMp|af9@%Db@yx z53x9B(dxQK3nfXy$&=>3&Lqh+)x8&oZ?yNT+E@k+kD(N5d#WF>Jfj5P9_+0cEqp?; zO7ZcpGSC7O*)~1&Nw$~cKd3j5UF69#FVp8>S`iME((=g&eXbN0=gm^@uq zOBU&$9L5HM9sfF^fDhC*0Vz*_ic34yc&gFb#y-EeyyZMEtV%aU8@9cI?J0ayOGd#N zPe-;u7AtCLIe%xWrY!%@kpmh9CEnz5kRu_AbqWoGeC>NM%m;Z1h-Eexi*t9wHpe&K zq0|)B>juiN6xkQ*r*LZftQb<>8vh1#@n>*Q132YKOg=H&Zy1KY_WZfwB8&lJP3#Ho z4moDlu@$RmGY(unh+(5$lDDIs55@>{N%$ z-08+;tS%`z+$`F?J1D|p8jEFnJ&+@<2Njobs!jeWrNg~v*WlZTCnj?h^m_yr$Rqb0 z^?kvGI^q>YmJRbo9bTV^0V_&#k2hLd{JoGhtQguehxpvG$Y3`iK;)Ceu(v5uEIZAMN*rT@&fPwONO zC3Y{;fJ$9^!@A9JB|GCFBwzM`mV21W9764&2P@##hN=?MtiTB_tWZ^0cE%DGE4p|w z+DD5~F{sSxVsAvOw)MR#t-N|br`wR{$%v4>7FS*TX~(C9J;oC-ncUznjK@lx z7`?N}>3rh}bg}H}e%vY5#`)P5v~mkf62$y^9Z}KIapWo3*`9-TswB?Tb9v0$>#hR+ z(>N-Pgk`&h0YCE-)SG(X+|JCs6hb| zcK)6bw&b5r?|8^7-i0!azxBV~3XCy8^beC`_pN>rknB6xs0J-gmM6I#FvjCabshhnCC{lZgc0%ID!FwK!#-H1S!Ga?j^!#&C%7i=Pse{FFXxRMt81X z7@`1I=c~yNV`>ZBU0=n8blzSrCE!@^p*h)l3SPm@>YO--D#AO@cjs*?%Im{&!m=>$ z*azUyF~QexB-^o?Vo;8feQ=PEg99;PC)rxL4D{l2rYtORzuBrSa+jdOO^1A3`_SI7 zUH-<OOc?j5tO-9=QQ3CwOo_q`dyF@A7*D9;#N{G-S=#x$MsZWjpJKkB#or;*1Fa% zj!wRg2*yO+^^qDw>drUCRP$FKNv)&Jd&7svT|&^pH0H@0;#Q0$Pha>{pl81-EKee! zJ^-kPng@OuzKXIiPWYx#nciNvy$_j9uIu56p1KTG!HS zJC6n(n^tbR>A;!wFf-iGhZR=yD<+|~SBcmdc5%;=9Jaa&8@NM@`DB#!+34|w<5zdi z0cPUWj;*{FGa*XY+lEIpv^7H3pRxkUyvZz}$gRtr~slR$_uPXJwfTu2i`)2;xv zUKhg<9m3QL;OaC*lHH`=bQON*!=edKxq~!&g-Z$^6e5CJm(mf>>{}wsq!2_kz1dWz zXtYxnWm@ap@W6?$5er&;@3_y!6h!TG>?E!wpri}GU@Q?8O%uBR$^D>fmDU2iyhLr@ z7Im*`M$96O4aq6~MRIXjxswu=$Ycp4U6y*J3R z)DsL!I)U=~B-EhV*)L|WYu0OnXXIq-rn+Ur+(PT8x}Dx!tloH9q+;BxA(iDcH{nff z6baa|`h&sANzASx5H6sX4R$@Oe|yb&{7&QEiwQsVquW%TWG}@TAOAmSG zON!jt6WL6+>*=@0`-Qb*SL=dE2SSLK-ae-@%eY6H+3RM-EfyCh4@ z1^#Kp(@OSNpl-=+FP;GP`-#)}H5{?4sME0O2T*x5En>s&s+R#Jh0u9*#uRtkB@Fpi zOUZE4Zk|VV^uooEin+~A3Hz$_W2Il^lPMPLU+C73kri9Txjq4$Hi%zQ(Q3Sd=7`lL z%U)P(jv|=YVtb(MzZqKAnqszi(=5EL)PUQ?>sCR_TxFCm-CGA2L;{SXJ-Wk~_zJI} zu}=H77gP?X2v2>5qsuBS&-i;XYU5@l+iZBCt7A&`cI-pRh)UiI$J1e&2b>O$e;PVY;3ESHz=I-FDyluPiVU zlAV?Jod&Kv>or}vg7-KdN?o>mvc^>lSqZ3Xa)VZq^(NgrnPNlC}(iJ^6fPg#F9UX7nD;% zv@l(2pGDWi)vC976&1jg?5nGwdgCx*lr7a|5CEgAv$e_`hKc5%D>Q@;w;Hb=MjpKV zZh)aFIajocid{)eny`0y?YI((Ic?D&G>{8X+_gqa%Qy?Fc};CdyZxXk4l=9-WECN- z?Df8qXyce$jnJ_>oI$UHzT}Zu(O1nmd+X9mk?&DmCAXx%JGQ|~$S*s6K((MTdwIpg zfYQi1(pCYZraVdUit@%sH-}781_zu;<-Zjs`bj)Ug{x87y#nJSEef`UdQF~1CDejm z{z*m0;BzZScEZ6oF$vhT9RqM&$o81(a}Ah}tt|_Qp(~}k)Af8MXdCmSO51}D+SdlB zbG3-U%I56}ZZ6xeXEx&vqgk?F-yjwqJTloQwQwzumh8$gwIp3S9PBEcyRa3dbd3y3 zyYz=xVH`Zw`hAs_Jnv0b(N{?^y zhL7%DrAMy$V@=zP*6E+E*=@x-5ve71LcWg}MQ3KVjH`*M(`|}Ii7kbczg+4n)DGsIXawiEAuclW#x>ikWrTPoiz|wMG2gK z^>RL_%PC=xeV(tp`hiZW?5a_jx{7V7?t8vlG}T(G5r^`b!NvNBkt0pim(y*t-lo>m zkNqax@&~xYy-w%q2Mbnd;(8o}gpnPHd=c%NS^Hf6&UF3)eVKh`yez2HFX{O@wUNYk z+f2UO4bs%7AB!;@JON%cV_$UMzZoEv{Ai1O>teD~ymEC;!?Ea<$Cz8z&MGUr&}!>0 zsZ1-oC?MVQ*l&IyaR3^Rt*MeAkC;c`c=N?Jv0k8cEJFH)o~?pZerSvAyNqLPC2f{U zH#;yQm_)TYy-GZ(moJTJy17Gv!K_q@X`2q^Vn($RU-pFLA^(HGuj7K~D%*h;mcvGr za&u#K)E;}S4)zm~>M}d^)@_oV!2|Q}&kx5%VNvYITH%p>;Lv%Kk>Z=+QS_ z>>A3K-^7UHIU$9Yf~)ZuoFN%+NMg%?@0I-{Y{8f5L8dAaP3fQ#K5Pj1=3Zhd>(@L7 z+NI5%+oQp1mZG+-zH{u6l=yMw>)@q}7j10UihH7RDx4x2*jp&$#;GVrsI<Y>|tUpi`% z7k*gCCh~@CkMaqSm>43mfC)SXtVsW#L^EEZXmouOe*&aZok}VN<+a?)*Z=Rr7XJ{~ zpyE*Jk{@R|;lySbS9}7b1J%I=h9!=v`6JQI$bAZUtEkN#Nd;ydc4GUwSg)Bici0m^ z%1tr$gle8~y!M;r*x2msKc6W4!VE^;8q$L4l=s0aB6uG_w|nmOk8K*y4$9{gBxBF> zMt!a_w3v;50+f6@Q1?`-G|Vzvrzqd0jH|QJ%<57*+NA{E(a{9P?0b51>L1&Cl&Lr^ zHE+L*KGBOuPiL+qRxKv7qtw4u0}{G}U4T zjDl^NZ_7yD?HjuO$HeqWRNn&EXDwLq5CkPK)ly>gT3(@Y<#fyZ@Tj05+HqI8(tikS z+J+{)Oy(^2Rion;ukmNl=m};7NBI$seWUIYPI-tyWheo+Z_q|%7qJz*h^>qX%u8*R zrt!@UIH6msOo=psN~vZ0-sE#-FO%Est=MO$cO4v3-bw?nG-+-qyfJt_+~C?!c{7y) z8M>U_5|XOu zo>L!`8q4)g26>t9@43A#c*|>#gzI>k1wz8xk@wBWNT%t^-6nZIzrM3r0iN_24lBPB zz68BxFkc!fE3phwHkRPH;V8j8W&zh!Dvn<}+q!&c`gn{}OCHpX#fnaN-PJ!;C}+1Z zgW|;UGWsj9|D@u#eHp|UwQ{jXDEg866O+qvj=3a8jqz3Un{m~Wn2rf#*t+Khm%2x? zlcUKLp6Qe$wYi$>rVP-?OLFPdJqc9jY0_MLfeewB!hGN?#x+Wl8Sy=Vh0&{qgtlfHQ) z_kq-=b)E~V*aeoQTS7wKb7~S!F11`cu#ELU)BO?z)85Ksk)2)-+u(6@pn|EN%;7q2 zWV6qHA7@m9s>3Q{EQ+nnUQ@ks1kyg=!LGzaPO>*MFDI-@~T|L`VMu8}w ztj>w`AJ+qcA0glcxn2%<$Mb4QqP#1ZA-Wp}HY?6L(StA@QIB={;zzot%pJ0t&!L+h zJ)SpsjTeBUobO_Xye4vlqS*RSZPkR0(9bkC7Pf&OFiKb@BGK@cdmtpskF@7l2}6l3 z_Fa!0?WrCN#|o0X{(i{8=PY!}PZehh=PgPG7Uo6iMqg#A=Rf9BCakm zSzAVC#(9w*A6*;UxoB^iP1Ycib{9MoQdfdyA*UWZZ8v z_gNDpK2<+N5BeEsD%LxdHg0b1#$p<(gHggm>gb%-!ZvqIw1w_uj3weJSe1=X-nta1hhK z9g0d`is~b0XGqH~&MpZq>l8vRa6e6^hlVx`FEsgl^fd|*H-`}UOK6Oe?ZLVzgp7Bb zj>V>;Nf)Ej^kVF5r{*{1EwBd;7O$L0bd!*A1dDLGsSI@3(QGzp~CT*;{ZNM6VJylC2)a&CSp+~9ATXD7D$)DbO`inDEvTekbd~8FY-9S z%S;i-iR41`ZLeBNM$nOOAKtj!2!51HDJ;Q)lA9Kd5WZ{`g^=9p1aVY|gvsR7MsJ>( z2^%;t%ETlCfzOI}Bi1ldhXoE^O;asfyH(muzKgPN(1JG^m?8jmdZ#qxN^L>a#7HkK zF2Gq?pmKD14N6oRwn4PJu-z4#gX5#(5FZZ}6*bZR?S zK|n#lpphV+FWLgYfS=v~1E7GTq7gHYFfuXo%h|;sqrZ5`Cm^q9|2`_ZYGR#U)*-7C zLP6g*=af`Xx4KKnAo~o%Z_@G4{aZ+0;QiZ&6}lq~QOYuL_VYdUPwaR3<%nKIC4L#j z=3jOXlKTX7jy(Y)20!&YUfNDw27D5~n=SkP;NWrS$*T>V zb8Av_?V}A-mtW6+Vn1Y*v2>4pox{!ecB_J)GT%s09xDrJWY9>FU?bYeS6lPi~9Xl9RG)?VTS<1Aj2ZCqOL8~46>=xg)z0%b}w z(GN46-NwY&^)y+~x?R-~JGg?aekC^ywStq(kTQrHQQthh^#p)#dIE6QJ(4gHogr8p zmxN`QeZ;TV?n92FVeXd2YIUwvWkYK_XLhyPEv&Q~i2Qio65lEVf1EqfpQl_`r}*wY zNA#=}ik9+zk#4XSsu09yjc6(To_N<6FTgB}+Xh9LN=r72G&Hiq$tWiLJc1&Ltej^` zkLtm0iXqx|90woMgu(5Nlm(=VdwQyOO$9z9%9Yv&caam6<*lH7X9=4MS&3gcY9N#P z=*x|d%V~il$i{6J@LG(2wgho=$vhZ3_j3gfh0?Jkmep4EP1KITtul%6N{dV+o(5ZS zjsXYodTySTb#ULuoe2cG@dnOUgOeiBkb_zQWp(iCE=iH41TM&YsRaa=p?M$?$i*PP`R zjuUE?ispz}SHy;hG7>e~Em4O55l_Abp)PQuq*$D+2AQU-FOHF^*-|O{t-aXWP=L}r zgwR>fNZL0I<>DIQSDFhBOG*a}$QI2XnwBmL^rMXgV^gfSrWeJwL^Sa8=VqGd6;2xp)8?+4;~vt3tIqpM2eij%{czwND6f`Etpjc7Ybo#(k_MH!~uUG9y`#_As}ex_-~lSKs$K zOIRabZ_BcZ}aD0vHJ#5``uK1YdY zfeuG$7T@s%peyBp{OeK5v>*H)ilUi`B#`EiztM}zF3qw{>${hkBLx2!)TwkT`4A&HAU7J zGf+kdY1Uh75K8<-@v1zEf86wh{c>jZ&C9P=IB>h$3j)F@m<;~9s!J+#gHZG@MHA-i z&9+}y0KmMu^>XH3hOnI?x>eqD@o^1F1#%iJ>}ty-lpNcCE*={0y40oN0!89~Rx&vR z6dkouRpHW3+xr-6`lDQhZAwOw#^0oKj_&if=9RJ$7=W>F7I$amV}Hr5{F>v^!g?aJ zf{(_mgNNI&;ui{_$?0cl$F>faQkwV=3Mx>%iZh*KGc5ma5az-5-tZIYa&cvG3IjxT zMD(YUV!@lE)UA>yZfSUlRhD}j%&4+Ey(Qk z!f5P~ZXfXuGw;=zv7HKpi~ipX%PuF$cf-1Sp*y5PeJg zt?Z#mabn_=h1EUWPiw!);J)!m?x3vH?WOs24AEo!t#T>X?yZF~s*ajYntAu@bMcF^ zBe?H9vyB@BC=V7+Au+cC5t9uvOpc$;@4zWiLuPy$eDg`)C1*^W_8C;djVBu+QHMl} zjM0~E+*7}68(e)|-4Js0|7|9}M+j6Ld&F*NhZ)_v=RQCjzea zf@Nh+1ZIS`5ZUtzitC-zMo%#Dy_#Vbv*D4u*zvRe=Vi(bkPVM{}*p>8P;ak zt&38F;-$sCSaB&N6fec0xVvizv{)Ool;ZAIpv8&=C&8f*oZud;Sa2xrbmx87x7S|h zto7r3>wIhfnIqSeC&_coYi7nPGWA~{KZxLpg3q9wAAMch+Bd>a93t#k2+{FeX)tMNA6*eQa>}G(a(JeY>RFdR z?c6=nZCRn%-5Zc{`tXgutV6hS;pBJyE=f5GMb)w~5b3n@p;Em29vy(DoKt%}RG9Dx z3SN(=P+0*qa2}~GAEzPMo9H}4Ukh+1n2?B>pB7khZR|zbwH@kFaF12SN7UQ3oNC*= z9e?W@?^RV0U6!$y`ET=Wi*x9Qp+!z!oxkr|`Qht2etqnh+yDuNJV1dw8&8@UgP_z7 zG=Aje@qNp3*wesU^^U|*GTgyb*~>F9G`+e~SpP^>;pJe0CzZSHgr6=K^2(5fN(lyY z*FwyJznN3;wnmO-ySIV{9t~t_qI! zs!hwazCU17SC^fezK-k-7KXPd2?=hVqzt_@`w^bcLx9?quY??EZdJYAqObf8_Rq*V zHdnrcJAWrn^{93vJK6=14u&68Wht~ueBT@;NM@zS{MXGpq(~M_M$`6^(5G(x=9bXuxV9`Odh)bvx%sxdWjUPi zXAFiDQbysSoUw#yIgDdLyvI!x3}U3+s9xKRHcBXZk;AursvTOg~T2b%su! zb4!K%!6Mtw&y4p$-L>T)8v26-eYsizHwaq;e7~P*G13hI3LR8KT@*!=byL&h=Op55 zw*xr27{tg8*>oE@-lsx&s9-l|oTj(CPOn&c&+?o^4-R5v}WYeX)~_pS?Qc1@)Q_L35Yf3Yil>-qI6O-|gZ1coI+VGd6GR zI}7IrpnN`KO57Wu`@(LtdwQrI3+QlJD}!6=<6$&efqrnftp;{`c|M{qujS`r;yVR^ zLTKZHJC2>UJ4mZ+B2PqB(bM}(@5_4P#p>GD@3*o!T}j@pb=ToIfMILFOuXigrsn4i zJnuhVEfxG<8$gH$n|%sA2*MIilCzKIol<))E8hov!Tnv?t zKY_fbTaN98&&I5*jR|*l*2G&;i*yYko7(`4|FL+#5iox-gl#3E`_M7m846V~Fxkyx z^g_m?MRdUf7%FnX+anc=&u0Vuuce-8DR5WwBpW=X0Nm=JCa#up*+KlXgMf^yw!D zm0}C$=C~jUC2&S0fNNAY`c2k1yhhnb7u)L)nH$q+%XF0C{oFmVJQd*E)U}rDrz)T| zEwBoc|L`LNlMjdLH1eaW^q0g|kNPr|FM+%JdIQ-CBAOywq~9SqKGb@U-$YeM)%75) zMw(Lmp!vi>B}tQp2EJKV3PCq~)G1eFGyc z`s%i4OueTv;Hmm~hPqO#M3lmET}Ph8HzR=RS)3m8S>}3EkEib1{h-0nu0FAyu`g1| z{)2)cPaSvN@u?s02(9tm&&2sUC5V&vaoOgf{D#llwvFNullvxDC5I$LfR6Frm(;wlG3IdcG4qoj z{jStT3Jg6n5!TJ=LLZLD)Cn>;#;x6BtPQD6gsl5tD?wS_!O^&awDXu6?CNkPVJbLv zggc9oLB=v*JujTqEv?#kSnKW7XtSu8+q6@-)yOV!1)%iIi?*JKZHY&2j)y1$IOuTD z2oONjii#Km_c2NrtdlE=9!g7QZ4Op9@>I`;EtNK7t78wfS7#h)tRhDS+r$dDB*G+c zFkn}(l_|oOCCrDk; zCO1i7w)H$dyN77J4=jzAuU=VpAWnv_v2yW*n_MWuwYgyL^Z;ygHyvtj^#1{3eCU82t1{(+*a3ydA7P z+^ut4rV1kQs%#^*#Z%`D>g`_F$QGW>cxrhwSA}nxhknRgJ1o5%zeQAD#4cu;#l9Gx ze#2p_W5p||+g=Z)1okecPM0Lgf6t0RAc5m6*iAfL3T&(iKuh=XjvD!GM3(9fcS3Hq z9Z1jWHSOu*y26Rk=@P4KThbKtXQca-P6p7$VxrzeGSmcezz8VJ;y-w@$!DZ7hgT3Gm(Zl(`gAW^$9cRt9;rEo)RGhNAk>m2r_TjprDgBl6gZO zo@a1lrp4^hpGxD*Awe?Jc~XIBY~y3Bkm4vj^>vVK+EP=`Syv9Lb1#0HJ7&w1*tu1T z$R-);M$Bq7S92$H5_~F|vovxs7{m9T+cJ1dvgGI{^=4v4mw-t+j=Jwjf3-|{lKc3e zkkgUkURos-8*g_cRT$|tmrz;rCL)Fnzk$3S>YT{bg!_`9d_FD+{EtymVy+id+O z9bFT3_eN!-9%?dTg$y?`BwHMQjd|$iy+id1gV+cOoE%MvKmha^cn0_99-U3Gu0@+{ zrMU#}P~^9Uvv^jbbLH=|$6?9XFA(>)*(cU^F5z`HFw_6(#B&6^XuOW)DKv)m5)4*^)FH;uVJ%08yJspUJSA~>)Mrg+tXnu_0yj; zXz<^r_eH!XQFdnw>5(x|?UXu}sv$u6rJ}p?oVswDo^Kv-;EY+OD)0U8LQ}T5swJa| zix$@QmV`On?_nXvDLYxQVeRFGV_Xy19!N8kB+~Ke6uEK0 ze&vz)C6ZGg0-E^8O$G&n8M`$0QlD8Cxm^A}F>IP(NxCZtnt*v{Sl7yqfAT*+q{@&8 zvt;p!Y?kv;7@z!8L)jE1$<0&qRi{R}Olg#4U%^1e)T9ypv?<%$8@93*GRhdP{`2`1 zSQ-5L+p=fPDNHv(hDB*TWlLQ62cD=X6>_`5Bp3K3x*30sV;X~ceOJhAVEcURWtjOX z(NI$QQn%-e1M^hsL3H2f>|cMl2A;v!Uaa(j1SKMa*h%P&#i5nI5VqA{Ofu9P^Z?jx z4CbUFiHGx7K`T1s-Rg!b$AD+d$v8Pl7wTZ%D~aND~;9%7VpDY*$i z`DAyy)^F}w>XFI9jDarhgW8n`ni5ALG-UyVNu>$62J-{?hq0K?u#yxadyVBrH1wjtpRT_` zjHvyCXs{!irj5)u@OaZ0tGx2t#!}f&mMXqEIFO^45&q{0-PI_%Q(A;8^ z!M4lw)PljS3GMchHZl>LV!-Q#MWYi4R0W^)}voZYj>c4R~h zJSX42PS3gwu0$#>s)4*%|C%MvjmFXLLPL?%#Qe!ao;3fx4{b)3*HSNU{Fnb)=k+gA zlKx9NE9fW)BKzXC&|4EY?nL!lRJ@1XF3ISm3P)6lrRF8%vPcpiH%e(@ zZxp^$SgCM9T>!>rCUBIchqu2wR1+nIx>n_UXsU(GSK`W5L;g4n>sAg1)s zIBk~>O52nN(QEmCuy6$?hjQ*p!-x0O|6u8MZk+x2gH^KPeyxo`uRfqhf9#L!oM!Y# zkSQ=;znPz`aOInSm7RQ{BOsU9YHsXi^dn=`Cb$x5Xtue-(kbdJ6?f3#_d z>6V%)G$wqkfWXP3P!p+pxyYcr6U0fKra?=w(isvOu^^P*8BI zP;esrCM`D}xye(K7(g)VmVj8Ykzawuh$a)*+>zfA8q1dW@~P_C(YTdM1A(FVsU+J@ z+;YEh3I^L-vebff=e;IUzbAW|Q(q;)^}DEGuzg8iV&_T7oA~B3PY!4-;4NcP&jY8p zIbYw&7rb#xla>BD&&O#(8hpTOb=6rytFm_-Ds#D?(7y#QWLD3g|GoP>U>QjF+WS_5 zrCCW_nK~kr9rjz;b^SW>*;pG|e+$t0Nv{w#WW8^Pz{B-3IbI{V#Ai z_C99!r)zGB!{T9f)p469r1iH*ZxdZ*F8wQr`PZ09Q3gSU(=yW11ogf~F{r}P4SPE- ztX|>BcR`5cQIZa5qb(?>gMp!eEi&08M{|0a7yGhXZc7M9IFQME_&s-zqg4zL?2SzQca_Vl!_0kb@Wt}Y zvE?cyx_MJ-iL7uN(~|tiG3AyYb`(?81&E&<>%mo1Y6vofd6{Xoh%-GufgbK8;g~l@3rKsPEYi_+K;sbwz>?rjP*KATU<`-2rdw)F@ZJhf+Dqy2)0f1ig7Vec?ad=X%WNeVcuRg+FMW@;wN4 zoZ{E1((v$d=V1>a>BB0zJ@}oDdt~5apadXtl%Q?7#=w0nAu>Htz~6X!w-X#w$o_`X!YHQ z+t>a;b$DeTRd1!F1#mIXGp>ilZW|L1mPLqqfI|CoTte-~IuCLJ#AvEy9m!16t6m4+)M!ckBQl>xy$X4Bi zFaEOAm{slS9_rJ(m$ZkSk7ZyZg{L|(H+tuD^AF+_cS8-mo>kBB?uu0E@~jLJ(Qa{q zvnbGh-95c|^asm7pdMeKIy!%>_VdUd?z+eMlNx8rYW3y``BaG;jUT2w4zBn^C~ zl^208i>byL>H8z`LoK3qzBW))7K>SasVuR3YSfcEImi5@3O!Y|_E3*Tmdrk@dS?bc zr%W#`{sLr+rN)%(MI7V-_3y#t_!d(_KX4k4cRtS62 zllJA_y%tGZvsptANh>G8Lc@tH)H=RPMEdhg8PXR#NAp!sJ8Cy-#Z=!%HTW3prlB){ z4^gcSYSOOeRy5}W*rov9Z&dtF3raoL z+7G6(jo62{(|5Wrui878QT2;#%276Tek*I}vZA(SzX-9CrtTsTTV`|Au%*)5_VfG_ zuPJ5?LoYmQ@B+U-nf6p*5YdOTB{_3})#JzJbt+@&okdRJQJy0yMB-6vC zZcVgztwp{oyp318(Yitxd$3*-03*tFsu!lGG#w1awI_!kNxc~IJ;6z=Xz*M&G{2p` z^<}fZ!A^#Rx`*AF{UQRJ;o^Lg+;?Phr-3hO_~}MQyV*KE>O%qWLV(X>;`%!1f(HBG zm4{q}-8a}=ekLc@j&9{vA81r>(4QHrWATxT-+&p<)LRd=<0V_77WG=!2C3169gG(d z%R2N{&VI=qKUz#UeJ1}kru^gWwrQUM>5?aGc%TupaNaXr9{h^)esF+WxYjDS$zXip z(qgl%OQY&oIL*hnrWW&4oojhvKZ!oxf&{ed?~q#!RCG%*(%j849qn>HR5bY-jA|1pv>m^9j$_$FA>2i6%U%QB)8H$YMQ? z@XLtCmM8d;r2WX#sxdmIe{{p-+HJ{vhTPf$m}ufeU#Z(gZgoF8e}r+K`muqX*rU_z zW!)?p;7>IaOXfZ4TXo5 zt>#B6ATevJ83oIovD1md`Q?RKj6jBV-{6Ljl1M)IINR(O@eA|$nV8_+6;bjy2F@z} zwp+m*TaJLMqLb{Vn^L-HzcprgE_epJkQdT=I%D#|n}$Vy0&0Up5@xQCm}GQeR%Pkq zaAWqD=ANot3QM{tc48Nk^cj>3v@@F(p2b!jP(!PUVb-%LAXOaq=<^mhoh~(!i7(M} z@pK{~(h0dq)P$Ev!t!ntsjtIyz6Ir`{J1*SpAI|F(Id0hMUue^5n4UL2Gw%K87ez~ zNXHSz&!}Lp0Xd?W*{Ba9W#%=QD1 zOp5|bS6Rbipr%~-lX%{5qf46QlN}|P=h9ntJ7-800pMlza28 z>OnpQ@|wI!{|Pg4Al*nJS$vE1HT2S%!llu0O|e$?la4w6uEIs};qSau|GDHXp`s>I zgO7S4bZ$q-e5y`UJ;nkkpRbF0Z4Fg)__Lh>JPKRdxdBs*=B9VLp`Gk2`x2QP6Yr|e zhZ5mYDrqZJFCD5g7u)Rnb?QLBQwm}U3gr%$ER1}CENQc}b9xs#^?@`dD?;reV9i3R zx8*$^KwEn6E?&kpLk3X*jQX}9vR~GfUZ;M}QltBrpq*P@+q~B1u@*%`v|5Q&;FTUe z8S4ofL2y}hWf>h3Vl)VbMaDi+woeqLaVZ)JZFPqocRrS$)o?0Ud9QoSMM&Y9T!DnqGGShilfrPx?I+EALU4^AG#7(Vfyqugr zl%pR+M{*~YbslzYITF$+$cdVx`-yQ^JHxbeZgD85@>=o6RL z18DuMoSI1BmJY&SdO6-bmR1<+8~o^f&ek;5UDkhT9GSk5o&-zz3-nRde2;P}GswAl zgq>hYb5yPY*5J(qCLg(a>HnV(Th0&Ww64=+8r{IU1AZ8p0#$8{dHzo5en)Io7rYf+l^XvK<^|j<;1SD}Z$raNv;UnA&L?Ek zrJ3p%*2R2@ej`$(ENR3;G}iNbP{67%lOKrlB*80tO<^6pd7VQtQfqCf(YVd!=NF}L zG~u7R^^5zr5)Jdmgp1bx__cRq{j+J&*#tQeIs0@|W(uC#BdS(X1UH<5s7HA^OOB7M zq#Rp~`{*Y1FnMiZqcif_#4H%6%%^X=cJuzS<#rJ z8$9s)+-A4z@e-!FrBiz06?0!$5x@Sg-P0sh1p*v`DyEu|pZ^(tuM`L8;;~%X3-J5Mcpeh^s z5~$-R^}|j{J%Z0bLh|{v;(2_{`dU$wIu*+p1y*6=t2!QWewloZR@Ul3!@Z;>4((ni z#fN3In)iG&BA>l3XGKZs#G%jh*sTO&>GkKMfu?%JleUiUcAU$*uP!~utkUOT8*YIR z9D$-*g1NMTwbnu?e&+^D?B1k0^NPT|+9-fBWQXoq@7p*+EkfBCWx;aZ_l(9-V+pvM zMc;x^B0}E!{eD4nngc_ClQY^Cfg|QE8hvMy0a@nMVPFguLuPgbJs(NIhF$%cjyoXG zvb;b(Lsu?Z5%;-Kr)&kwVJjB@$-O;bhc4>3bkA3_Y)hiaN>&xnbLZ^>>wm zlY^Ilu;bB=nhtD}hTwhBGW75;YD_NXAo_LkyF#Ys%PoL1y3afE)e^rlfZVC=F|3fDzn|`c% zD9g@b=7e@S(^%&;ZUv9^X~E$G_ucZT`r*7jf8`iKm=0d22j9*-!}(_p!;Z^ zuxkz2Vu_A_iF(RlhA7Duh+ID2B5K)!iTf1}HdzsQZa~dIKqi0b8YBlXsGF9s!O8Ft zTQ+{qst?4+)}O;|yNsmby}9;zDkpC3v9}BO?(X>9r|bD)S#>o5yMkyBW-0ag#?Srpkg<9U9CAa)BqI|165Bnnn4sV zw;yQVqmdk7notG^7(t>0$9AuO=PPLUPXqo6}5#&6;64ESjE zhirGO*5F{Otq%8v_`9->(88d5n*9SQZ1r|8J~};$5G2(yHv~INB;oyJitSo3E6hx| zr1<9N`nQ`m?mAGS{S^>=A+# zDeeFq(E#2pHqrPvy+Q^FWe!U?vN;zIDJ= zb*v>+i#!8oveW|HVo_JfD{qU*q5-) zZk9KzPD`eWg#*b@z}`k-TH~BgosS&34zIWd>beL+E|rwX^7PeE8z*+pokO=c?OH#4 zrOiK{1^JmDKO$RiH4)Lsn zN>V$r%sE$q|DVnT)FsuZlmp-2Sf?R3hjgHn^FXUg!0>Yq+q${aC8mZnlD2yx?QiM)|bJbuwd!b`RuM$j2)6hs6XUlp^``hQ?4+D9mK8Qdbqu$%ZfHeii`RdSpSg!`d$v;u3Bt8> z?lpGFdwu&Q0CT|ZCmw3wBKBCRlw!a$%yCB(^}&M~s?8k*oxILtU^MBvZHffj&GQLI zYiV0e3ohjf7AjN$jp}!)>6^WVWql#Jc{UK;6BJ!v!!iw#ALsxYvvvU z0y3Icvjeezok=;uV3@+lXp6Cjri?Vrxt%T$Cq^^fPBu8!eDS*IHn--xH$eAv$k~`> z1|v%4ty>*%Y3zH{^LUE5o+fo$P_oXk6{IcA4bg@B%MCD-PYIHfJ4=xEC_KllV`H}yv2?+?(OZkx#9JFfAHit&3B|G}c~ zm7`i0d!m^r@Jo!9H$>M+W7?e4m})ebs4#Vx-?H$HsbY?l;azL z(9!Jyt>v%mn$66q*DW$3uM5;}at;QyH+6~Rbtu-^nm~zcAbn@W$o%01ga$^@cYq*5 ztuiC*Y$ASHPrNEquy0tEo(VAfZmV(>Lch7olaCLV|z^T^tW05wUX;c|6nC-yuW#oSvi^&#iN{OK@QD> z^h2(rJ^k>^zv5HRtG*17xC4}u?Ma*1czRMnf(SXC`dp0qFT#==A_Mhe9F|5wx0%ZE zD0$SJ58Yw1&h`Rze`Z>Di@oiCgv5q^VU#JdRVG7*kW>uMd$0EVo&J$p1vXf>TGyUG zRln|@+>Fjy!lZ^N$QJEv%vtt>?e+pIG)1_gV#@1#w|D+KCR?vFlCcd(6Dh##g=yXL z|Fgisw-eS7XZtM-uDL*FdN#R5-Q_fa@crSGeL&_{@N|I;WyY4Oj!|b?@JbCY7{6*e z*H+@eEZ-*hHwB5;X?|nO8xWo%?dF*jOz{zR2?o?i{s3z48Ojk2rT+$}TkY*6++(sl z01RzLM{rSy9(>P{D(N^4=~5~h5Qsam1>TfUfKL*+L#HNDzyl*y;iF?8r#FXGPmt}O zC?>tl?|}MaX!2-W#*>Ing|K<929E?3Y-DtWC7a-f06SJ&^|!d_PIqkoh~Qk2knp-U zg^0wZ5DV>8u=1FdF%g|kse!@vaYTn|)`^h<>l!(Vi|)JdAQm3vY=R%iYc&`D{W0&4 zSrr#W!rPYF41rg|i^x8j6b4at zLGB=@S5;&$-B7EOn#oJU$rAQpD@$;im*hSw{Ih+hRX)oDO#-b>>-cJP%vH1sIf+%R zQF?wW%F*r<^n10=i~3($!F& zqG6UcraQ8o0jmp7n$G9>lnUf#auJPHvvM>}ch*1kNZ>M;cnJ?+ggw%~n;Q3_wXAEcsz=ZL_o-gI#NxAV{=2;Vl> zU1K<(m_-mh)){L7N7k(sq-*ffg9m0@2~~uqpCCUEgI`CyN;~+jc!Uif$M9tr=qT6c z4?;64KW+r3%J;kkGtwI&5FsLaj9wN1cCrW))lnPY&8Z2{+c^HwemVuNKn22(rUBOA zvYNurn4I(3;tN0D^mC#90{%mYFH>32)?%&7wv{J0@ULi^vqMV-UT{X^2fF4DK=B2k3m6`{`^( z&MJX&NX3Qr;>60N=Sg3pT+-s{=WsZboA^#*EJiU^7`l7w|MTTRu*pZP3}@+R9ozd8 z_0i0BHVe-gyi&v-(P}K&BRNdH`0IcBWHfD?$(7GUnV(09C=3O0)Qb_%{sk#Cb21Cs$)WBeZnl0)mx_L4;)tw(DaTbkc{9|nMU}d*uvz^W4s6N_IMEY(}xhOnep|=zbyCe0m^zg}S zc(~qTXBU3D?QllF-(%}AHv#EE*-$`;I!$4|O7nMHUf!46hy#N1IpCEVzeB_oTFPBbh8um2dlp>@<8uY0@k7q2F1>RjOy=<=Jv=s z_~3Ordu1()`V{D6eZS^-|KEhxg+Uw-={0AjO_;7hU(CVpBKOKJB*=%lpoG0z45MF)VlcGD2r`l%l7zud7G zBk{`kgZ0EYr&0S*9A?2SxiW1$;_W%rewO*jm15R}8dHPJY!TXo}upg~SzdWU{lS=pE#omMjY!o9Zx++V{Nr%&&0mRzlk&Q*p7jNbfu zA@QN9iy;N!;r+-3yQVhl$HCmI*~;kLrn`Nq#lV`va>AYY?vCnX=|jDMv5#Iu}Mod3~fZt7^`JPtAD8R0S@5=TWFor(A8FJ1ngqCL#o zy3{lUF`}aD+TrW(%Z$@TkuQ52vmmkV|0O|;AJON#HzEnYen$isBlF5Q*kJPCQ|}zn z#k$uk24p&|w}O?)cmqH)#|&-q;5=)>+_JE#oEC;MlaK73vb;O3=Ik0C!rC?UQsFPI zhi!U2vm%<&wF$w*Zrm+l$mfFMfx@0XmNZB~jg(o)V>BR{Ejlv|AW4m2jFq?qaVN_E zNY9w4^Q5Y4Je#uj)ErW6qN{j9h3Ppbh4)nk)ovYExJ$ifqQ&UekeoO3kNYRnv)&vh zswPjYFB$AYi-nx$+e@X{scb01LlHH6-<+gmz3ei-u9*d%wxLq2Fw#WjM?&xK4nt~2 z*w>fnpq`0m`nk=qQRVycBh-gmG0JqJ%O9XAewpB> z(IV6N12Pohy0J{_ve>7B%fC681Zn!X4%#>^2YEs>O-Th@j0Tbcl2bR$E6r7xlMRsA zC987+AU}$8ChLGD&%PqF9j~%pOwOw*^KLJe9lf=i^B1MM-a$5Fa0ukw!YQ61Ph*^& zOLepsC)xk^X)^7k`aIrO4;EHn_>`-oDcq$Jeb(PO>lc0=tk;@aE~m)K1GQHezLk+Q?-@~V3d z17wV7J=%~mN%orPWn7Ea5`?9oJ-bH&tm)+ycH$^qSl*i2#nQd2Yq6}SrAj;%$#TqA znwm5q>{g>NZn`7mWUZ;mAt#nSHZ_>T-n)=6GX| zZ;5k`?f9!YaD_MIxI=hW7{W@FWoU&k^ zo1q<29ZLb+&X3m@Qn7#%T^#Gg@=|w!tICPXGw7$OIyRO8#+Z5}Qp3sM{>AQ09t7{D zfk7;+e>LSQy6ivrY!H)@EmP?!UuzR77VOzlR&T0c9BJ>yz2IAJR=7b~=V&DOg5L zFVS0S^rcf9>^BZw`g=aCZcIq8aFtLj7%>C0_DwoZ=OtDžmdMEpGKsc-V?+C4vk zH0q>bPVqhE@Q|%ZJ-_zj+vq-+%RSd%m$Crig_L;D=;(g-&haN@#Wr9kNuA&FKjn(^ z+9H-XJ>zpJV98$KjlxVlZF=>g@4*GOy>LsaxgBWLH%;{K$A)kM!xxKA%qrP;?Aqli zTT7<9Ve_fwGrq6Wb)vT7h;8M95tZ_uLne_kI$4z+2D*Z{Jl3O5d+}ig#~_2F`&@*% zcr`<_p1a220%FGOXRzvEE_I=gfdr7S*H381!@&jN{eT9?dwTZnwweQ`XVRhSOcLO| z-IZU5=B*Vw8rH+Nvd|yjb}w|Qv(V&x$Z6-OH$9m~J#i!k;3vA-UIP_Yb6|GJ!{1qD zNgPIyyw1ng$TwJ@7IR)!I71s51OQay+}mn2 zsMHGg*s_ioq$!sGV5=I+v;2e+>Q7C*fA-_5elEf39i zi^UR@ZwPu9CdpKo^~zu!G{z+#)D8fI~k0qQ>Nu7=JT?aQ}-!1mA>`5t60V8ywrZ$ z7V#72EH{xEU%k!iO!;qpd~t0R0Q09Z0dq7<6<2AcMYiH0b;2opFS^aM?BwgOnc|t1 z5nwp6=|6-C8vl|g_-BU?7g5#&Ez`TlVg~x$B!95b{<_KJnAVf|;k9nZ$0s8(wxfN0 z$OQ(A_~$i>V1LU2OsY@5{cJGwkSAM8_|@IzUC)1tciJX{>0~(80IRCRcPhuw-uBc+ zjDp=*{RExGxBm8|UFJ$zx4if@*tNJ5QKg>4f{A-^n?=nNQZO3^r#*l2H)MIh*woh> z39kqj%UR$ZHkU96C_RaT$EcavZI#iM=A~xFoqQ0;xz_SercWg>o7)(fCpP5>LA?an z7CrZ>b(7qmpM1RM$UjCzkTj54-RLqs?A`{$h@YF1$F{C5F8DG~Y$UX}~`!;~Ft1=AD1TooC1J z6!m2ISMQ#FnAO^8$xW#qli-%zX<3{wv|^~vK+6vYXVqz;=cPxtdIz+I^|GSkmfa?> zT4ZjmP8HNX5U^}p*IobhZsHHtsRlZDbzbsn(Z%|#r6$6LJGZyE(u=3cXw;t-5Hz#W|;XO z|Gn>RycfH%v3K7_WkjD-eNIQ6iqln@UuJ&UVK;k{H1?2H)bS6J$c$ZT+QQANUR4I) znA!XKhl(GSbKTdNm4zF9I93_*r+xh>x6vqVB4h6^m$+PQNpLNeIWJ;62n4$)eICmZhn;niI(Se-BuU9ZKn= zTdS&@M(B6@ha2{nhI|8Y3qg58hRUH}ToL~-%8Zb9(tl zZ5Sq&&!|U?4anWa;;Vgrwo1`AeH@?e{sPE}|2-GcZt8I@DDM1gnZ={9wA52u=DnYH zdufbXg(A>bxYUVEHvY-&RvO` z=SfzqiZ4@deb$&}%87F~v{sGPcciqmeTYM6#L2XX!-sJ!%Plje9N`k5OcYw9T~Pod z@eF@mi7VuHq|BI8bOOZ?vH0SMw+&`K$MZk3EjmX(t};KqbifQ5I)9D5r7DTMxywLW z{90C|`c>JQBSCUj&u~JIEjcel?QqjD2H!-ebd@{0NO@3B%&1JSpIN!YR}g%yZHSup zCu0w`a=4muP2IvmEQZMT_T;Mlrv!q^bh=3Y@4&vg6q>%YkK$;pK6#di=<{5)7R!t5 zxF3ml2v9vPj_oS;7@$E->RoV~Wxb6wxvbBIF7;Aw=yslaV`sHRw{=P!p^d&(eA_Sb zErn^Sln$fL^zS5HiD0=plJUMSvx-FEj0a(XhlqY#J+~Ok&Y4XSD9@WLVGbDoNhX%9 zE5(8yf0FWe7glgnFY||qpVM|IFMlqZ7h(4H%T7xURSE$fjr9pO?(tSe!Ym(HF=dOh z?wRoO%t20=kx#jYR+cY9>PC#v3OlcW@f{axt`1Ta<0pyCc^YY6En2m5^6le3HD6Z6 z?&Psbjxm)IjnU83l(<}Hf)Qz7f-kx71sf64yk-?7IfpT*IDKXxNG~80St^~ZtvP8a zS$?g3aP+DFD|uWW483*8(xQo~8P<5TcC_ig8+aM6dfi&I8yRa}YMR$(_0vPq=9pu)OKDnF!3Vzg1a6Aua8k3_@tJ*ZGPsW-N)S-g13}*VpzQx2>rGe zkg6|WvWIx<_Vo$v`R0;Kn4+V_Wd9rL4s8F-715a?@ZZqE4ZqX3)#qMk2P60o6zL2N z{aoBI8BX>i-SuyVWM*`Tzd4a%VLzC|At3%HyN193kh6=co4bXi+})#-l{5}6vWcl3 znEe|8GK47kN66fb2$>w6B|<42xU1&ye_Vy7)pvB}#4_dnhqAIWRXq~GI(ZQ+} z{#0mM^+wY-nq=Oz4Qm?T6!OaW>;v%7cC-F#cjqt3qI|B9iR6q(@JA)8$ zB!S_o9vFle=?%0lJ04|@q(!L9M@Hi=Lz9p^A_te3o_a1Q;XL$-rx${Z~F$ zpv5@T#X8dQxB^R?wu(-krP%QX)hT`}K`WVj(=5Brf-Z~FckF?pc!KZ>5w?z2*;&$M zN@!$E7oYNYHMFqZ*k9=?hVujp@@MaQQCLpOASL1l`0Dk7;z8>15ShomVAu@FxK8A} z<_hP2rpbX9P>3NS5Alcpr&az-H5mi3hUQenWC9mk>*!YzdrlR=4EIz@S5qh!tUm)P zG)FlWB?5?*<&wM^Uwi4ZZA^B+tm<<1N7Sq8VeZ0x!=4yKh|9wKFFsfY?-iNmnooY_ zGItYtSPcq7{0RQ$hB^?QQtxLuO0sHVaaS}15&B6oY|;c z(u-F}Jy~w8`oWb{tE)aETIp21a7tKecZM=d5=5(7GW*c^d;^`)Xb-(pX>O?EWDfU za*I@hnIU-^=c4KASenT_%ZP{845eIBW0PCtX$i*pVpAO1;g;HZXk*2gmLL_yS6iAI%ZyrC^*RWJ|&NqE%fy}yNU_&OEFGzIyLJrCOW_}qg4G=~N9|lc|2B+Eaj)Kz39jq&T-%@ZT z9nP_p!c2=KXkDGmtet>i1p8`I)`%Ppi5tgTIkRjQ4@6CehICYXFmu+*s)gw= zL@~7Wimm%lyIO9lt(I&k@j);K>p7a51M2&Lj4)-o4CJ~&PLC2!=6BGPJPE{DTs+&B z0(zuZX}nB_rM+bbU%ACOE%MtEWTrSO+-^lE4z$FO*c_kM&|q*MFsl+aJRcyI;E{h} zl`Kx4kO9Fn@PSI(nl0vQ?Ri%anCw3}+{K z&rBr$I725wjktA(Pc2BkOcb1B_G5%$p3c-^A|ia<49nU=g^C*?uXoCVlO`*p|vzI7KH9L+ib)kg?K_aKEpoe|O zQODm+dsg^tmjNx?Ux(CHxW?3-Yt=C9FoRVFD*O-BKeh}5)lNq7*W87@s74Kc&Rcm= zj2DHhjjod@xD8zi5o!OBKMvo~HW@b^_~CUiF*;j`CB>3f782^QL$eeZnWL0~(Ao{-K8+dhCTb=SAUpU7x*ES&_q^DW#St0!0(9 ze*C=2SmLV4vjqtu8i0xwPg7oXAg?7PkB1d4oTX{ZQ{Zn=-ba_L8ZDfTs*{_Xib9*h zGsw1Js#u9jXT5e9L3T$IJ=Z~aMdv4C?rdxA7pq-n#xGV?2`RJ^Z6N^~C@$yJUSIq{UcHd&7;qYLS#>Q~8q?Io_-tZZYT zwig_JI%mxXFBpG(pX~~8nTR4Sb_88&*m9`$o!0EYj#X0?eEm(mT&qL^gGVE8p{CJM zek66f2CYw(xujMs^iYpmXi;H<|Hz&eRWhYL0l+)6{na_cV5c4?+^xE$2sc5`j!c4W zG%Nz!!pg$Pt<``f94ijN-*2f}-kP?tLJo%<%bLE|F^$J8dP^rJfycr%??|Kdqkx%W zFEIMY0S0%De)J$QgHe)cGI54iW0SX{vA4oSuFoFmr$|uTtS526Kn#SJ9o(2;Cf503 ze<&xx080-esQ)rLx)P7~$M>WZo58+|-^Hko1vEH1PS^b(6(iW=G!73)iT&Dx<_{kO zX-PR|wyI2*?4i-kPcgH;1VvcQBE5Yz?-(`q`Ey>14aZTyvDQoFmvsH(GZz*2_0$zA zF=IpD)&I0yWZ7FH)#7)*XW3ybii(boUcb*vq>(mH_k~#EyKpBQvp$OjBPZ?a{{^6N z+yBXWZTW3?zcvLg(0e5);!CC9UjRJPhKG0rHNS4ZYN=ENkFBFP!HaqW*6wE+XneT4 z(1Cpqx^EcN=akDqrVT7M3xBYj!VHWg=NDFLdys}J$`Gkp&f3%`nJu( zEe4rdW9;3DM)WHev6j3$ei@%oEJ+iq@nPdKLWL%#vw(v?Qw*@a=zQ0vAn-2hj3J^U zaLYT_p7G9}<6)@FKW_{v#>Y>?m=_p!%2C_(<{1hFa&kF0@ZbGbzBq{Y?~mAq8-CoM zX1(=9-W)?2bcb8*F6~`3Ij{HB?PA=)xru#|_Ww^iMhZ$;tNa&pAWf*=eR{J}+Z&$Q zPlw}FWPW_kC8)`E3rajc#$7-ax}2|?qvSf!)tY@ox{l(mv4Nzn3*jjPAwxj?q=(M` z3s7p(UBhk@{7Jgey-vzAd{7^MDE<)`+ktBiU932XBB0G{et8pLdRc&Zb% zScGP*>{B0Wv8n0itrLD*GYkI*MrTmOUjUwE$sQ@f5=l%lps!=kphc76H!c(Qds=|P zwjCGZvBEBm!X@DsCSV~M7%C2A18IVT2S8L~7_ew#Gz-dA+iGvLm6&RESRgwSXoU_bl9% zHAR{hp50nYXH|ji1F}&i<}(KQm>kz4#3$U#i`>7b<1FzMc=%qj&%sw$cYHYP`DY3N zfl~J=^C|hjBkFuo4obZyBhHsE`uVqT&c5uc<)^9ujyB69H|%gO3*+y$73Dl`d4_gh z;c79{Goq%rJ!z9C4xMXa(DnS|xUkoC1RJzVKvpye*sW~A(Yc;`%8SLW9fFjT0_l9F&j7EX(BLFhYB1sTt&c3U?^-Hf8Go$s2`(!C|u4-x!A7=i`opgd8N zPe`Vlo&}`fR(EfQbEYpT2_c$I#Y}L-SvBvU>MOMJ*}HbEkETjJ0D~{a5ie>PDiFrK zh|2CFm*aCf6NBvbS((##Sq}S3O?qmCn@l zYXj)rd`uqhieF;70Xsj<;DeE}%bAW$n;Yx#`8yH`1pLw7D%<&ZfU zLamu-xx1lAaM;T%cj>f9n;^JKvtW;sS_6>IyQy}Y0+OH7DMFIbdDZN>)deHOza5ER zW3M+-Lw&&}rK+WKaV!1qjVbew%aEMiFF>td7_y@+T*_Z3Qh9<@1}2XBQpeBRbMao&#8N)DFh^={0(^U1WV~x1zsmv+V<|4X7sUlCH9U62_j0n4ah(`<=A?wh0 zMibYz)`;5`;+orgDU$7yZyqUqKDoyAUD+Gir(KayYeb=%wcPspeM%ns71F&7Xp`^* z)$=k_yhpNiEQ71KMO9~yO$8elTT&uOiGB*onvaw6&EIT0sQD;zChdoXCB&}1G&}sm-K9am4ljN`@jE-AN_0kxRZk#R zI+uRKXQ=g;y68I>Le)shAKR<`5)4D|$o48!Egbp<8DTNwPS{IWRZwEqO7$(Cx&(aM z)wp*KAX)QZd#~a_ol#iV{gpX9w`Dc-aKj<}CDuN!ndJ>g3XQ6TzOvfdHZ`48!aMfL z&^mEgxH`jNy3xtc400G`i`a@2aw|I(o;8pH(}?Y6V!_vJbxsNsi?SXl~zYcEi}Js5H)%jG+|1obY;-(DI`(nMUiaOtoRh+|S>a4EC+m zr}c$HV+@L#*-$o5Sg&|2+<(k?=PW{k1OGyIO;Nb@=xG+9q&ACRIi0uN=*?FEx4+aR zH6+{ZU6@w(8M@ONb=P~A?U4LvpZ7;rKz@h7pV@3N^Miu*ytY3yN*wvPXrmHx$W^?j zDA1$h{C4I&k~)p?D`uRHgv&KhjZ#~hy-l@QrMd)pxLXPw+QBq9gZv~;A;QSQfj!yj z6HNqYlce=B34~m>i)H`!$Mgy_5XXWD0^(@3<;_LV;3N42WB-gLvd-`;Mq*(6E{RD* z{dWPCq{|HgL)sh5Fx9OlDwEXweyJ~v*Nbw91FkzaLS}oKdA#0XlIE}8y#t~ff9Qyw zM0iAO-!nA|Ni_(1`QI|jP?96|r(G9fsOQ(A#gJ+=lgWL+vRta9D>T-z+4aJp06CvO zYlYnvp0lE`*k2W4*vDS|)XR3nNjCa!_D1fGUekT(#NX8rsj~Avq40)Ju)no%>_z23 zMAbea!O(#h;F`jh?#edYrEM=hBFP$R|Ws67e;Dc>AXEbR|gV^`TytXs-Ej0wpHH z+P+VJTNN77!T5FXsYVaI7$0~A$uCCK7mU54Pi<-d02|tviM!eEN1eBi9DHcUNCODf1F2qz4gAsMSy+?;- z`Q~(AseVojtwFb*pt#ReNbAHj%mMS)%L8i4a6254tc(Z{s~(UW(2xyk0pPKVBGRJD z_7HxRAU7edQwCoUxvt^_>@?@#k&rC!$P07ksSi3!+OH2lFQ54{(c>$laUsfqP%gTR zDV+(2YBR|_AUDc$Y0lw+eT(j;y{GfY|4P-6-%`OV;n{v$suw>Tpydk6V&dQOW!$wVb&5>Wc zs<;X|uR~eNNxgsw2N`1%iaXyR_!rrvBh#v!gCb4fIaD_4Y%7vLRw91RT#RDqT{{m0 zZ?4X{wM<`*oXQMM%BN%=zVn)<^jVv;%`6^g!1tXG7@F+&r|d$EKjU`;!=5O)q*~8~W3YA& zH}+Jg2;x>7ajd^ok?=Z};K=p5Y^-FUZ?t$3F-izP+qvjK=zl!wvru{{kKB(k=LRIW z{i`lrrKP7It|7yuL8^yVNU5@PD`M}#Pl_RAG#zf zq3FRX1fTZde{%oRam5{kE{X}Q+9X`nw7g{4=z(}yAN!w(RJuxSRoHTj>xr@U{eL3* z2JA29s$;;pRgcZzA}FLmX%p_c+kn31{@ynA=%f%WT0JUyOgO3}zxYqJ6&o3i{7b3n zY<%lP45lCpVOt$7OKiU7AGoutu>5I`fpVX(iFhi#;40MwQ>D6?FD6#}Bz=u~u?Q)bA zSH*V6!6;t`EUefe+Jc&wi2?{6FshB+>kaN?=6GaOtvYDM26sPGQES{Q|HL2v$d(jjFt?$#>R60;S zWW-LiWZ=kVbVm<5$g_dX*77i{_SjJ)X}I96Mu=3Zcdh!xoGd-XVSH4R$QAkXp>%uZ(JTD~@cgkg@6 z_@xEqijs*sMAM;23yVFc%qwI*>hXop_m*`ZHOygxt(zr&c9Z+)y1E*XZb(39lwC<) zAA+aUmh-Ei2S)x=to?-*mODLBvMTgSWfK>eJgf;cMHYXnOmW{tLuKf8kZEYE^v#Qx zCTsjvE9(~{3>Mr(Hi4y%Vo+mk`rK)Fn1tsyCA1(;u3e$6YhcCMq%+cDnOF&=Zr)1c z=-{R$;b2T>oAgk^9(O#VczK~0;Qm`qWw;RQeBzqeZ0%!!PYV}v5~~2&(cPc2vSdRs zzQ;6{2`RA@9uDE^VWa?f^i1<%`WlZeVEN7&5E zKNEidSDET&6;Vm|K67(X!a@mLBDUJ7>QYP15oF>mwB}`;W=KeQcom;nP=KwxwRBXY zh-LnA|L=YG-%>4R+qaBk;=(Aa9&w@q{`(~z?*b_^lmb<=*!QUT~Z3@(|Ur581F+IH9@HzDqf zS0L?KzVmFJgD0#V!p(aXqExo%dN=i%_;oQDt?|3jKg{ImmFxS>E}c=?X93)zIW+ox z_d7aJ#E0kA0@>bNITU7u{Igbv@|ddxb=QHe05#PJ$FINbyZIN&{YPUIgMReF;$OlJ zX2|VrA^~lJY==?BzmsiPn5)4fXso);lk+{b0beR5mfl7Zut=-}q}hB7)38EWXG%xF z4Rww9u$vvpAL?BQ^`#p1Pb-z-npFu`r9a#EZovxY1T9_|LpIl&C#_;xh=_%(S*)rI zk!%+D!*=O5LvB=n=Ak2w#<|k`VD@x3Kn<5G9d81*JPfSf++1KNVCN5H)^T! zZ=5KM`7Y;I%lM6#m9LSWiUs!SXsy}J7TZc=ZP z42dfcXYP0|;a9h^Vv6&x3$}a+L1Wi{FBYLxe6mP@N{6bby??&NclwugBp4tk4@Yc| z4zZl12Z_*?o)W^>#k46yRGrw_l0_Oy88>4aKOlP_$l7}E@YNb2oKR5+wy8vBpbW8G zC_&UL9LM%TE*v==lRIg^2Zb(}P73kKMgkIkKb;J$4k4q5NlBqrJ=ag>%a%)ndfH+g z!q5O?>Ik25M*t7D;!Z;Ccq^UgU@A0&1xF)t3?*~WM#I}7FI?;gOQnoBz|Q3fm~Fyt z(Clq>wRB3J{B8&xtFn-N-YrIqOmJuA)%9_-xVq<1hsHe|ee<_zU!B!Ui z7`=E0S^*ZkBra@&c?w531QDjJ$y;G`&4414TIs6Rfo>z~6`hJbrcM7`4X`ZH_3^43 z*=1SGINqWgkx50W9St=+(o=XE-3pr?;Ebnj6}7Dx%5U2-h*xm+dL;lu zFwYd2QVcR9xZq7^l?M@(pT9o)GlBzi;jk%!vhuB}V!>@PO=lElex>u0)0pA~eq*QO&a|0I1A9lsFZ zYL7vQ-Z)sfu4Ogsm+H8WR67{HjrNns3ZL38hvJ61BBwV?+gUG8hHliwb zO`vGl=HR35du0$)l32mC-L*Pwj3WdCrzaVv}J^XT0^%Pd}G{ zu*55cgl=O$&vO~n9s3a_mSiL_HHw@4G|#flo-6fyVs>&gJ-cMKxg&&741Pdisot>6 zuXB;}cQ8g%Cm^daU}nQvlgFV%+|vT6l;FTx^tNPNVPpms}|~ zvF*Oi@%s5ynDBBre*nH($YXg5TlU7AC?f~l7VLzeu^VMG45W&I1MaBJKog4eBou>K z#ZuSwk=f9CA@a61_P$8K*bg=~>zZY%x?#-vk3{ED{9|bv8mcLG9d4*z+B|$LiQ5i7 zg4n!=g64`g63-g?vJiPJ_r<9YvJqNf85Fh{5_5!&6cwa)b1{u^3BDphS@f}3ykFrI zx#Ol?_SrsTv^lvos92O%plstmVgHR^<-X}=f({oazw** zN6Y}eTN8`oa1NJwu5NxhQUwBNpg3&-{IXm_<0>m0cO*Am^BQkffuEUAKdK~L%?B*B z`QSb2$lw&;DsH}d{po2Ras^#MZT^s81UTm4%lis-9Y)>IEGHqseVYpj2h4W&q-R8r zfGuHEnkA!wJ;9)~2MEKKYT2DD5wLQ}6?UGnS9eNqvfR{)%u_09#&YRcSAe^p_fRZK z_xY@V({bwH6nRPrY^Ft23|H5GdJwqFIC+jj4g}@15s89u5r&+cI+nMP^nnnO>9~}b z;T>PWd{{*0YXfUb^avZ_%fHlHk(xAn1u!KI_)LDO=ej5;i%mJxw0faOKFu*xPLFP0 zNns#(6tC89W`q^o8>E%t=kb^5*i(-C`}&vekqUDUi4GwBoeO@xTbDmUzNGIxH@1 zWy0cSSEtreGTM|$6l#YE5ZTUvQ*{{OC+6(T@EA92VM;DQ&=nB@8G8rNW$=*Dl@iFp$t(BsAqxE|PHEeG7e`3z0ty<#;e zW7c**BvDgo`>#CD-{sR~AqwK@qhr~=@tNyokck6bhsVYzX%?PGDRE(koOk;LnlPcO z(wxv54HVd1k$H6_mhy$GW?;jK@mm-ehYJv}@rR>nG$XMKVWA&`OB5ZIS4e(T5EBJ4 zf%law!Q+H!vtfRknR*(jZ8&J7YiR~N$H96+bAUWJCzO$nZ}oY#cK11I!AO+=P%Jba z6PfwpmtBHXM}?ki1@wnBQRE4tMecSBO-x_bFA{YCfWOwix3 z_-aa%@A0}SCnAOh(jf14%XGHU-FuL8;@fn zJdzRJ4irjf*i7xi>PnpVi;oQK$@WZQ$5$elPADjQB5JJcrmA=i<8SDL2LCY!0a*|! zR%!juKRr8yx{I#u+6@Me{VIsuE?};37!)*&!$`R6zxHI$Zg4@-+EbwK$fvvdLr!b( zHTr(m8Z2&`7OR0gOa#f(tO;x!p5gQIX(@ZT8sb}+G~#Y zO&FX}BdlM$rT;iMh5r#`f;QqE=T^Kne#-M(rVrO|qB4Kcc{oku33sn;yCRx=)9Tvj zTc|?9&Z5&Seo&)m7)(|l$_NFzM6>ZI6BF~ZH+-+vc!hR-^hyw+Lx&4AXL6{q;XF)6 zg+qgW(8e%km74}DZEh&2J33oz35-rvL7H6B(NzNJzV=~2+cARaZ<4~qkXbz_GYpKd zmrWRP_akrkv~~xh&vfhq;F)aj++%AF&}CA4B~Jq&s_lhB@aRW+>Ezr}QJ9g#ig$(l zaQmbvCd`g_8e9S>g6UK0twmn{0;E(em+(VH@CXGk+HjSrvnlGDk}7_;pG_IEybui& zc#-^#f@@?kW50ka-eB8TRn5wTilN!kVwTSC8S8Bt4fGPUL3H4n%^uqRK4w240E->` zeIQvb*6rLw>_DYz>DX7TDm4d%MMIqc?}sK-vi2}j!%8Otp@n5AI!r^eU(_xE{O)3^ zd2sD;jzm5(TrVA=6{Hqe=Z}w7lee^j+N{-ddM*;S#i=|mXb@bJ3LILM1#2=f+2Gc- zVZ90@CV?+_@nEQ2Khezc5m4Mx2GS8wJn$|gV#l&{7)C(z4W?+?Du(kMuhdx4LP=7z zcaUi<8W?jWu@Ed{G~W{`V9W%|uy%FGhi5lh8me0$!BQ$JKJXSaf*Y3ymS2X*`7n#F@(4{`Z3Hiv}eq%q1%x(v+A2&56GJfNX%XrxUnOcBCKVPNsGXb0Y45T0%(Vx}ZKC3Dhb`E>Csm(VoT0#-~X3!h>h3vu`JB zwz9m8h@`bHKQx-VWoxMX61BmI-|Kukqu%IOdh3VwM=T7|8{FTOI&B3*A zX#+VnfJ22MQ@X`?pX-euOPLxH?!Ws8-(X!s8^;Vsgz@mX;J>^pZP=%$LUeg3U|Fw;oXu0f{c& z)5ZnRW8_id8B1=hP+5klJ_rEnC%`aaG!V0DCOOe_k)seGD@QbZA)VyzS`)ZCnO23v zAz8w=wN?wiE-xKuZ=nMO2j7c{Vab4?ZU;@qissdUL>?D7>!x1bim*}qpi{jLJ`HIa z@#}YMK}?3{;US%SS15jyGIO3&{szXs%|;%w@EqHVV`rZWgcC8b4+JSDlWeu#JP=$4 zeMQo9x!E@VFURuawew1m@!+{-np+@e-4mw>%Q zodyxY-=ZIo4O)q9`b=di@EN4g1Ez7wQB{CmBEQT!j3(fZzs?6`;t%Q@@FQaK|DtVC zk8b7%g0^|oGVgQ5s`aoz4|x zA24T$(ya;=g~n-yz&4LF4K$Kk4GfzBqbeKnDU?Bf8*#wfD6ShwWw4M+?DN_9Bw!dp z6F_kMYxDcweg%b9AlwcF02boaZ!C9l9z+xODc444D3jLJwFBK^>8$oFf2dTbO+(3o zt#*rFlY2wGZ54Z6ZV`hA*Aqg`vYSStrHXamt@aie4r%sE!8K`qQ9%)LvZ;RHLh8kd zE1U9AZV6WigG!YH-Pui1=2T1HkWxzF$aD@_JpNfJ!cNU};7EUWR!Y0b!j){aN^hed zR#&?LehlTEHp2g)?v$q?)Xw6=tPPDOa}=7HQb&-;;NOmVvKkA@{y8d_nC(^=)aJZm zj$?LLMdi#b9 z{Vl>yYui!dHJUa7+LhgYzie_i-&66r05gX{biaXv#nMxD)K>)#HD-RaLP}#Y5eMlj984V*px!aH z+Z~xd2r|$2J>JttOC#$jn*)AY&%3N^QRc}p!}9rkH+;C`l)643S2H@y1qkdBj^^0F zxmreQ%43_SL|ZbdcQGxYfc&*>YvYoc8q;M3K4W1u-p|h=W7ejF!Qt&vMagV1yhL-6 zs|^toh3&v8000W`CtT8!FtIGnBUKQ<`NE!i=Xy5ev)3k{0x`yH-z2}NsP?eE7^JR{pszGux_3UQT7c(m;ou-V9 zb|D7#nK$1G|uKY^;8#@9!DTadoe|>WyiPY7!{tGUvVhsRrIyHHAUr6a%=54AWJj!0z5#@c;ebX;=P;SD zHutVK$FHQ>?5OG$Z9iUwg1NAZsx~(pE4qx8*!EGS>^Fz-E4L|z1UeGA$_a;;eCO4JU` zR31BiVeX30LBMGKIo8p{Nv{|otU_=Xu(hQA-H`_j;e5Z1DSz^>ZZH4-dcBAjxD2+O zV;V{AqW)yL6!ULW-lv%MH=Xt8RC0|>F5Hg7bfKro=ui14-yBJXKnV$1FMH*+KW%hw zwC=g|L}n|m;KSpcz4*$7CE>89*xSIy`Eg0t`*((v-qo~(YE)GBTe?kwMoDKyENCj{ zZ?&=*^Ez;K&^*CpLYDBh86=tn{M?xZT0%u#2X^LvuFVv`N35?=SX+8CzdDydKs(c3 zj){r6zhoDEm)W0seEY-^$r>U>Qxjq{IKm7WK9`eo*8YL#ciWd9f)|oeu1vhtRY73m|23O9KjV3&Q;a-^itkKwe1cd6FOz zz5W-#O`KCRX!7D#{YH|q#z<7>@f5Fd-sv&m4^VNCcjgy16%Nm;2+0bhO9C{x&+Ni7 zLAWDd;zd==z-OXd_Zg#f{H*m%H?Qg+dXC%KFEwBTUbC}X9D5zhn9Dy12|%ww5yj(5=7iP10a4SXK=a z&JDpQhJEA#Wa?3VcBn}zyO0}<|50eVl?@CcYCnuju_AW$=T-a2Edt@gAcSfy=?k5% zKeN^sO|u%sTQ~A+{3NDF;VT}pWM~~gNuNjwfy(SqXG*siSmut8evQci5&9t0qF3FK zK;CHbmfd-}MOf|)Q>5nU@#&%hjI+W}gkZ6vb(VrBEwXcZiIMIBgVnu@7=%0C3CxGb zA+WeprX+s{rwH+Qco(c?uyINf)>tINQ(?(%2|HLqH=Ig0GPi}fz!bNYo^F0-9i0=C zp3|yM&5Hi1Op9Vx(>baa`4@nkWL#jN>LXSdAa=f?A0nWc5Qs1Go4&&-PN=^rltK=C z<(ouo3MBe!5`WuqfrA)D(FGiI!$c#5YPdb6YvWF!v&x&YfS#v;FDkBDuV(j5& zQysN7LX{jJx=6C4{7p$}DwvM;ZGGs$xVD$!6TI@H{ z$8m5rZu&ie!ft&77FM=2i5F0E7&Amup?EeIqjbWVna*r1W<{N@Wt@K;GWY55j?U8H zBl4F5GYLD%>SaR?8aafhnb+asmn{`lM!gHkRMbMmy(wMuX|rSvTb&8$C-}mpjb}*nq<~D>wycC}b)WtQp0omR%X5s#7H-g63!$pJWy_&V14=>@53)@Wi(TS4 z>KzX=J=`uT^0c91L()0~xXG0vHl%aXYfm8pPlUreEl6=*t+eHwJ?sQ{Ww{LD#`~P- zx>s&=v@!Tl7#&b&+llfR74k=2*%T87${Rb|JAT3XA?_rDl@eANV}jG zo?uZ?Fo(Bs`oT6Zgx*DCT3iy3AIY|9O>kFZ3uLdmJo()EUvEfM`!%8L_7D+G_4*qE4lXeH@+OKc_E}rq99V`2NFug2p6p`|5@9M@80XrxL7ri3>Uj-TqKCt`0 z0M$IJ&}mj@x2hfjl>x8gcWgRqhf?2kcGLY7A+k=y$vf3y`EqG^p-979(@bT4dekeW zIO3!1J!dZjGpa5Jni@Yr40Fzcx$BJrR(=e`5UNp|)w(x8^7}lG&pPgt`T4+erHiR& zLPt*&s#MhH<6|n%J(jfAlDpH^Z8Zf000W{|Hz2-JZ=;tZwOn(~-m}}2bq#HKv++c} zp+=tQ;@BVz2TbRDdpkhl@bn?y(rCV85BWBvP&QUkR)*7-Cv zKy_-*yk*Av58O9h?~L`g-2RV&x$jA_9$!`I!dB|-8LtEF-9Fv9=zYnYA>an~o4GUV z)1B|>1*|4so9H}o^Wm@GBCEcMMARqChs4#0;83U5Sinqe97w>-LDDpEP>de>NxP(} z-APkf#?S3rN^!TuIwewfELS=v$23Hxl{mZ>Sm*~hth4{xGuSaw$!A>P5cR(>hSYZq z`b;7F4i%$USL-M8*q1hffQ;5`_ZNV2q4j}F&hRk&Ei{7~M4w;fOoJP`nbmF7(b%kh zmjjFSM-3YH+bP3efatBV<8y;Q>Y3Qp4O%l*POvh9iTs1?k?4JP>i zi@NuYYO34%Mw8G21PHxD=q>bKl+b(c2uPLQK@?P4Kzi>W9i&P}KtQVWUZg032qL`; zh&(sy<8wUcyx;xaG45YCV~?G+=K9UK%3gD2X6?P_{NSI#t9WdVyd(lj#V)*&LOI{W zA1a}wPrxpBq1AeQi62&zC{*{Bq$H^?4aBROA=mPu^C|NN1)v*LJ3rW}60HL)qhQAsq6cqwNPSOp_|vF ziPL(V*n6EUIJ8vF;qHyhhAPz^y8QLaO?cmmvbg`mh*JcSU6FQUBo~DUMt%6}%;+s! z2Rn^>My{qC?C7mIX&veWR*{~U`$$$S=1Lj zyBHmvZ5}GN+cU1u%s_rjtJdESl(^?oGqW!IY=?ej@cdJrnjTqI+dhwZ@!oe3=x}t>~I4q>lzBy4Ix&Y z3GdeYaE!UiM}E!&TAwe>sSn%`kVhO4Q`Vo4sz_O8A9EjGhZWF#aYo z>4M7~zmGW*JVbID;?WW31L;Cg2EH8PgRZc1m-+V}<{3 zTtz>5k7Q~q@%eZl2Nqq8oH$YJj%)X6h*6#hSt{(n)9CjQm# zU$EXW1@Avt>VBtx4cRZu@yqLfmnrS{;a@UOCcwSFxJJ1TezT+Sg}*Kps{L<%f;%4l zyAUbwU*LY4Kj`ewr>^DH{sPIVRIF~=WF4M_3t(C`oN)_=SH#r4P4`f(MH2JlWs+W#5WABj1slYfN# zp54deA}13f%UV(=Q6w`HG~OuK!JjRcmYk3hL=k_@Q&i&TQU6jMuYrJF0H7U9^OGX^ z7vOgpDu2{y8Td!`7tf#P0%8RP{&jXYDqnVUs)e$xfc`W8h@aSZa9(A{* zL`1rqRR3)bW# zbitj8*W>V$;p-oh694N7fz-j3)Cq>v*Kvp-j_BUGV9N->0@HK6Jj$1gg=qLwf&DJ@P|$f{8-&id4G&UZ7<}y7s5A^cY;!ik@Fv1 zqyKdF2jIss@>_-7+SlWyejtC*$Ji$_u1|(VQQ5>_l%MB=T)tk2K(tsyigVgDuhKUh%NUW$JcIR5Y1|Hl8KjDUZ0!B7*u`~L*{Pda2~g0107 ziYd0`B=mpLw!hioHWMqyBk;rqC~DvGWR!X47yXab=@EpoR>44& zFFR={D%j9F(QB!rivtSta+y$bJbHcFB58*WdV}|p7&x~2Q7N&25JP7= zE|24#@Wtb)%WA)@mV+r?dMyfMGz|>7l7|MC@Lp0Kyu@pgU~;ypbcn6?d^^o|zif|$ zGM@KjDKd}1iQ70}2s32(s~&o{%J$e)Tk&J2CzSmZI4#>T6~!@Orb52WKEpQ;m8Ns> z>Eu3D4m3R|cfTZy!*UH=>x0axosDOu0Qs6Jo425H3$@MSFq)TA$)m?(6h_=VjLf)a zTCVDNdh$5Q&qHUU_(-k!*sunlB|bB-dIO}{;cZrw<&+|}7+G>2s@d2RzV0Y}o!-bx zUl~ueAEOiNt0?RPg_{deRbYSoQqfvOSwucy^sb2L+75E-d)gvN-x%tC?xu-3{^ZqW zS5)*#5EvB6+3A>|Z-_(F*s5DtPTu&i){MX)471o5Oj#Q!$pk7^u5e#W=RdkB=-Dp! z=qcUmqj8?DMQ+|NfAC`KjW4+td1a%9Wo!F>sm++F%0(L4cV*!GD=q@2)gpU~JQEi`Y@GmKt!co>AerwJN>EwG1A@wMpAe8Htn*M{8I8Sppti`D{340uer~=00Qw zYydO!D9HuRmqH5B@3D<7v+6#(kt+gDUGK(>6D0AGK6p=gd^(g%5v$jIb#6Z-19=p1 z7=vYw1LAg0T$CJ0vzme^e>?kZYk4Pv$TEV+@i7yW9*xvd^w2@zL7US=Pzcn<_dbHp zjexwYv??(&u-)2ebM>8NKt*?IW{EjF9=|55B(Ud)Uzo<%d>fOy+`bqpuQemCTCYBWH?M$j@xCC^8y}a%OP^YZQZ!G{HViY6M;6F1(bUwa{w`|O9E>1{} z0t>R&zaO2kVx8h*R=f@d1CW;Kv*|DMrjc}UH+kE`!9#TZv9b`vg32D|5Xktey&;oe zz_U9FwtR1=1@YJ<7=j~@p+{7#Q0nDky}qyHnpPuSOpI^QCANa1vo%-zG-0wHKU1mT{;KRaTbhFbNGGT2+=*#Q(4vWoSa+4bh+Ao1CLj;@YG2^SMdAiUA^wZRm=~ve0QP+b*t8f z0NNTaWnSMqbLWi=dV-064x_0>w0QQs@&v9zc?nNYEx=^zbgJxmX495g<)=kUjEKt= zy+0=@Nsi!i3Te!GZuoWl5x0LU80t3lRlSl`@!fq})ZD#6j+I{0tLHFGaSW#(C-Wl!*pB5rLhT<+q>vK-j1j zf#6=T^42$PooagKJ?F*YR989_cT*IHZ4)1!9J;|)k(%@o@Bu;#D1uXcdo>r$SNaw- zj0`C+aI|@AR&zdm!euJ@<7J1RVVF%#q6u3z?cE+syr5%)!d64+$?mab=`>m$Jm9@= zN)^rEwVCz+Y`-W*2;0`Bt#Zbz-hlz1(Vo4yt}~0KQ{*_c1lBz!zAYlT*=LA7Z7ltJ zq_8KC0h$Q4b+omHA`Dj(>EK{(kK;JKA%&tiy^k2d(&IgU0mgj9gEVC)5~DxH+$9@o zR(y9?$VMV}L)@>q9Y(r*p$6@71P=QDi>b?6hOp;yv9v@=l1NuK5-3gPp`ZnhTx;R_i2LRG6obAEY5Rpy)+W8=?1rf?yhK`!7OQLJQ5i8!>0kr>gyq5W!rlwqhx9ldFCNiSJkVW3Tl@G# zP+p;uo@44o-rCyJ?isGTmt&S}*d%Lwb;voSkpUU_2_BMjtCq8}-f4{DtN(s!ofcF> zJA`^Ot3gpXVBZku?O%Y^^B^58!y>@;jG@G<7wXkc9U#=s1b(>KW}PB}`@QkI6W7Eb zX7!f=lMsr)ZZfZP0>}I>%kN(fDT|i{R|FrrEC*i5(bK5krIn6=%e0=#;?2C-NflYc z77DuP5uN{(F@wQi4eN*kNffPok&pNOSvKYD`sR=AV zP`o$R16|+Xb^L+|q1PtNA86Lk#)1m-b|OL<9RPsa<=jFjBjtm#7BlN+KC+z0(Mgz1wTX;lVqNw>(*eCu3y8@Cu%MmBRoJcyR1PI6SV-+n|8M^XKK+=+J= ziUeb?9_rm2Kx`G^w#P<+!%=a}pg#U%>S&K?1HG&%@c#nz$&KBrxORR*)}aVS#Biyc z=)&fPkX1S?NB%=#)%>CSImdU;BU#TqG+z(0O!6ULbm44 z>^>_B-iH#p+!1xv*XeTFd+x*NPq#+IW`mYx%`l+|GXrsdJ6j$mB zp@~klxGhrVLX@{0yWX@FQQVbyO`?{*+MSi3GhTSN4T|>_2`EqA2za|jY_ZGH_MKng z$xina#Q2&PIy|t-#A0(}JRY^P{Dr7{lJDC~b!)4ThcWN3p~8rR!yi2FvnR2JrYum<|fG?)F#2^rKHuD=cD|1gb=E-0XzrOaz^?^7m5 z0I;Trw*q3*u=k6t^!KJHd(U;iZIXD$SJw9DiNw*3V&Fn!aSgI2^Y~H4nzg4C*(A(= z0h%1dEZ9}}cy!22cb%@XJPslzcZ2vS@+!A-5;pI(=T-oWZr6Mw!T1_?B;QB;Btf1G zD2F+iVE!<)g6{q~@(oB_^ug)H5`ENg@6xNgv(bQeVrU@Gg@+2c6|=iE>xI^PuJ2~2 z4!S?S*`AsWVtWChf8!Y4^wQfbA=dXD1KnQ$LeZ_#x+^j@tN#3j*Y4oHfoHZcax}g- z&w2gJUMSZ+1<^Sy-BGI5v^di-OL)qQaDj$vTzLdnNx>+D<+dFBZom4jhII}cnHxl{ zxqzfdqCnldi;A=+0qzw%JxIW%i?4MA%oTH-w?a@*e#8Fk-{uPuuLyNvWYDg!ywm#z zHhze?)t(UdBjGWq_e4v1uxLu7#`3d7ID_Sh2@H_dJq=dKJz4BGjaUUz$VI+S35zS( zI1&IOds^tHVIi(^y6@+zI4!heT*5mE1ugcbt{JURaUvL^W`zSu9b#%E0M1ztZmGv6 z21%WR--!atPBSC822tT4NNkeb*%jKH5bwba2^Curud}s2B7MhiKJM({%;pCJ z<7|=v`cJBIZaGyM);A?Yf@;V67=Z)#zoQhG%B$3dqZ} zZpu&>KJnPcAF3pFlhfZvdG;nenmG)GGzH@5F2!?{7k?RdPt7Cbb7No4k6N2-@{^b6 zn=g#S?=MWevis6>hh(sN@JZBB-L2GYd|Nakg*@Tmc{oR+k6*>=g**_RtC|m5wLp;?xv*?Rt7 z%MK?bi|!#Yw#n`Hixd=dW$DZx4kkuXU9P>U0NiVnyNjj|u&lju^VT4Hp!VUZ5-RMW z6wNG?QNRVE1EL+}eOv>oUKw)r;=WRk>i}c6=U^2GD*+~R@d}RVjGfgM539?1sA&lK z&Q>~1cczV4cP=1klpzF@Um*e3LAF?fLH8o7Y-=gum}o;c9r%2PB$b6afUG%&7 zBEd#SgfDVj-A56nj0!SB2UiOMbNvnxp&9!%UsC7h?&MjMgiTV0=f^O-l?Faluspth zXh`7}@9`*fAKA6Jw|boyE*+M}#^{(A*mOH-fZ4oZ4^PKlI%sj!&*-hfaP@9va|L!G zGGN|82`O(aoc?5xGFJYjy#|T!@rrE4n9ZT>7D8AV?=>rtHM3rJ8Z4t0gM6zR^CP~h z`BfN)K6O1K-eb3CpP5CRhQQ>80tr@zUzrQ($oME#B*#^6Srt9NCDb48;`s~Ez1nyB zp(m)mZZeW_!?M!k?u^l4m47P~^=bt`u7y3LrB|K-Cp|wXmy3qd^tut<4cR3?FssoZ z-|Bq>mISe?Ont?fwsmmuqbl|4$yaY<&nBO`4n#sLo@-b~L0v>|Uar}rODzX-0_#4! zMjjArt!~xmwVWd>)?YCQ4t}NP0Ehzfsf5G}H`1;Y3ZsWa=E$P;H3tz`Ee_++b*SeB zZWZ>;=}%@nk{pmX4;=Tr~>eCSDRdUsTfdoz2b) z@}Er}+w81{ALK4IOxT*d8=hRAh?@}tVqT3FL zYmFlwle0Vp2iE~l#M!nd;rk8n#Qpjnr>W06Ipg&Q*{bi%liKRIK>$lI!RVSM;aLe= zKxb#Dg^VSe!;aEsta-|bVbZ(jE>dS9aaH!ls(}aY`GHt*rP-uV_Oztzp7JiW-L}Xc z;ZeL?`T~g*(vFqY9frbr+3{H%-a*biWHJ?vgmxrtV!~4<7M-$w zW^!ezBok05R``QiAcNhVj7rW&v@pgXPeH@Sf*^hhyLMMKm%U`7Xif=LKTZYz1l0RL z35i10kGeRNJ=0|~`<8=!c)FF%R~rc#rUEQZF=2*-)H^N__QQ-LjDl1<|p;2KQUguJfy{c-jDu1b1280xc;FwO2rX;4f zreWTb8M_tgMnaQGNW}c6@oV>uGNv_qgkO2k=>32(fU=ojDaRK2fR}vGlt4OV^fVKZ zhYBE)q~IQ#DG|IKl19|=X_?fexE|Jb`uwR#$t5XP5DqFll(|ZK!(=Fh7pT{^D$BKwJBglDJ)xeP2)r-91ej4dYqEIIiijEW&JL}EPlyGbB^fS@^qN#tEi;k!Mw6G2s5pS6 zYLw&{R&sAT5rNCAs<)02f>9n$$WBqWP#5f$AV@6Rs0)q&pxH(4snXrUm<#Hu67WVu zhBU~N5qdRLDH!W1z*sQC85-0o<%hTJd}9|L6ygx|WQzhI2a0or5wiOfSXP>-h!qlG zjB`_&C@*EU`I@uOB+^a1OfIcdwojlT ze74Sjpc+`!7yML^N4A)7fmvt9oQ&59%y`bF#su0{kn26y-hc)>DtYTnp_bBeD4~Rf zPhnru83p+ZXx3u60Xq*_z85PHm!wZDDyS(5&}wWYjIH>E+_G?QkAJC%?KNZ99xX%c zo={RXQ3Jd>7KC=v%%wdX8L|NkM5@cm8;6Csnh3};H#1-asqmGc zB@7@^)gCG|v2qa7Fn?fWWW;PNoMe5-0DY%k66eTA21|4n0(wEMgcx+*n(|qRh`?8J z_WO#b89>ue&W4l2H@Z?bs!DtI=ZD$HANmQkEFnhzC%nwyv z9#eCP!KTK>G&g!nM^2L6#rKR-B1(vaBfq=$aYH6EHpy*6hfk@HG$m6Y?M%&kb#_Kh z0p-Xn(IhMYA27bFMSWyRtM!Vf{UJTlN2}96T~Qa4s4l~KWDdGjc{@3?EdtB?j6s*# z65lXg?Wo)ztuwT#yd!Sn7f38`W!@zL0MLQYKd4oJ_ROMIdCSW;_30*tzE zRj%gF9cnpUur)m*Uh2f&yNiDQ|FNLS9p@Rii|7QGWmww)W0CphV#A6jSh&#a?8Q~h z7jC?P^y|(ph}|{c{r)lNP2^m@Bbu#)RwTBqR5RJmrOoEcw*xMf4MZ`{fN{*|I1=A! z`igp$bH;Ci9BxD=tt_39c|8i#DrUVM>bK0Zl@a@o^~T=;LE0t^^HMr95fWE$vT;o* zOsbd1EOJ!_;|?&RG54Cd#r41&nSj@7_Cxe$2I7Rn3zrCq$!H)?$v)A+`D6qz^|_8q z@%VIk-7Kdo3qfjt;pc@0x(*4SFVpzg3Il@#%aiX6U#Olu6A$DL2p`8#H6?PrMUwE$ zsR{!qHOu)39uk9WF_{l1m`(tR;;P|oW(93VB)V;dKGL5YIF&o22DREr>E(xu^2ZuvN_bi}+b#ja5Bn(}&?%PoK%vC^swd=#4dXZ9Tuua_ z6`hfvU05Eefft&e?J*CG+$^Tm0NARBACGE(=xYf36s$3Tm(^)MI-yO$2)Spkyxyf$ zCXq=ti<35qQnm_B}rsUk+$!okNpYV`L(^D_b?XZyqc))o>`Ya>{%!f`N7pCkJ zAuEJsG5bP`JIVLDxOtWK)o1#SNB4)1Z+o*A3TB@cRj4@Vf>ah+*Y(DaczfcAz2GZI zzPJn`s_y%4)$rpC4R(_*I7Q=(H3VEQ0F0z@)RjMss7KTWV|+|tX4ht*vS|cuK99d4 z908GA>uVrHOmCE#&3XFlObXty`vf5siHWpLvFi9t_9;5ORZkAkWB5$c2uYwEIFKNZ zAXn1sv@bKc^a2-X7Jx z#dOZNlmZ0OF>Vq!>qcY9EM3tRL4HL}L6)yPB&EClgoGVmI43!3k-PKt7^8jD!y(HS z^{i5NjT}+vLo|CJ_xACAza;+0le@y5os#zPN>a7ox$KAX^dV^4L6BKNwxit_KS z7uJEI@@T1ujT*h`ASv&gwC3x~GkLR*t?1ACzeW96DnqwnL$>gTfrW-O@7{W1S?#E} z$@!P~ofu9ICxVrl@a%_Q5iO5JUvh4>#P=vAPw1|-vb~Sx@P>IM2aa*-m+O|OrvCR= z_8WqaLg7(1%%WGf#k#Ky-+wK;)mhoO+nMl5qPOWZq#sHmn&9)Q-;VJh&paUGV|54& zCix^(bNzGuOHUq3`{zYPmcn#eHx*kW>fQXhA@S*P;U#`|v;Eoji)|0IFUtiqn3_h)2CF)erLQ~18Kiy#&Ox);ho4xmre>d=j zMcQ}HI1iF{eYJ2(OXI0Z(|3fkA9o3(8o+* zs5rlm3eW8s#tu)N9y3Ba;$05ER=8s5PFbtHX$P{ytn|BhvH;aA07slqPvT-af&np) zwce-rG=F{(%Zu+W1S)`w6}CND4vL;Ce7o<~-W%|kDiL$B>w!gFzXDg%IPHhE^x5P) z6DeqjfiL|f2h2L?%?u+Yf&1*^?}-VWCJVbj=v9b}5L$l|QAs3cb5a@t(U4?Wt{c@xu;W>_#t1?5chBSLo*a0f(buNSq?x0 z%X#Re4o#6r#-_mGyw?xx%fvTX@oV{ee{8YIowAgt;B$ z31PT6EYu(xSTJ7jB#w@mXAwJtmCttq)3uqa(A7PMI88Z(Ky}D7l@D+6&|KT4-jOJ> zQe=$k43m7yXmI72L}LhoXJ0&|aZZ;jBHKBQQJ&n%3R%dF#!5L5JZck;gg7kvCykt>{@D}n8UceeyuXE$^8aN zcf(yaIren0x8T0V7W%^zMPtf@SZL>*ZkS1tqI!VD6L^i?gFV|BH!^NA@e7 zf(Lggzt+Tjd;8|`&id{DbwSySr)mBKd$Q_@LtN3+lZ?y$d=x`>WQHHDt z^ELwrV8?hhl|ByiZII#NX)}#nV-Zvc!idleQwAw=CpNc{zLsNzH{uQAo1;R?^PCnWg4yVf|{hS!~8VF=*+&PjcLLNK^xZ>Kf}?8$_3$a7#1nE11(@oUu6~as`=bw& zt*8inMgXTaj3s8GSPC9lp8d!A+(1lrrY@s!^~lqFW8*A@E68~%1V~67dG=zyD=9*S zP*Uhfow@pxfqy4H1j7kwD=)94Lq!<`WNhB?t6Z%fvO;5y(wYr!U->KvN;SB6Chuq9~cmj1_&N=`e9xGr}5yI|k(H zvl!9`OL^(U`FDnx$QTNx>pgG;goMWGG|{())deG`@-E_=oQ*@VMYEKu2YC>ptoBcZ zb`=oI(fPv4WenA!$r2)Srn*ROl$*{h;iKi-BbcL0=`{n$K(r2#B8VGEFPRmCQaroeC8zG;s3N4c? zZ?dOb;M!nW<-Ya501o(YY*Z*4%I60OVp8QL)Vcs)7R7HE7ATH0_ZvmXX&zZ_^uB}to8s~(wxqEHq%g!jPLmL zVq_Ljms>W7T9Pr8Ck2%`VCo27_>1@Ttj+moX?4G+xr;VIelo($dLUdsxOnG{)3><2 zrMn8E_lfDGcjX@8=dyxc!jhPhQfMz7_R!-P=GlFBkKX|^Sv7{E37nO+mC>D5kwL7K zP`FqY*=QFA6<~O*%wByi2c7`$Zc}zq;HPphFcdPo<3?TV*1@8PT!=IYZ~xo8opD! zFN=D`A3sWL`B_j}h^IkC#uPL0F&$3hSZShPpQ{wIps1m632(x9WZ&T~KK%YqWMzWSatiLgm$ z0V%NubyE)LHCTX22)zGb!dy6F(_jtvE^ZYHxB?MEw?-?YMuSxG81~bpZ^j!*02t8z^s$@^d?x#Owhr)4S;<;R-0%j7K z5o2veP2)sr{QMF(tl=JoZW5x(%LBp?4~s6h>Y%n6#yAPuuG{PJtvqI}UK=CnJZA0N za=Ie9wOfz>&bN9^DVj(^&^{YollVQw*~v-!rU%3J@1}TY@Ht{i7NEZ1BC3a8!vRi* z-3Oy{B$GxeQj%h_qWiLcmD}gY4(i6h-~-hshe8r<>$TZsLy%9M9||#EaftgQVtBoJ zQR(-^ns{E}sV@#1Wtf|9z{{g}7+7$@@yA2^+PDgaaDtx-(c=aICbp?inh$xw2!$Tr3a+nh1{A?H$-){<-&% zXN9N$`Z@rKhMU`FUCh&>RRpvG3OCM?E1|bXEc!ai%0e)^l(uf8xzF%Oy-8xZpZT?t z?{U9_dq&UR)L9~+kPx1lHXOjlzX>cREAtkWIsg3o-xS_`nbbas;wOv0|6nCAkO%oo zqyrQw&Fdy<6;bQ-#e7FgU)B5J4{;&DRt9NK9-f5)-l>&MXx)5)rB0#F_iq4$$*$TV zCnf*;Dr@_7=LLOP03{Iu%^;bPe zNAC@`KB__O-=p@gl)_4&SVGw7%!=|?x|6+;;_c}L+R6Mx9&Cs3!k#WLn&r2EgNhKN zM*M@xYs?RYK04wcV+%+-o@U*v-iZ71rFUL?oJ&+N%W4)C+WM$60I^{@mUbNkyt@uD z_;_LCr`d&Fwb&kND*hsyNClE;Vd)8MRrzRKVwD%~%4MqbGZhn6)BN!)y%4=)=OOm{89SYiE=(Rnj<&_nF))DVvZ}Y+2DT z^#R;=8{%Q*R#&tlqn2S1u`QvYaN-ca+_8#tlHF);q={iuB^lPx^+I{2du^&-5%U?a z@>U4fU0WH9?wj(w&TnLxvahzrp7b%;V_ltZ*4&|#qQ;asOgnh@uwA|-t+g)7?Za6m z%R4srZyFC>@EO5RGgYz9!OZB4baouO=Euk3o^Y+ODjzu~IaNktug} zJ{r^@pd1kwU^L)Tt=C-z%miJs~D9feyk(-6U z+%TRzw$Q937!L-F%3WdJL_abD@1Y1z+V=}mHy?`7sMAj!W!396MWx@XnV6z%00IdPOC)k_WLdi zsSL6LpnrmWAuI3meEIjfj-hDOL-0UGM<8OhkP>7)4`3%ZI;?S_Yhyc{*^+)qC!6xB z%OZwIKj~Ys^~r5a&;xyBh9Y+db{I8Cov-W_5@oF9Zl?JaVQ=Bb>j?$tW$GJ*$hq2= zfaDOXX9`wVjAzd={qlphp)A;$eTq$f1*DJ3$lQ%Ob(1YP`CL{C8bh(_U#h6n*n5`n ztyi+ghB&0=6EU$7ZaqhQVziY*H&|D_yN!ws1Zji=no~=f1oUJr_c-!5t<{6Aa%P?( z3N3NCFj;CW%mUF^r1Z~Y86BxE*O7zCMY6_q7PhoBIYI~!+U4ztIc|UiE)BrW7!})& z>SbLH zJ82V|vN%ki65D(`Z;;$-hIa#NCkr7iDCT1t8vVTTmiZHQ*EsDEwoYf?MEuybrP(U$ z8f~a#^ipYn_24~genPrN=<_5jZ2g1}rj#sk0)`aeOse2F)9-uGX-^dGo7DK&7bC4b z<>|{KDZyk}3@fZ~KWj02RVxY)e)j2y+$zv_G)d#~{PHx8xx)d@K)ol~FWvY|HKK&T zXz0B^Ha{X)OM2$5yHB(!U*5X8g=BWX2tp%yUubFDNj2UeKM1`|-|SjZnZ3*B9S2txBkYRmF-aUed?-U~ zYiAZrWca4rOe$8NyndK${nkTjV$w=PI$tNMSPvaL0+*kxcmWQF_LuYG3spLHu#Tl^ zx>~s5Fz_??eRLk=kcy178OvwvG*+G@*TUFSGY4*{Hp=cQ_fzn5tWq<|j=NLq7-BpR z;(#?bE@2UHj$0LW0~9JvSUiFiQr!+#z2s(u(t2&b=z$%*y}X(oyXC^Z5M3F3#m$LS zWiYt+og)i^J{VNasMt6Uom5YNZWtJI5jUldshJmbV0V+dn7w;TNV}Ewk;m#za$3-v z68CB9di2`lS5e#~8TV)fBuLZL_2F5c6N{|)y3Clz_M7KpQI72Q!=B6?ElUx3C^Il| zw9y5rhCTpDfGs7s@eNGRIz{nq(t2k`uVowqhDc<%BfP#Sbxz#v0`LV#H0rRV4(hU( zV4F1C&7~%Lx5!qaBE|viGK>5UVIpYsFBocgLW~AWy_W2ZB%TpoVc>fr2)GJJx{gx< z5aHv5!z(b$$#lMDaDEI|>s&$Ckj-cb1Am9q0S^5rgKq4p&Bj;;- zvbkBuU-%FZd+jEp@up&Ch08in2u9C_xaku0jYZlt8TSlCo&~U@ z^@LCpG5iIq|t*;cj`m=+2sLL6BH ztI5EM+885KO3k)-_0Tp47y%{;lL+HP_?%eO+v0YDisCq6EOvDE`0VW-mbcn*)+af1X=OIurn6v?s+eVATBzW~%;jz%FESUoe$;#mZ|$0PHH5)o`1p9=&nk zJezN|-c6iNl{07VDav(F6}r43z*rqK1%QT8)k%Tcl6xGgC~70Fb8C|v-T(`q5ie|M5a$uAdyI3ZScnHg zqvP%z$p@L=N$-u4HMM?I%u4Rcg&L0Ap;s3>Y~_(gA&>R*`cUKXBB(TNs9lYw&s){_ zSgwz@kHNTuGX#nqn$Ib7N2F9`bS}A#&<46Y?QE@q&YliQq|s^2Gz;~V78n<>(y_!X zvgBzZcsECEOLpqXlKLuprGvoav|7sE-Bj@Pr&Thj9ULt+w8P%+JK}bp%=ahJfnT$a zcvBaz*3m{kL8TCFX0zNB2}B?wvJBcCoOVe5N(4eglviHtNg+Eo2@Nr{U-=^`fn`XK z?sRK7=bA&IZUyR7_3FkfmstyoYTe!X1*0KoSxipC)3#AQ2-`?DBE^X&s&~*Y59M=- zq=&_XWeG@*#{1N~H{_Y>;L7f ztekV!v-fZB=kmAvLWy2-Qpi1E;ghU(`3pC9xZLQXC{{>Lv}ws5F- z`n_Db<8*7$W1o4wbT^XAk<>dib^5DDgoJOB2TFwieNHx{0lG}n6*`~wx5N3N^s-?j zHL>*-EE+8{+G^2omY!k*3&5c3Z3~pwota ztk$HY>W9A0t{@zj=}SA4RK3B|4BDHI=vb^-2H5vMu07RDQ>!DGqWh#%GAnWBVa`00 zFVwuEIw{viJL~};Pr+eK(nHNLS&~M#N20O-_nKZXNc1{$d4=0jXMg3{6G5%J&J|IZ z347cm)@cZ!I4N^gSR1YEHr;BIz?s>j&e+r=;X)MrndEk4ti+0Gl_VN7(eQxltUm1P{wr&{ z*G+UuWro~mk8{VS+0)r+60*xcpRtM1+<-d38_B^^KK=MWO(m+X|Fz{Rs|)H2{M*q_Y&)7Y$xqw#9ML%l- zSj=yYW<#uj2?iv-E+_`<+TA$@iA~^iLk4zPDMckYfvM8UF_xR1HAFlXG-;N~RBKkc z-lu~;nc|Jg@Q%`-P8lhVza9~u?5nf2mwrOH_|en zKki%*3i}8NwgXLN>+bX!b@K8S3j~^Y&giFh8r#n5%evt5A+&4~C1&h?8MfaaZJ;!9KR?qU;bqzC_8FXdEXM1XDpB zcrDViKT#^^QUeomqlyv8%>5QvHnIE{$+zdDY9SmG7nO~BJ3Jh=7u3Ur5*)TaK*CuT&w!gL-KCIaiu9^g53hu_khC@N|&4fc! zG9v3WCg&Qb&uu;z2Cs^axSDl4H(&KO%}uGf62N=KDcAF4^s+_MdPP4M;ygElj7g}N zE8EQ++Qnv1I8dKWQ{SwO@^T4}qT9}~3}UYs9#lXF0Sp?oI(WRz+q=Lb7`4KoyJkL? zSU%LfR_u6!%9qPObTxhEtxYkoo?c6FACna9M6dF#&thv z$K17+ZgB1@(yy|v-SAT4tBcaq!+LeO(ed3q9jXIcbJ^6S1bV(j$p5+|!-T8XKh4;U zf10rlCoY0RTFY+kR8=&Kj_os=fneNQsP>F)wX_B)C%mpJ-znG{`PYf_?dLRaVWncY1>5qrC)LE82 zKUy2ikN18T!YYp&VOJ;==QK7VgNW?bU0ROz9npsKFr!3ogH=WUTt%Q)w5*;_Vh7Z8 zw0GY?_3hO1;d>*KjrFm21OvEzLHv0})4&@zUNczlU%)k=Q+s7>6@T1JczG@(;QC*{ zc+p)1ZRj4!OO^CfFbCqM7^ zH+2!(LR&KyZkTqk057pwGpCyH&%DTrok0nTU8#D82ds?X8_EPw3U2H=A}uFo0NqBh zDwVoHPVT0<#K|sDqMFA8FY#D1v}Ob57HC(L7T8EX%-~qB8Ss3;V2*5(hzSK2i0kvm zTyh|{f}?BFHrZHbGkNVy{{r+G)wL2!^lk8sdGBphDaeP(=8tx-a^Ec(PQGC_aD(~} z(c~S_2g(CLuvie<598`G>)Z;&MrV!Vj$s4dj z2Q-4<8}vVfACN}KWvp-%P0FFZt%)>50sh3NAWc^?Mnqi3@s&fJsC)`jMP{+0sZJzW zASU-(hpDi0IgPU+)}wN~29~(|sLD(yk%tZ9&u35|#TTf)2{vY}#`N{&k!j^;IW-IP za|9^+w4FQfw*haUElj#YbFyNu#?~5@`rIWDn5D%kQk?_g0FNp%WkXHQMX93UApeM7 zVcHsr=7KS0W)Jpg3&7rVXtS+|Z3BXIEvS0;S7vDpN2Jg-WRlh1(7c#H+-yU!Lnw&e z*&690&w5i}xWa3K$`+d2FV`hUV)ST92{TFo(Qa52OvLWxSxX5d@ zD#17+%Y8#dGJXM0Q*X5VzG#1I$DB|Fjv^>&j_{Z(W_L#62;z0}HZ^+x3 zQGyli&&e?ZG`!a1`q$WZRV#TUbh0IrlD4`o?{FE@B4vGkls<|sUoj7~UP*7ub3jHF*pABa0>;ftC5q_Puhd~zAs*8NTZK~Si-V+?8 zaD}(F5*6w? z@f}3R4Jv4G3Q*gVgmNIEA<1?W!C3ZV;WX0dy1oUPTUj6K1s8EfKmg5?wP3BpB4>cK zl=J5d7;R(>OVxcyAV($NE>byZ5(=?Vt8+7&RTK@}7|@yGltU80fxTjPl`SQB!bkgH z%P+ZdMgl|@?UZ$6ra8h}-+Nc_$r9jP9w807(x}0t@SkUcD#2Wx%a1T3msfkmC? zPze87uGGaMd(MJ4u4jXbFS;Pz%~tUBGr5pKZsT$CZU|XHf-FzB1S?i^cnS=w1xUHy z@V)=?wuAKfi-0vR=R?8?YsO_h7V{EMT7!LEaVdMK3)}F;{tMk4Gem{$=lSa7(IJJ) z=nq%P*ZS6_{MfvsyAxZ9vKkd}CguNdT`Fc+Mf8I8b~<+TM#GVP|&r1H35w^P^{Te?0UKhuiIJmu?RyKS6jXQ{U>4(J4EU>#|gSByItK7q@xUg zCX(yCrkNZFs8+T*zw<2iNJz@gN8n&_`zcOl!Bhg4)PcBWEZxCpd$T+6BEd`buR#?wjfi z-8wBH>C%`o_SZE#8*EUw7KSeGS2Qmt7o}Er-Xd4{YC%ym7Mm&_w}^Pq>A3nu zH|~cuycIOF&m2O??eN9KzIu})89XlHkC8FJ_*E9*UctJ0Yxj()K4X@9At{+HrAL7o z{8NA6Qm{-2?55Q+XR}r=Uz%1fbbr(Jp0bA{|>7S@D&TLdp_N#AK$qLva{XB8aAjtY`1hpLVXpX zxs|sJG_EHApz<9NUZ(t(&UiIimD&mO8zTF;x%*pj&83guSw6AbGv-4+d0sISUk>bS zH0B<|mEZ;oF}@}*nzuVuGff?GMn!LDn#_tA(u^R0bF#-ltAqg!i zUeC~-H$=?!rzUJfLyQh(kCi&?Ch|!wKl^XcHRS+ybF;*qkfw^9il|*v00B~J02xQ#O=G@ucm{fQrwk?DU^0Ibb(OwXTb72j%I!*?*t zDmV@N`p46sEyP(d9&9$(&f^7w+{PjG%my)*I#AWs%$k<;RP;UewO*@|r@GAX@t5gO$hb;X!sr}Zur4{DaV<0TS`?vRgg=L8p zm6)ohJ&f@n@%{zmq&7?DwI{Z#92NV(1Ck&Y#apn_Xck<{dwC~i0U2XKM4LsID>w08^ej>W*wbaUgQq^%y+y!sU}>c5 z{iwH*8<{DoJPcq877`Ra?FeE%IYeNV$tj!MS#S7hovlMT@1}79?9r2Q*L)OWwox1h zS8!+1Z0GW#6;Lb|^Lex)7Od!?=AfSty1429I%G$a-0h@GIsNLdBCq6;MkDq) zIg|X5u=3`3x8LVU0s!u3V%B%G%mc|Rx%%^f$(IaZeihkBQ~VOFWt~|ReW1P0NNuf= zt7WB+1=ig0(Qgw(+Tz`kOC_sg^Du7B7Z?cW10V;IIMM&9$kafro_!Q_B+CA5sS(5n z0ii5rS5uLnF9fyr6^syb`Y<^Z6kccdri@0OwWi@msjK&9?iu;^J*~a#654u5AJD>$ z0Kmb7*xs9Y(GIxST^uX#P%9_0X6SuJmd#jdDjWCgkD{EnIA@xLrj%J~mVq5Z!<~|; znyjUm9-k~Jqi|vVjR?pXg5un1D-z$YM>B2t&fAwKKFUd4cuo)sdKUP? zkEU*7h&@!n9Oviu;o#=nTkR{ko%xo}OPlwESkAZr^rLB?^oxuqAsn-fFHnqmE#047 zt!dF{2ypW>c>@0|uQ_2+*!I`~6oNQu4q1%chy&QS6DTNN^A(j%RX(61?e z*vV^&YmY84iWOp$f!2&*92yO)5-&V3s^*oTDtfm@J4`A34y}G0XS_ftEo=l~BtLIs zOb-|oQhF`|EpWL4sxzQRY7cn&1mA4`0|7d-RGZWaX9raNrVB@GGvk6xvOZAg|_rzU0t*XS2f=FMcQIXY> z*NgP6Rcrx78V$Rl#!%c=gvQ0hbzKG|SDUM?Te35C$0o{h(vp(cns39=ZFtdS9DVM? zwv>=&Z3N~$e@sqC0s0`phhf9&*SUu#MF$;qH{z|i3j|}sm7;Tx>MBpzAQd>?V6(%S zaS=tOxN4L_BD%m!;@J}{?8}I`Q9%_GBF(e0>n2_h;MelA%cH}$1dR=DgjyZSye&l~itv?K z#}T(HoxP7*hl0+DJ%0$Zinnm%AbHfU3!}GjPn3ArQ)cQ?NjB#uD&JkFOuE|nv|x7m z^jf3B3O8nrM!GRHlX3dwdKoNk=_uqA$pE*iB@6v5T{Bwk_DH8LGn+fmbxLcgX;XZ5BDH+S*Hv& zSq<+CO*t>rH&#!4`mP$qD}HtTlNDXuz~BzgyA+>*&KJglS62R`bwSj!h;R^#V&^T% zc>Pc3pI2p<(NW9nN!p)^e<lm8#+8WSA+B{<$3HGH2kT1RZ%GJPBMS__lS}ua4-`6P~8h-=; zs+>s%Ikm2;ww)5pUa96H1%T8U9fZ@sMF_B!L8q?D^<=vaO;gO7g#HYM`-arHj!0X{ zC~`4wR*kGKarrpGoh%%LoZLCL;1**M z0+t@azvgoY4Lf5F={_ixbnfaaDYSMyo2ke;SA8gXQNJ_e^s>}&Up~Z4zbv9{jo~iD$R~8Q1RNVgkT%2N`S?-SGu;~w*=|L2 zFCBdZD>PV*2;nI|TbX|KcD;jL&MLC$H>mKVQu=brZp!V-A!z_LVQ?IS1p>vsa+mTA zeNA<4cbtf;gvkIZA7-;?v=D@Eik@FiqQ93vz!p4Q^%6>?$;{?Acb;<=`HH7A$R*Xc z$H1a)$OgPzQb z#1dQ?^WfSFmNnaoq#67~j0`GTf)cNSQ-3{dHOpBLdS39$jfwm;^|#xd%B` zxAHSpUm7wqmTuqyL1#9^m*3Q9)X zw6;=9s_En>qE3XdP*ik4P_!VvIF*d&6HiufP^G6?k#VqaYK2IN=D*oqP~pe+Aiswg zu4MH5E|#85G$5~qFm|QpU*Wl!f%wA7>lgL9q+aUJqSE)=Yt!O(!3+*4GF~Za!qxcN zx12&BjCJX)8*O{{QEv}?kG-Pu5Dus(UT0f|Hxb@IEKwqFF@~c^a=|$zI0!OpEfpKg z#%Mbfr_Jel(vdJaPx!&v@J6P3UL5~3zUJFBs

1Qung&bjy!(!Y!y9tns+bcQ3u8 z#epcIHpTMp2?9(^-yqb_dlXbmi1+<9fzc z@*KGOp`i-Yi zal3=AI4u7e@6-b+QyKrNk{f2U4$#KpkarAvsxt}$hJSCeY%rFfsvAE=1jumHzWL-> zc=2|8_=OR}EkUx%Rk>6}PnR9t1PjQ0W*`|RTqR*)8m&e%x8wqjIz`SWF}+sF=#18O zY#Y08h5Oplwj;FMdW*iLC**v@%S#EL?7VhDUT7{jfBOq)!9;z!L$Keq!r}m|Y~TG1U%8)&zWn-!@}Vyu$nHdrDsd-W zkCFoNHM+!Na$BXcLy=y+^!u!{`fT<1gZNt^e`qNF>HGJTfA9CYX?uk^XsR|RTDA$< z)lN)B>m$)MrHxk6P>uBoI%4Qx#WFZ`26+z;d6fhWSnPbUT;mX z)?Srt)M*%_wQd#Lph!@Vz-7h>rNzWi7df}v3hsLjW^OH zHu>{$OJ=s*56o0idd9I!hHhuN9|HCQu4{+;-X`MZTv>CBVlx=x=KXw-WvZu(H=~7` zw|{ObmcGk*b{*Jc8F@^q^M`i@Qnptddn>xfO&61^3g3eZqvW$D2xO@kjAT?_g zjytF(&Tr^=pw6UN3gWX`38Rl#1-KV?c5ho`D&M6k7i{iU5HRDQ8%|Aj~1qsC07=b4$PGB~dYe*YFLfc)o*z{f8_A$nFT z!Xe@BsYLXcdt9;E=18k?rbKJHT|cifOY1TVuB@v-cvzs6(rT?1S)j#Y`%GJA{^0Ou zCmXw-s4j0F-=oL)+y+?z7*838$vk1<4)=UliY{j8u$@0xw3 zCwqkM#?N$aM{xz5CsRO`F>Bc3(!1lC#;5{%*p_m53D%@Rf?Q@RfT>258Uzs7z;i{gf)zbO3Taj z+6~-ZH;R<%!(X6_bu7|0eXp7JLkQ#lP_aJlN^f@j@`79fW^XW4d@cNg@I&^4!ksgL z#&f#yb+4jcEPW|EW&mJtN%uFZ-)2i7IJ_DXVwY-jRwjt_#a5qh)5 zm2&bf?1#9%{l-R3kXMEtU)Rx$9Mx62IXS<&l57&y?toCw>xVqg*8SYzG|Al9Vg>R$S?#%-*dlzV(G&GPb+AGO_i0OJShTEJ#vpF>PrIe-XK` zQp4)X!O*4+tO-Vw+0O|Ew>07>;!WDFv&lw}nlOlNj$o4%E`0y>AGvt$!|{hvdjpu0 zXd&1bCn8;Uf8M*oh!Z!;i_E+7dA;WG7bh! zo6A>^opT%m>}FRt3&QKu)N5y3nl16)J1(?MFLPH)vWBa(42s}AbXK;qc5ThQwY!=3 zZ8d$$vN*M$1@Bewkeg-IfQxC;fu=%@*IQKR#L&}Jh86j~8Z)f8a1V5YD%aB1S4s`^ZJQ1x7@DffL zj?`XxT>KX>$#tOcYjuiR8qQ0JW|Lv#A(jPW;i>5y2dXp^O6_p+(Vj5)qHD6QWveE(DrU7MI}!bcIu)@1%)Z@E`7ufs z8`yD4e3$pp;Jb!d1#9P?Z#MSre}KKA`ce5)F^1X*Wfp9Nz@{?m;+?W#!cfsoFTZ>hquB#yq)=!qp-3z)2c1*atqCY zG{>h9k*ovbRE#pEa3uvIDgk=8Q~gSj_9eZ8GAmsS16whh=ZH^<&ecUnx@A*6z<1rV7o-#8X56BPm*Bqf7+88NGpwOxmPsahAXW0W*LfG~^2kaqmRmsHQ4RV$^~J=# zReuU{eCGbqwIRLjg9IAsOhgI?IK-ApS6^CAyL;j0z2X)wh{+Y^JW{A%uXNch_4*Lt(b@acw52~1s6G5zg* z@Ln7|2-SHX8m|837v6`TMe6Oj=hc)eBQS%xl4M5O6B9Bp{)f?=P-lT^+pTv%8a;kP zjre&8viQ(zR?w(2^Bo@pt5=?iI3T@t_e0ZIkmF!t;TSd-pEEE_zaJ4f#l*Xo`VRN4 zz`lJZT7dYz`Dubol&r$nkN~&hNnNM@2^-$dogEz2v+HPceI(ASJ2i7jeA9)hVn?)usWR@RzMp@_2(jV zQb%JXJ2-R1mp`(!Ls)Rgt3;!3Rn3xFuVHK75Y(;Vd1XouTKL``2Qbnr<^iXM-gLD8 zsSq}K#H9|*4QXUw&YL;7-*GigK7Q2dMc|;|<#GFa5a2{Q;mK^rg@a(-7gUXWpkM1A z$B{_k5K+W};!wdBp!ogzYjw$`|2MMj--)=&pYxvK(A@RA*EOZ}ABWIHVVo~j)&lO~ zjt0^ngzyL3gA!eGN7f{seBTbt+)m4T`x!C73{rktQTP|I^at?&nQhDZoJoh{><4re zJg-ZM)*kJZHhUtLAe|dTO?~~k4UvMY>Zw$r+%h!Zc)w!6?bg#5T8x;`ozhyfQ@N=e zJS$x*!hvJHMp0&4GszFGmiHI19Wa0{1@qoynq?c1lAuUVr>D4`Kl7d^XI5-*Z{v1l z8>aI1-+%0lA1VR>K!9hza?D`rf@z*32Xv~kb4+n4-^MmrOu&-MrKOaY7txiX)=b#? z9@OGR;n&pY+kYnB_(c{HCPZ#9n**Yg*xGN^Dt;Hp8RRhpSk^;Itz&8jzwIxLj73|x z^B7uK{i+42v(Ahq-e_Hrh{_fUd~jY}K|9-Q_k%uHj?- zte5g>Pn*<^E;7E<+`D5U;hH4PDglf|9x8ITk!fp*kC3utUFUhg**Ax`OdAoo^9apF3u!o)_3hf zGAb-}4z7zlgvP?ruKfz4A)J_ej@7l67dE9ml+qr}*i#dE^J24-kpR<=fru^vHSFZ* zyJTksB|%``wagn~nO8|`-`QapCy9A|jwoi(S`MSgodXrF5*w;)4c^R2h|be@$Viw?}ga|1n9AU#(Bcdy@Gh8K#B_6PKL2 z|08>RBL*59gh~n~j3mwLtvkQoSFD0@$cLxu1~gVnknD)Y%TcENZGca{i?Z|(cJ^j| zZ&|T^{vf{sF*iFpPG9b4D{k~=Y<~fgBgwknd|_P?U8rWgq`1)~6-)qUU*GEkO`S_V z=fsM$Iw8w-!m_Lwa(o+(FPm9`4l8nJ4ZLi|UCF@%GE!)v7TW;`-`-t=NJQjcfY_av7D9J^ zzr3O}!Cd8deHP8OTy=w*KYpvEpSkh8w$H%CVwUnDB&stZW;nBJPwus*qpRlcSw{DI z4&A9FPs*5yuj^1BYZ|OO@Qr^41ChHZ&%)YwZe%_UnhVMJ#~jFcFo8v;*;8GczQ#{B zZ7`m1;ve_%rLJEko`hMdwIGJ`oG`M%S^i|!W?Oq&Xvl2!dFiurgMR@$(BK{-pD^f?Pl>x+w^&;nE}^EY%zRH;YHbo2##Q6_ zgWAw)JEX)g2l~nhF&C07XJVXxOt1M+tGvjso5B$oyG;mW84=*SnA&ajXI071K?CyPmpN+w&6eFB5WBde^|^np^hU zBdnM&KY(zTEOlK4demz0gg3}sA3-sA(>!gH zMB1Wm5k7-{5XH>9C!rKB>oQ!T>s8gE1iW^m+{@N+;;!RnqG2ETWh~MJ=L11!oI3Er z8WJcBYJ8H}+rT+Vc7wycF0j@x-c}I}R1=u`dQb8fae-4qsbzu;=QidwFzrm7P?eYO z2SA?Lu*#=Y9&J2XkB!9$3x;ui!l|o5f-tN#SbW5`HOLI|R!kSflxbYk4%aaG*^{xy74t+D~@A4 zRqFviSo0zoc4?I8(QE_!D!v~cxF2&mYjl#%OI8Fov?yBDYniXk_Km~ks4vZr2W&_t z1r4ksmTdytHfuen?2H<+q$&30&(+u9txO$(LuJh}G13J^?hB6y1%tbOaQbOvNAbVz z1{(xdlOVBEQ4jAOIzM8NeQO^oV|G^=#)GBMCUR+f9EQbj&b(Fd;fr!GlVnU`@0pbm zbOCAT^jAl6Y3_!d2XOZH_8kJ5`9{wHdE?FHS7Xq!OiAZxXqdmCfrV=uc`k{Fhx%2K zLqXTxxh++JpNSc#X<5nCQawXfMh?OP;dEc`(};2z$*Dn9!2i}#=@t89!9;Pwxg4~N z!rZ2Lry6_ZIM}8R1fNnsw$AIQBC$>RQceBO+HeX$XJod9_gE$iJxo^@nRmidg{+op zUdW+`Q@E&&{a)?pbDWGWjP|4xNf&NQJdw{Gb@<5=)8prgqg^q;uyHYcLL_oK&#qZU zW4Wt(CK)~UjB+?i3O!8a$nA2bmI&{LyRoLm;4Vaf5$tkAdu;k>=4HM?MNC3kOaGhvK0c@Dx-XDCk?0d#j=M$CInbUFIC(G@@93S4 zVrQnPKoNO$hpS%zgY)P_8BPUYta>4Y;&CWdW|65gifhwkTx)pb2dFZJ8zx))&=|!4 z4QwqQ*~-maJ7=O8Jt3i5Ey0RGN`nmW^RRYEMRxQcoK{Zx5j6f7FGn;QYS}e-$H^xF z1d#Tx`G1cvbnLu5*NGhu9TlX1o$ukW7@Gg%8|%NAcMy~$@=@lO!uuLS|7pbs;jI8P z-J5_~0@gmi-PSn$Mb#1=BkCJZ^BJdRG=X;0=GT}#)U|Fvuv>*N%>9OY1^Do-xxQ}2 z$RG?@`NuJrj_!OY*E^xBtZD-LvQIq{_|FOaeC}=?$SxlIIb$pPt70TqyjI2q-8ntG z@6$Ec5U0#_wl#jQ`s<}0zxNVBrt7xXJdtZI*IcL;3Y=_q;p;iPxs!JKD7X2Xt5HS7 zN<%1hExd79Y9xm)q$suAxpBwy)c!j%!@KBsE%M9$WOG?rRD?L`X6`-Jov(GhK6?uL zmtWVpteGvvQFj=+eJcEhEf%${o Date: Fri, 5 Aug 2022 10:57:12 +0200 Subject: [PATCH 62/91] [BridgeHub] Setup Wococo parachain backbone (reused from Rococo) [Bridge-Backport] Rebase-fix BridgeHub] Added zombienet startup tomls for Rococo/Wococo Fix typo --- Cargo.lock | 11 +++ parachains/runtimes/bridge-hubs/README.md | 69 ++++++++++++++----- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 1 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 21 ++---- .../src/chain_spec/bridge_hubs.rs | 51 +++++++++++--- polkadot-parachain/src/command.rs | 15 +++- .../0004-run_bridge_hubs_rococo.toml | 37 ++++++++++ .../0004-run_bridge_hubs_wococo.toml | 37 ++++++++++ 8 files changed, 202 insertions(+), 40 deletions(-) create mode 100644 zombienet_tests/0004-run_bridge_hubs_rococo.toml create mode 100644 zombienet_tests/0004-run_bridge_hubs_wococo.toml diff --git a/Cargo.lock b/Cargo.lock index 8edcc339e66..c0e22540ece 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1024,6 +1024,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-xcm", "parachain-info", + "parachains-common", "parity-scale-codec", "polkadot-parachain 0.9.27", "polkadot-runtime-common", @@ -9923,6 +9924,7 @@ dependencies = [ "sc-allocator", "sp-maybe-compressed-blob", "sp-sandbox", + "sp-serializer 4.0.0-dev (git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges)", "sp-wasm-interface", "thiserror", "wasm-instrument", @@ -11634,6 +11636,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "sp-serializer" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#aa7520bd0a2094204a6c0b33865aa264e6d686a5" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "sp-session" version = "4.0.0-dev" diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index d6c5b20f500..2bb2d2b9c68 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -19,7 +19,7 @@ Every _BridgeHub_ is meant to be **_common good parachain_** with main responsib ### Deploy ``` cd -cargo build --release --locked -p polkadot-parachain@0.9.220 +cargo build --release --locked -p polkadot-parachain@0.9.230 mkdir -p ~/local_bridge_testing/bin @@ -28,41 +28,78 @@ cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-paracha ls -lrt ~/local_bridge_testing/bin/polkadot-parachain ``` -### Run relay chain (Rococo) +### Run with Zombienet + +``` +# Rococo +POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ + POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet_tests/0004-run_bridge_hubs_rococo.toml + +# Wococo +POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ + POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet_tests/0004-run_bridge_hubs_wococo.toml +``` + +### Run from cmd + +#### Run relay chains (Rococo, Wococo) ``` +# Rococo ~/local_bridge_testing/bin/polkadot build-spec --chain rococo-local --disable-default-bootnode --raw > ~/local_bridge_testing/rococo-local-cfde.json -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --alice --tmp -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --bob --tmp --port 30334 +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --alice --tmp --port 30332 --rpc-port 9932 --ws-port 9942 +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --bob --tmp --port 30333 --rpc-port 9933 --ws-port 9943 +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --charlie --tmp --port 30334 --rpc-port 9934 --ws-port 9944 + +# Wococo +~/local_bridge_testing/bin/polkadot build-spec --chain wococo-local --disable-default-bootnode --raw > ~/local_bridge_testing/wococo-local-cfde.json +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --alice --tmp --port 30335 --rpc-port 9935 --ws-port 9945 +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --bob --tmp --port 30336 --rpc-port 9936 --ws-port 9946 +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --charlie --tmp --port 30337 --rpc-port 9937 --ws-port 9947 +~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --dave --tmp --port 30338 --rpc-port 9938 --ws-port 9948 ``` +We need at least 5 nodes together (validator + collator) to finalize blocks. -### Run Rococo BridgeHub parachain -#### Generate spec + genesis + wasm (paraId=1013) +#### Run BridgeHub parachains (Rococo, Wococo) +##### Generate spec + genesis + wasm (paraId=1013) ``` +# Rococo rm ~/local_bridge_testing/bridge-hub-rococo-local-raw.json ~/local_bridge_testing/bin/polkadot-parachain build-spec --chain bridge-hub-rococo-local --raw --disable-default-bootnode > ~/local_bridge_testing/bridge-hub-rococo-local-raw.json ~/local_bridge_testing/bin/polkadot-parachain export-genesis-state --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis ~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis-wasm -``` -#### Run collators -``` -~/local_bridge_testing/bin/polkadot-parachain --collator --alice --force-authoring --tmp --port 40335 --ws-port 9946 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 30335 -~/local_bridge_testing/bin/polkadot-parachain --collator --bob --force-authoring --tmp --port 40336 --ws-port 9947 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 30336 +# Wococo +rm ~/local_bridge_testing/bridge-hub-wococo-local-raw.json +~/local_bridge_testing/bin/polkadot-parachain build-spec --chain bridge-hub-wococo-local --raw --disable-default-bootnode > ~/local_bridge_testing/bridge-hub-wococo-local-raw.json +~/local_bridge_testing/bin/polkadot-parachain export-genesis-state --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json > ~/local_bridge_testing/bridge-hub-wococo-local-genesis +~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json > ~/local_bridge_testing/bridge-hub-wococo-local-genesis-wasm ``` -#### Run parachain node +##### Run collators (Rococo, Wococo) ``` -~/local_bridge_testing/bin/polkadot-parachain --tmp --port 40337 --ws-port 9948 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 30337 +# Rococo +~/local_bridge_testing/bin/polkadot-parachain --collator --alice --force-authoring --tmp --port 40333 --rpc-port 8933 --ws-port 8943 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41333 --rpc-port 48933 --ws-port 48943 +~/local_bridge_testing/bin/polkadot-parachain --collator --bob --force-authoring --tmp --port 40334 --rpc-port 8934 --ws-port 8944 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41334 -rpc-port 48934 --ws-port 48944 + +# Wococo +~/local_bridge_testing/bin/polkadot-parachain --collator --alice --force-authoring --tmp --port 40335 --rpc-port 8935 --ws-port 8945 --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41335 --rpc-port 48935 --ws-port 48945 +~/local_bridge_testing/bin/polkadot-parachain --collator --bob --force-authoring --tmp --port 40336 --rpc-port 8936 --ws-port 8946 --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41336 --rpc-port 48936 --ws-port 48946 ``` -#### Activate parachain (paraId=1013) +##### Activate parachains (Rococo, Wococo) (paraId=1013) ``` -https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/explorer +# Rococo +https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9942#/explorer + +# Wococo +https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9945#/explorer ``` -#### After parachain activation, we should see new blocks in collator's node +##### After parachain activation, we should see new blocks in collator's node ``` https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/parachains ``` diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 4ebdf070607..e716dc69ae3 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -65,6 +65,7 @@ cumulus-primitives-timestamp = { path = "../../../../primitives/timestamp", defa cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } parachain-info = { path = "../../../../parachains/pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../../parachains/common", default-features = false } # Bridges bp-polkadot-core = { path = "../../../../bridges/primitives/polkadot-core", default-features = false } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 7008fb385e2..09835bb6562 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -31,9 +31,9 @@ use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify}, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, MultiSignature, + ApplyExtrinsicResult, }; use sp_std::prelude::*; @@ -43,14 +43,13 @@ use sp_version::RuntimeVersion; use frame_support::{ construct_runtime, parameter_types, - traits::Everything, + traits::{Everything, IsInVec}, weights::{ constants::WEIGHT_PER_SECOND, ConstantMultiplier, DispatchClass, Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, }, PalletId, }; -use frame_support::traits::IsInVec; use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, @@ -70,16 +69,10 @@ use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; // XCM Imports +use parachains_common::{AccountId, Signature}; use xcm::latest::prelude::BodyId; use xcm_executor::XcmExecutor; -/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = MultiSignature; - -/// Some way of identifying an account on the chain. We intentionally make it equivalent -/// to the public key of our transaction signing scheme. -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; - /// Balance of an account. pub type Balance = u128; @@ -489,7 +482,7 @@ impl pallet_bridge_grandpa::Config for Runtime { type WeightInfo = (); } -/// Add granda bridge pallet to track Wococo relay chain +/// Add granda bridge pallet to track Rococo relay chain pub type BridgeGrandpaRococoInstance = pallet_bridge_grandpa::Instance2; impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_rococo::Rococo; @@ -512,7 +505,7 @@ pub type BridgeParachainWococoInstance = pallet_bridge_parachains::Instance1; impl pallet_bridge_parachains::Config for Runtime { type WeightInfo = (); type BridgesGrandpaPalletInstance = BridgeGrandpaWococoInstance; - type ParasPalletName = RococoBridgeParachainPalletName; + type ParasPalletName = WococoBridgeParachainPalletName; type TrackedParachains = IsInVec; type HeadsToKeep = ParachainHeadsToKeep; } @@ -522,7 +515,7 @@ pub type BridgeParachainRococoInstance = pallet_bridge_parachains::Instance2; impl pallet_bridge_parachains::Config for Runtime { type WeightInfo = (); type BridgesGrandpaPalletInstance = BridgeGrandpaRococoInstance; - type ParasPalletName = WococoBridgeParachainPalletName; + type ParasPalletName = RococoBridgeParachainPalletName; type TrackedParachains = IsInVec; type HeadsToKeep = ParachainHeadsToKeep; } diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index 9091fb82c88..aa89eede88e 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -14,14 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use polkadot_service::ParaId; +use cumulus_primitives_core::ParaId; use sc_chain_spec::ChainSpec; use sc_cli::RuntimeVersion; use std::{path::PathBuf, str::FromStr}; /// Collects all supported BridgeHub configurations +#[derive(Debug, PartialEq)] pub enum BridgeHubRuntimeType { RococoLocal, + WococoLocal, } impl FromStr for BridgeHubRuntimeType { @@ -30,6 +32,7 @@ impl FromStr for BridgeHubRuntimeType { fn from_str(value: &str) -> Result { match value { rococo::BRIDGE_HUB_ROCOCO_LOCAL => Ok(BridgeHubRuntimeType::RococoLocal), + wococo::BRIDGE_HUB_WOCOCO_LOCAL => Ok(BridgeHubRuntimeType::WococoLocal), _ => Err(format!("Value '{}' is not configured yet", value)), } } @@ -41,19 +44,25 @@ impl BridgeHubRuntimeType { pub fn chain_spec_from_json_file(&self, path: PathBuf) -> Result, String> { Ok(Box::new(match self { BridgeHubRuntimeType::RococoLocal => rococo::BridgeHubChainSpec::from_json_file(path)?, + BridgeHubRuntimeType::WococoLocal => wococo::BridgeHubChainSpec::from_json_file(path)?, })) } pub fn load_config(&self) -> Box { Box::new(match self { BridgeHubRuntimeType::RococoLocal => - rococo::local_config("rococo-local", ParaId::new(1013)), + rococo::local_config("Rococo BrideHub Local", "rococo-local", ParaId::new(1013)), + BridgeHubRuntimeType::WococoLocal => + wococo::local_config("Wococo BrideHub Local", "wococo-local", ParaId::new(1013)), }) } pub fn runtime_version(&self) -> &'static RuntimeVersion { match self { - BridgeHubRuntimeType::RococoLocal => &bridge_hub_rococo_runtime::VERSION, + BridgeHubRuntimeType::RococoLocal | BridgeHubRuntimeType::WococoLocal => { + // this is intentional, for Rococo/Wococo we just want to have one runtime, which is configured for both sides + &bridge_hub_rococo_runtime::VERSION + }, } } } @@ -71,22 +80,29 @@ fn ensure_id(id: &str) -> Result<&str, String> { } } +/// Sub-module for Rococo setup pub mod rococo { + use super::ParaId; use crate::chain_spec::{ get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, }; - use bridge_hub_rococo_runtime::{AccountId, AuraId}; - use cumulus_primitives_core::ParaId; + use parachains_common::{AccountId, AuraId}; use sc_chain_spec::ChainType; use sp_core::sr25519; - pub const BRIDGE_HUB_ROCOCO_LOCAL: &str = "bridge-hub-rococo-local"; + pub(crate) const BRIDGE_HUB_ROCOCO_LOCAL: &str = "bridge-hub-rococo-local"; /// Specialized `ChainSpec` for the normal parachain runtime. pub type BridgeHubChainSpec = sc_service::GenericChainSpec; - pub fn local_config(relay_chain: &str, para_id: ParaId) -> BridgeHubChainSpec { + pub type RuntimeApi = bridge_hub_rococo_runtime::RuntimeApi; + + pub fn local_config( + chain_name: &str, + relay_chain: &str, + para_id: ParaId, + ) -> BridgeHubChainSpec { let properties = sc_chain_spec::Properties::new(); // TODO: check // properties.insert("ss58Format".into(), 2.into()); @@ -95,7 +111,7 @@ pub mod rococo { BridgeHubChainSpec::from_genesis( // Name - "Rococo BrideHub Local", + chain_name, // ID super::ensure_id(BRIDGE_HUB_ROCOCO_LOCAL).expect("invalid id"), ChainType::Local, @@ -180,3 +196,22 @@ pub mod rococo { } } } + +/// Sub-module for Wococo setup (reuses stuff from Rococo) +pub mod wococo { + use super::ParaId; + use crate::chain_spec::bridge_hubs::rococo; + + pub(crate) const BRIDGE_HUB_WOCOCO_LOCAL: &str = "bridge-hub-wococo-local"; + + pub type BridgeHubChainSpec = rococo::BridgeHubChainSpec; + pub type RuntimeApi = rococo::RuntimeApi; + + pub fn local_config( + chain_name: &str, + relay_chain: &str, + para_id: ParaId, + ) -> BridgeHubChainSpec { + rococo::local_config(chain_name, relay_chain, para_id) + } +} diff --git a/polkadot-parachain/src/command.rs b/polkadot-parachain/src/command.rs index 55c6400f55a..e5734e4320d 100644 --- a/polkadot-parachain/src/command.rs +++ b/polkadot-parachain/src/command.rs @@ -37,6 +37,8 @@ use sp_core::hexdisplay::HexDisplay; use sp_runtime::traits::{AccountIdConversion, Block as BlockT}; use std::{net::SocketAddr, path::PathBuf}; +use crate::chain_spec::bridge_hubs::BridgeHubRuntimeType; + /// Helper enum that is used for better distinction of different parachain/runtime configuration /// (it is based/calculated on ChainSpec's ID attribute) #[derive(Debug, PartialEq, Default)] @@ -479,7 +481,11 @@ macro_rules! construct_async_run { Runtime::BridgeHub(bridge_hub_runtime_type) => { runner.async_run(|$config| { let $components = match bridge_hub_runtime_type { - chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => new_partial::( + chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => new_partial::( + &$config, + crate::service::aura_build_import_queue::<_, AuraId>, + )?, + chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => new_partial::( &$config, crate::service::aura_build_import_queue::<_, AuraId>, )?, @@ -775,7 +781,12 @@ pub fn run() -> Result<()> { Runtime::BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => crate::service::start_generic_aura_node::< - bridge_hub_rococo_runtime::RuntimeApi, + chain_spec::bridge_hubs::rococo::RuntimeApi, + AuraId, + >(config, polkadot_config, collator_options, id, hwbench), + chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => + crate::service::start_generic_aura_node::< + chain_spec::bridge_hubs::wococo::RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench), } diff --git a/zombienet_tests/0004-run_bridge_hubs_rococo.toml b/zombienet_tests/0004-run_bridge_hubs_rococo.toml new file mode 100644 index 00000000000..9c9c9c49d15 --- /dev/null +++ b/zombienet_tests/0004-run_bridge_hubs_rococo.toml @@ -0,0 +1,37 @@ +[relaychain] +default_command = "{{POLKADOT_BINARY_PATH}}" +default_args = [ "-lparachain=debug" ] + +chain = "rococo-local" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true + + [[relaychain.nodes]] + name = "charlie" + validator = true + +[[parachains]] +id = 1013 +cumulus_based = true +chain = "bridge-hub-rococo-local" + + [[parachains.collators]] + name = "alice" + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + args = ["-lparachain=debug"] + + [[parachains.collators]] + name = "bob" + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + args = ["-lparachain=debug"] + + [[parachains.collators]] + name = "charlie" + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + args = ["-lparachain=debug"] diff --git a/zombienet_tests/0004-run_bridge_hubs_wococo.toml b/zombienet_tests/0004-run_bridge_hubs_wococo.toml new file mode 100644 index 00000000000..b938b1672dd --- /dev/null +++ b/zombienet_tests/0004-run_bridge_hubs_wococo.toml @@ -0,0 +1,37 @@ +[relaychain] +default_command = "{{POLKADOT_BINARY_PATH}}" +default_args = [ "-lparachain=debug" ] + +chain = "wococo-local" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true + + [[relaychain.nodes]] + name = "charlie" + validator = true + +[[parachains]] +id = 1013 +cumulus_based = true +chain = "bridge-hub-wococo-local" + + [[parachains.collators]] + name = "alice" + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + args = ["-lparachain=debug"] + + [[parachains.collators]] + name = "bob" + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + args = ["-lparachain=debug"] + + [[parachains.collators]] + name = "charlie" + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + args = ["-lparachain=debug"] From 69f2159c012606c7c1fa5d3a573b767881c5a7b0 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 24 Aug 2022 22:20:46 +0200 Subject: [PATCH 63/91] [BridgeHub] Correct Accounts setup for bridge pallets --- parachains/runtimes/bridge-hubs/README.md | 80 ++++++++++++++++--- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 8 ++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 18 +++-- .../src/chain_spec/bridge_hubs.rs | 14 ++++ 4 files changed, 102 insertions(+), 18 deletions(-) diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index 2bb2d2b9c68..3da73178a5e 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -12,23 +12,34 @@ Every _BridgeHub_ is meant to be **_common good parachain_** with main responsib - sync finality proofs between BridgeHub parachains - pass (XCM) messages between different BridgeHub parachains -![](/home/bparity/parity/cumulus/cumulus/parachains/runtimes/bridge-hubs/docs/bridge-hub-parachain-design.jpg "Basic deployment setup") +![](./docs/bridge-hub-parachain-design.jpg "Basic deployment setup") ## How to test locally Rococo <-> Wococo -### Deploy +### Build/Deploy ``` -cd -cargo build --release --locked -p polkadot-parachain@0.9.230 - +# Prepare empty directory for testing mkdir -p ~/local_bridge_testing/bin +mkdir -p ~/local_bridge_testing/logs -rm ~/local_bridge_testing/bin/polkadot-parachain + +# 1. Build polkadot binary +TODO: description + +# 2. Build cumulus polkadot-parachain binary +cd +cargo build --release --locked -p polkadot-parachain@0.9.230 cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-parachain -ls -lrt ~/local_bridge_testing/bin/polkadot-parachain + +# 3. Build substrate-relay binary +git clone https://github.com/paritytech/parity-bridges-common.git +cd parity-bridges-common +cargo build -p substrate-relay +rm ~/local_bridge_testing/bin/substrate-relay +cp target/release/substrate-relay ~/local_bridge_testing/bin/substrate-relay ``` -### Run with Zombienet +### Run chains (Rococo + BridgeHub, Wococo + BridgeHub) with Zombienet ``` # Rococo @@ -42,7 +53,7 @@ POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet_tests/0004-run_bridge_hubs_wococo.toml ``` -### Run from cmd +### Run chains (Rococo + BridgeHub, Wococo + BridgeHub) from `cmd` #### Run relay chains (Rococo, Wococo) ``` @@ -64,7 +75,8 @@ POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ We need at least 5 nodes together (validator + collator) to finalize blocks. #### Run BridgeHub parachains (Rococo, Wococo) -##### Generate spec + genesis + wasm (paraId=1013) + +**1. Generate spec + genesis + wasm (paraId=1013)** ``` # Rococo rm ~/local_bridge_testing/bridge-hub-rococo-local-raw.json @@ -79,7 +91,7 @@ rm ~/local_bridge_testing/bridge-hub-wococo-local-raw.json ~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json > ~/local_bridge_testing/bridge-hub-wococo-local-genesis-wasm ``` -##### Run collators (Rococo, Wococo) +**2. Run collators (Rococo, Wococo)** ``` # Rococo ~/local_bridge_testing/bin/polkadot-parachain --collator --alice --force-authoring --tmp --port 40333 --rpc-port 8933 --ws-port 8943 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41333 --rpc-port 48933 --ws-port 48943 @@ -90,7 +102,7 @@ rm ~/local_bridge_testing/bridge-hub-wococo-local-raw.json ~/local_bridge_testing/bin/polkadot-parachain --collator --bob --force-authoring --tmp --port 40336 --rpc-port 8936 --ws-port 8946 --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41336 --rpc-port 48936 --ws-port 48946 ``` -##### Activate parachains (Rococo, Wococo) (paraId=1013) +**3. Activate parachains (Rococo, Wococo) (paraId=1013)** ``` # Rococo https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9942#/explorer @@ -99,11 +111,53 @@ https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9942#/explorer https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9945#/explorer ``` -##### After parachain activation, we should see new blocks in collator's node +**4. After parachain activation, we should see new blocks in collator's node** ``` https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/parachains ``` +### Run relayers (Rococo, Wococo) + +**Accounts of BridgeHub parachains:** +- `Bob` is pallet owner of all bridge pallets +- `Alice` is `Sudo` + +**1. Init bridges** +``` +# Rococo -> Wococo +RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay init-bridge bridge-hub-rococo-to-bridge-hub-wococo \ + --source-host localhost \ + --source-port 48943 \ + --target-host localhost \ + --target-port 8945 \ + --target-signer //Bob + +# Wococo -> Rococo +RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay init-bridge bridge-hub-wococo-to-bridge-hub-rococo \ + --source-host localhost \ + --source-port 48945 \ + --target-host localhost \ + --target-port 8943 \ + --target-signer //Bob +``` + +**2. Relay headers** + +TODO: + +**2. Relay (Grandpa relay-chain) headers** + +TODO: + +**3. Relay (BridgeHub parachain) headers** + +TODO: + +**4. Relay (XCM) messages** + +TODO: ## Git subtree `./bridges` diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index e716dc69ae3..bf040394f5b 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -82,6 +82,9 @@ default = [ "std", ] std = [ + "bp-polkadot-core/std", + "bp-rococo/std", + "bp-wococo/std", "codec/std", "log/std", "scale-info/std", @@ -101,6 +104,11 @@ std = [ "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", + "pallet-bridge-grandpa/std", + "pallet-bridge-messages/std", + "pallet-bridge-parachains/std", + "pallet-bridge-relayers/std", + "pallet-shift-session-manager/std", "pallet-collator-selection/std", "pallet-session/std", "pallet-sudo/std", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 09835bb6562..30cbbda4a23 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -467,6 +467,11 @@ impl pallet_collator_selection::Config for Runtime { type WeightInfo = (); } +impl pallet_sudo::Config for Runtime { + type Call = Call; + type Event = Event; +} + // Add bridge pallets (GPA) parameter_types! { pub const MaxRequests: u32 = 64; @@ -558,15 +563,18 @@ construct_runtime!( DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, // Consensus support. - ShiftSessionManager: pallet_shift_session_manager::{Pallet}, + ShiftSessionManager: pallet_shift_session_manager::{Pallet} = 40, // Wococo bridge modules - BridgeWococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage}, - BridgeWococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage}, + BridgeWococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Config} = 41, + BridgeWococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage} = 42, // Rococo bridge modules - BridgeRococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage}, - BridgeRococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage}, + BridgeRococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Config} = 43, + BridgeRococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage} = 44, + + // Sudo + Sudo: pallet_sudo::{Pallet, Call, Config, Event, Storage} = 100, } ); diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index aa89eede88e..4bef37f8932 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -143,6 +143,8 @@ pub mod rococo { get_account_id_from_seed::("Ferdie//stash"), ], para_id, + Some(get_account_id_from_seed::("Alice")), + Some(get_account_id_from_seed::("Bob")), ) }, Vec::new(), @@ -158,6 +160,8 @@ pub mod rococo { invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, id: ParaId, + root_key: Option, + bridges_pallet_owner: Option, ) -> bridge_hub_rococo_runtime::GenesisConfig { bridge_hub_rococo_runtime::GenesisConfig { system: bridge_hub_rococo_runtime::SystemConfig { @@ -193,6 +197,16 @@ pub mod rococo { polkadot_xcm: bridge_hub_rococo_runtime::PolkadotXcmConfig { safe_xcm_version: Some(SAFE_XCM_VERSION), }, + // TODO: when go live, check it: https://github.com/paritytech/parity-bridges-common/issues/1551 + sudo: bridge_hub_rococo_runtime::SudoConfig { key: root_key }, + bridge_wococo_grandpa: bridge_hub_rococo_runtime::BridgeWococoGrandpaConfig { + owner: bridges_pallet_owner.clone(), + ..Default::default() + }, + bridge_rococo_grandpa: bridge_hub_rococo_runtime::BridgeRococoGrandpaConfig { + owner: bridges_pallet_owner, + ..Default::default() + }, } } } From 0373bfbe53a87c27361bac21373d61d66e6c3bdf Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Sun, 28 Aug 2022 22:32:58 +0200 Subject: [PATCH 64/91] [BridgeHub] Added best_finalized runtime apis --- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 ++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index bf040394f5b..d934d21d7a0 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -69,6 +69,7 @@ parachains-common = { path = "../../../../parachains/common", default-features = # Bridges bp-polkadot-core = { path = "../../../../bridges/primitives/polkadot-core", default-features = false } +bp-runtime = { path = "../../../../bridges/primitives/runtime", default-features = false } bp-rococo = { path = "../../../../bridges/primitives/chain-rococo", default-features = false } bp-wococo = { path = "../../../../bridges/primitives/chain-wococo", default-features = false } pallet-bridge-grandpa = { path = "../../../../bridges/modules/grandpa", default-features = false } @@ -83,6 +84,7 @@ default = [ ] std = [ "bp-polkadot-core/std", + "bp-runtime/std", "bp-rococo/std", "bp-wococo/std", "codec/std", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 30cbbda4a23..ba378e78a4f 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -59,6 +59,7 @@ pub use sp_runtime::{MultiAddress, Perbill, Permill}; use xcm_config::{XcmConfig, XcmOriginToTransactDispatchOrigin}; use bp_polkadot_core::parachains::ParaId; +use bp_runtime::{HeaderId, HeaderIdProvider}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -233,6 +234,14 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } +// TODO: check-parameter - move to bridges/primitives, once rebased and would compile with bp_bridge_hub_xyz dependencies +mod runtime_api { + use super::BlockNumber; + use super::Hash; + bp_runtime::decl_bridge_finality_runtime_apis!(rococo); + bp_runtime::decl_bridge_finality_runtime_apis!(wococo); +} + parameter_types! { pub const Version: RuntimeVersion = VERSION; @@ -701,6 +710,18 @@ impl_runtime_apis! { } } + impl runtime_api::RococoFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeRococoGrandpa::best_finalized().map(|header| header.id()) + } + } + + impl runtime_api::WococoFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeWococoGrandpa::best_finalized().map(|header| header.id()) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade() -> (Weight, Weight) { From 2264ea702c8a8d1a99c73ca6bc22a56eebc9f0d9 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 30 Aug 2022 13:42:14 +0200 Subject: [PATCH 65/91] [BridgeHub] Updated readme.md and script for local run --- parachains/runtimes/bridge-hubs/README.md | 148 ++++++++++------------ scripts/bridges_rococo_wococo.sh | 144 +++++++++++++++++++++ 2 files changed, 209 insertions(+), 83 deletions(-) create mode 100755 scripts/bridges_rococo_wococo.sh diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index 3da73178a5e..f10fcf5764c 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -16,7 +16,7 @@ Every _BridgeHub_ is meant to be **_common good parachain_** with main responsib ## How to test locally Rococo <-> Wococo -### Build/Deploy +### Prepare/Build/Deploy ``` # Prepare empty directory for testing mkdir -p ~/local_bridge_testing/bin @@ -24,7 +24,10 @@ mkdir -p ~/local_bridge_testing/logs # 1. Build polkadot binary -TODO: description +git clone https://github.com/paritytech/polkadot.git +cd polkadot +cargo build --release +cp target/release/polkadot ~/local_bridge_testing/bin/polkadot # 2. Build cumulus polkadot-parachain binary cd @@ -32,88 +35,18 @@ cargo build --release --locked -p polkadot-parachain@0.9.230 cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-parachain # 3. Build substrate-relay binary -git clone https://github.com/paritytech/parity-bridges-common.git +git clone https://github.com/paritytech/parity-bridges-common.git cd parity-bridges-common cargo build -p substrate-relay -rm ~/local_bridge_testing/bin/substrate-relay cp target/release/substrate-relay ~/local_bridge_testing/bin/substrate-relay ``` -### Run chains (Rococo + BridgeHub, Wococo + BridgeHub) with Zombienet +### Run chains (Rococo + BridgeHub, Wococo + BridgeHub) from `./scripts/bridges_rococo_wococo.sh` ``` -# Rococo -POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ - POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ - ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet_tests/0004-run_bridge_hubs_rococo.toml - -# Wococo -POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ - POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ - ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet_tests/0004-run_bridge_hubs_wococo.toml -``` - -### Run chains (Rococo + BridgeHub, Wococo + BridgeHub) from `cmd` - -#### Run relay chains (Rococo, Wococo) -``` - - -# Rococo -~/local_bridge_testing/bin/polkadot build-spec --chain rococo-local --disable-default-bootnode --raw > ~/local_bridge_testing/rococo-local-cfde.json -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --alice --tmp --port 30332 --rpc-port 9932 --ws-port 9942 -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --bob --tmp --port 30333 --rpc-port 9933 --ws-port 9943 -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --charlie --tmp --port 30334 --rpc-port 9934 --ws-port 9944 - -# Wococo -~/local_bridge_testing/bin/polkadot build-spec --chain wococo-local --disable-default-bootnode --raw > ~/local_bridge_testing/wococo-local-cfde.json -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --alice --tmp --port 30335 --rpc-port 9935 --ws-port 9945 -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --bob --tmp --port 30336 --rpc-port 9936 --ws-port 9946 -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --charlie --tmp --port 30337 --rpc-port 9937 --ws-port 9947 -~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --dave --tmp --port 30338 --rpc-port 9938 --ws-port 9948 -``` -We need at least 5 nodes together (validator + collator) to finalize blocks. - -#### Run BridgeHub parachains (Rococo, Wococo) - -**1. Generate spec + genesis + wasm (paraId=1013)** -``` -# Rococo -rm ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -~/local_bridge_testing/bin/polkadot-parachain build-spec --chain bridge-hub-rococo-local --raw --disable-default-bootnode > ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -~/local_bridge_testing/bin/polkadot-parachain export-genesis-state --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis -~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis-wasm - -# Wococo -rm ~/local_bridge_testing/bridge-hub-wococo-local-raw.json -~/local_bridge_testing/bin/polkadot-parachain build-spec --chain bridge-hub-wococo-local --raw --disable-default-bootnode > ~/local_bridge_testing/bridge-hub-wococo-local-raw.json -~/local_bridge_testing/bin/polkadot-parachain export-genesis-state --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json > ~/local_bridge_testing/bridge-hub-wococo-local-genesis -~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json > ~/local_bridge_testing/bridge-hub-wococo-local-genesis-wasm -``` - -**2. Run collators (Rococo, Wococo)** -``` -# Rococo -~/local_bridge_testing/bin/polkadot-parachain --collator --alice --force-authoring --tmp --port 40333 --rpc-port 8933 --ws-port 8943 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41333 --rpc-port 48933 --ws-port 48943 -~/local_bridge_testing/bin/polkadot-parachain --collator --bob --force-authoring --tmp --port 40334 --rpc-port 8934 --ws-port 8944 --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41334 -rpc-port 48934 --ws-port 48944 - -# Wococo -~/local_bridge_testing/bin/polkadot-parachain --collator --alice --force-authoring --tmp --port 40335 --rpc-port 8935 --ws-port 8945 --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41335 --rpc-port 48935 --ws-port 48945 -~/local_bridge_testing/bin/polkadot-parachain --collator --bob --force-authoring --tmp --port 40336 --rpc-port 8936 --ws-port 8946 --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41336 --rpc-port 48936 --ws-port 48946 -``` - -**3. Activate parachains (Rococo, Wococo) (paraId=1013)** -``` -# Rococo -https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9942#/explorer - -# Wococo -https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9945#/explorer -``` - -**4. After parachain activation, we should see new blocks in collator's node** -``` -https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/parachains +./scripts/bridges_rococo_wococo.sh stop +./scripts/bridges_rococo_wococo.sh start-rococo +./scripts/bridges_rococo_wococo.sh start-wococo ``` ### Run relayers (Rococo, Wococo) @@ -126,7 +59,7 @@ https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/parachains ``` # Rococo -> Wococo RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ - ~/local_bridge_testing/bin/substrate-relay init-bridge bridge-hub-rococo-to-bridge-hub-wococo \ + ~/local_bridge_testing/bin/substrate-relay init-bridge rococo-to-bridge-hub-wococo \ --source-host localhost \ --source-port 48943 \ --target-host localhost \ @@ -135,7 +68,7 @@ RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ # Wococo -> Rococo RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ - ~/local_bridge_testing/bin/substrate-relay init-bridge bridge-hub-wococo-to-bridge-hub-rococo \ + ~/local_bridge_testing/bin/substrate-relay init-bridge wococo-to-bridge-hub-rococo \ --source-host localhost \ --source-port 48945 \ --target-host localhost \ @@ -143,13 +76,42 @@ RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ --target-signer //Bob ``` -**2. Relay headers** +**2. Relay (Grandpa relay-chain) headers** -TODO: +**source-host/source-port** - WS-port of collator's inner RelayChain validator +**target-host/target-port** - WS-port of BridgeHub collator -**2. Relay (Grandpa relay-chain) headers** +``` +# Rococo -> Wococo +RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay relay-headers rococo-to-bridge-hub-wococo \ + --source-host localhost \ + --source-port 48943 \ + --target-host localhost \ + --target-port 8945 \ + --target-signer //Bob \ + --target-transactions-mortality=4 -TODO: +# Wococo -> Rococo +RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay relay-headers wococo-to-bridge-hub-rococo \ + --source-host localhost \ + --source-port 48945 \ + --target-host localhost \ + --target-port 8943 \ + --target-signer //Bob \ + --target-transactions-mortality=4 +``` + +**Check parachain collators:** +- Rococo parachain: + - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8943#/chainstate + - Pallet: **bridgeWococoGrandpa** + - Keys: **bestFinalized()** +- Wococo parachain: + - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8945#/chainstate + - Pallet: **bridgeRococoGrandpa** + - Keys: **bestFinalized()** **3. Relay (BridgeHub parachain) headers** @@ -159,6 +121,8 @@ TODO: TODO: +--- + ## Git subtree `./bridges` Add Bridges repo as a local remote and synchronize it with latest `master` from bridges repo: @@ -177,3 +141,21 @@ all into one. Now we use `master` branch, but in future, it could change to some release branch/tag. Original `./bridges/Cargo.toml` was renamed to `./bridges/Cargo.toml_removed_for_bridges_subtree_feature` to avoid confusion for `Cargo` having multiple workspaces. + +---- + +###### TODO: fix zombienet ports as bridges_rococo_wococo.sh, because networks colide and interfere by default, because of autodiscovery on localhost + +###### Run chains (Rococo + BridgeHub, Wococo + BridgeHub) with Zombienet + +``` +# Rococo +POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ + POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet_tests/0004-run_bridge_hubs_rococo.toml + +# Wococo +POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ + POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./zombienet_tests/0004-run_bridge_hubs_wococo.toml +``` diff --git a/scripts/bridges_rococo_wococo.sh b/scripts/bridges_rococo_wococo.sh new file mode 100755 index 00000000000..8087769b57f --- /dev/null +++ b/scripts/bridges_rococo_wococo.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +function ensure_binaries() { + if [[ ! -f ~/local_bridge_testing/bin/polkadot ]]; then + echo " Required polkadot binary '~/local_bridge_testing/bin/polkadot' does not exist!" + echo " You need to build it and copy to this location!" + echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)" + exit 1 + fi + if [[ ! -f ~/local_bridge_testing/bin/polkadot-parachain ]]; then + echo " Required polkadot-parachain binary '~/local_bridge_testing/bin/polkadot-parachain' does not exist!" + echo " You need to build it and copy to this location!" + echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)" + exit 1 + fi + if [[ ! -f ~/local_bridge_testing/bin/substrate-relay ]]; then + echo " Required substrate-relay binary '~/local_bridge_testing/bin/substrate-relay' does not exist!" + echo " You need to build it and copy to this location!" + echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)" + exit 1 + fi +} + +function register_parachain() { + local PORT=$1 + local PARA_ID=$2 + local GENESIS_FILE=$3 + local GENESIS_WASM_FILE=$4 + + echo "" + echo "" + echo " Please, register parachain: https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A${PORT}#/sudo" + echo " parasSudoWrapper.sudoScheduleParaInitialize:" + echo " ParaId: $PARA_ID" + echo " Genesis (file): $GENESIS_FILE" + echo " Genesis wasm (file): $GENESIS_WASM_FILE" + echo " Parachain: Yes" + echo "" + echo "" + + # TODO: find the way to do it automatically +} + +function check_parachain_collator() { + local PORT=$1 + local PALLET=$2 + + echo "" + echo "" + echo " Once relayers work, check parachain: https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A${PORT}#/chainstate" + echo " Pallet: ${PALLET}" + echo " Keys:" + echo " bestFinalized()" + echo "" + echo "" +} + +ensure_binaries + +case "$1" in + start-rococo) + # TODO: change to generate: ./bin/polkadot key generate-node-key + VALIDATOR_KEY_ALICE=(12D3KooWRD6kEQuKDn7BCsMfW71U8AEFDYTc7N2dFQthRsjSR8J5 aa5dc9a97f82f9af38d1f16fbc9c72e915577c3ca654f7d33601645ca5b9a8a5) + VALIDATOR_KEY_BOB=(12D3KooWELR4gpc8unmH8WiauK133v397KoMfkkjESWJrzg7Y3Pq 556a36beaeeb95225eb84ae2633bd8135e5ace22f03291853654f07bc5da20ae) + VALIDATOR_KEY_CHARLIE=(12D3KooWRatYNHCRpyzhjPqfSPHYsUTi1zX4452kxSWg8ek99Vun 56e6593340685021d27509c0f48836c1dcb84c5aeba730ada68f54a9ae242e67) + COLLATOR_KEY_ALICE=(12D3KooWAU51GKCfPBfhaKK8gr6XmHNVXd4e96BYL9v2QkWtDrAm 15642c2e078ddfaacd1e68afcc8bab6dd8085b72b3038b799ddfb46bec18696c) + COLLATOR_KEY_BOB=(12D3KooWPg8SEatSi9HvdPJVYBNxeNAvyPTo3Rd4WPdzLRcM8gaS ac651cd6b9a2c7768bca92369ecd4807b54059c33775257e6ba5f72ae91a136d) + + # Start Rococo relay + ~/local_bridge_testing/bin/polkadot build-spec --chain rococo-local --disable-default-bootnode --raw > ~/local_bridge_testing/rococo-local-cfde.json + ~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --alice --tmp --port 30332 --rpc-port 9932 --ws-port 9942 --no-mdns --node-key ${VALIDATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30333/p2p/${VALIDATOR_KEY_BOB[0]} &> ~/local_bridge_testing/logs/rococo_relay_alice.log & + ~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --bob --tmp --port 30333 --rpc-port 9933 --ws-port 9943 --no-mdns --node-key ${VALIDATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/30332/p2p/${VALIDATOR_KEY_ALICE[0]} &> ~/local_bridge_testing/logs/rococo_relay_bob.log & + ~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/rococo-local-cfde.json --charlie --tmp --port 30334 --rpc-port 9934 --ws-port 9944 --no-mdns ${VALIDATOR_KEY_CHARLIE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30332/p2p/${VALIDATOR_KEY_ALICE[0]} &> ~/local_bridge_testing/logs/rococo_relay_charlie.log & + sleep 2 + + # Prepare Rococo parachain + rm ~/local_bridge_testing/bridge-hub-rococo-local-raw.json + ~/local_bridge_testing/bin/polkadot-parachain build-spec --chain bridge-hub-rococo-local --raw --disable-default-bootnode > ~/local_bridge_testing/bridge-hub-rococo-local-raw.json + ~/local_bridge_testing/bin/polkadot-parachain export-genesis-state --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis + ~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json > ~/local_bridge_testing/bridge-hub-rococo-local-genesis-wasm + + # Rococo + ~/local_bridge_testing/bin/polkadot-parachain --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json --collator --alice --force-authoring --tmp --port 40333 --rpc-port 8933 --ws-port 8943 --no-mdns --node-key ${COLLATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/40334/p2p/${COLLATOR_KEY_BOB[0]} -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41333 --rpc-port 48933 --ws-port 48943 --no-mdns --node-key ${COLLATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30332/p2p/${VALIDATOR_KEY_ALICE[0]} &> ~/local_bridge_testing/logs/rococo_para_alice.log & + ~/local_bridge_testing/bin/polkadot-parachain --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json --collator --bob --force-authoring --tmp --port 40334 --rpc-port 8934 --ws-port 8944 --no-mdns --node-key ${COLLATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/40333/p2p/${COLLATOR_KEY_ALICE[0]} -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41334 --rpc-port 48934 --ws-port 48944 --no-mdns --node-key ${COLLATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/30333/p2p/${VALIDATOR_KEY_BOB[0]} &> ~/local_bridge_testing/logs/rococo_para_bob.log & + + register_parachain 9942 1013 ~/local_bridge_testing/bridge-hub-rococo-local-genesis ~/local_bridge_testing/bridge-hub-rococo-local-genesis-wasm + check_parachain_collator 8943 bridgeWococoGrandpa + ;; + start-wococo) + # TODO: change to generate: ./bin/polkadot key generate-node-key + VALIDATOR_KEY_ALICE=(12D3KooWKB4SqNJAttmDHXEKtETLPr8Nixso8gUNzNKDS9b6HtYj 8c87694d3a3b3a0e201caceaae095aac66a76ba37545c439f7e0d986670da8e6) + VALIDATOR_KEY_BOB=(12D3KooWJq6xkfyV3LFxoNgsCskAwLv6VfLhL3cjHfW4UxDNwroF 9ebcd7371b427d63c087762fe3dc5a1d4b01875cb8611c263feda21364d4a329) + VALIDATOR_KEY_CHARLIE=(12D3KooW9yQQXz6xUJY2YgizKTFLS5fPaPi4KznQdMHEW4Hzt3NL 8bb1c50421a73082b8275ead6e3f150a774a0f8d6ad4a786df5d5b7688115cb1) + VALIDATOR_KEY_DAVE=(12D3KooWJP1R7XxqEuSryXSKYRVz9wdMRvd5LSqjRpdK4AvjnB12 e160d8b0ef4d66e02abf6663417b024f27f6cb2f826b746f3d267c58a32e9c05) + COLLATOR_KEY_ALICE=(12D3KooWNsQbEdW1eyC6TXAJCXxZ8qzkCJPWFqhWjco1SYYdaKPU 93ac8257255961b3d3ec0038747f0f9c7ddaa5dd352297daa352e098285965b7) + COLLATOR_KEY_BOB=(12D3KooWQ9yxdcf7kavoaKNWmt92tkCGQ8C4sq2JNBgCjvGtvvFg 7e396f01a8a74ecf2010ab2d57ca9fc6c5d673914d97ad6a066fe724e60c3412) + + # Start Wococo relay + ~/local_bridge_testing/bin/polkadot build-spec --chain wococo-local --disable-default-bootnode --raw > ~/local_bridge_testing/wococo-local-cfde.json + ~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --alice --tmp --port 30335 --rpc-port 9935 --ws-port 9945 --no-mdns --node-key ${VALIDATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30336/p2p/${VALIDATOR_KEY_BOB[0]} &> ~/local_bridge_testing/logs/wococo_relay_alice.log & + ~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --bob --tmp --port 30336 --rpc-port 9936 --ws-port 9946 --no-mdns --node-key ${VALIDATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/30335/p2p/${VALIDATOR_KEY_ALICE[0]} &> ~/local_bridge_testing/logs/wococo_relay_bob.log & + ~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --charlie --tmp --port 30337 --rpc-port 9937 --ws-port 9947 --no-mdns --node-key ${VALIDATOR_KEY_CHARLIE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30335/p2p/${VALIDATOR_KEY_ALICE[0]} &> ~/local_bridge_testing/logs/wococo_relay_charlie.log & + ~/local_bridge_testing/bin/polkadot --chain ~/local_bridge_testing/wococo-local-cfde.json --dave --tmp --port 30338 --rpc-port 9938 --ws-port 9948 --no-mdns --node-key ${VALIDATOR_KEY_DAVE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30336/p2p/${VALIDATOR_KEY_BOB[0]} &> ~/local_bridge_testing/logs/wococo_relay_dave.log & + sleep 2 + + # Prepare Wococo parachain + rm ~/local_bridge_testing/bridge-hub-wococo-local-raw.json + ~/local_bridge_testing/bin/polkadot-parachain build-spec --chain bridge-hub-wococo-local --raw --disable-default-bootnode > ~/local_bridge_testing/bridge-hub-wococo-local-raw.json + ~/local_bridge_testing/bin/polkadot-parachain export-genesis-state --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json > ~/local_bridge_testing/bridge-hub-wococo-local-genesis + ~/local_bridge_testing/bin/polkadot-parachain export-genesis-wasm --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json > ~/local_bridge_testing/bridge-hub-wococo-local-genesis-wasm + + # Wococo + ~/local_bridge_testing/bin/polkadot-parachain --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json --collator --alice --force-authoring --tmp --port 40335 --rpc-port 8935 --ws-port 8945 --no-mdns --node-key ${COLLATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/40336/p2p/${COLLATOR_KEY_BOB[0]} -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41335 --rpc-port 48935 --ws-port 48945 --no-mdns --node-key ${COLLATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30335/p2p/${VALIDATOR_KEY_ALICE[0]} &> ~/local_bridge_testing/logs/wococo_para_alice.log & + ~/local_bridge_testing/bin/polkadot-parachain --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json --collator --bob --force-authoring --tmp --port 40336 --rpc-port 8936 --ws-port 8946 --no-mdns --node-key ${COLLATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/40335/p2p/${COLLATOR_KEY_ALICE[0]} -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41336 --rpc-port 48936 --ws-port 48946 --no-mdns --node-key ${COLLATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/30336/p2p/${VALIDATOR_KEY_BOB[0]} &> ~/local_bridge_testing/logs/wococo_para_bob.log & + + register_parachain 9945 1013 ~/local_bridge_testing/bridge-hub-wococo-local-genesis ~/local_bridge_testing/bridge-hub-wococo-local-genesis-wasm + check_parachain_collator 8945 bridgeRococoGrandpa + ;; + init-ro-wo) + # Init bridge Rococo->Wococo + RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay init-bridge rococo-to-bridge-hub-wococo \ + --source-host localhost \ + --source-port 48943 \ + --target-host localhost \ + --target-port 8945 \ + --target-signer //Bob + ;; + init-wo-ro) + # Init bridge Wococo->Rococo + RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay init-bridge wococo-to-bridge-hub-rococo \ + --source-host localhost \ + --source-port 48945 \ + --target-host localhost \ + --target-port 8943 \ + --target-signer //Bob + ;; + stop) + pkill -f polkadot + pkill -f parachain + ;; + *) echo "A command is require. Supported commands: start-rococo, start-wococo, init-ro-wo, init-wo-ro, stop"; exit 1;; +esac From b8887433f1d922303bd82fb1f511fb93d989257a Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Fri, 2 Sep 2022 00:27:59 +0200 Subject: [PATCH 66/91] [BridgeHub] Fixed TrackedParachains --- parachains/runtimes/bridge-hubs/README.md | 46 ++++++++++++++++++- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 19 ++++++-- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index f10fcf5764c..8aa4bbde04f 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -45,8 +45,12 @@ cp target/release/substrate-relay ~/local_bridge_testing/bin/substrate-relay ``` ./scripts/bridges_rococo_wococo.sh stop + ./scripts/bridges_rococo_wococo.sh start-rococo +# TODO: check log and activate parachain manually + ./scripts/bridges_rococo_wococo.sh start-wococo +# TODO: check log and activate parachain manually ``` ### Run relayers (Rococo, Wococo) @@ -56,6 +60,16 @@ cp target/release/substrate-relay ~/local_bridge_testing/bin/substrate-relay - `Alice` is `Sudo` **1. Init bridges** + +Need to wait for parachain activation, then run: + +``` +./scripts/bridges_rococo_wococo.sh init-ro-wo +./scripts/bridges_rococo_wococo.sh init-wo-ro +``` + +or + ``` # Rococo -> Wococo RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ @@ -115,7 +129,37 @@ RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ **3. Relay (BridgeHub parachain) headers** -TODO: +``` +# Rococo -> Wococo +RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay relay-parachains bridge-hub-rococo-to-bridge-hub-wococo \ + --source-host localhost \ + --source-port 48943 \ + --target-host localhost \ + --target-port 8945 \ + --target-signer //Bob \ + --target-transactions-mortality=4 + +# Wococo -> Rococo +RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay relay-parachains bridge-hub-wococo-to-bridge-hub-rococo \ + --source-host localhost \ + --source-port 48945 \ + --target-host localhost \ + --target-port 8943 \ + --target-signer //Bob \ + --target-transactions-mortality=4 +``` + +**Check parachain collators:** +- Rococo parachain: + - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8943#/chainstate + - Pallet: **bridgeWococoParachain** + - Keys: **bestParaHeads()** +- Wococo parachain: + - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8945#/chainstate + - Pallet: **bridgeRococoParachain** + - Keys: **bestParaHeads()** **4. Relay (XCM) messages** diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index ba378e78a4f..b60b132b9ad 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -43,7 +43,7 @@ use sp_version::RuntimeVersion; use frame_support::{ construct_runtime, parameter_types, - traits::{Everything, IsInVec}, + traits::Everything, weights::{ constants::WEIGHT_PER_SECOND, ConstantMultiplier, DispatchClass, Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, @@ -192,6 +192,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { state_version: 1, }; +// TODO:check-parameter - remove all unneeded and reuse frm parachain_common + /// This determines the average expected block time that we are targeting. /// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. /// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked @@ -234,7 +236,7 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } -// TODO: check-parameter - move to bridges/primitives, once rebased and would compile with bp_bridge_hub_xyz dependencies +// TODO:check-parameter - move to bridges/primitives, once rebased and would compile with bp_bridge_hub_xyz dependencies mod runtime_api { use super::BlockNumber; use super::Hash; @@ -347,6 +349,7 @@ impl pallet_authorship::Config for Runtime { type EventHandler = (CollatorSelection,); } +// TODO:check-parameter parameter_types! { pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; pub const MaxLocks: u32 = 50; @@ -367,6 +370,7 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; } +// TODO:check-parameter parameter_types! { /// Relay Chain `TransactionByteFee` / 10 pub const TransactionByteFee: Balance = 10 * MICROUNIT; @@ -483,7 +487,9 @@ impl pallet_sudo::Config for Runtime { // Add bridge pallets (GPA) parameter_types! { + // TODO:check-parameter pub const MaxRequests: u32 = 64; + // TODO:check-parameter pub const HeadersToKeep: u32 = 1024; } @@ -493,6 +499,7 @@ impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_wococo::Wococo; type MaxRequests = MaxRequests; type HeadersToKeep = HeadersToKeep; + // TODO:check-parameter type WeightInfo = (); } @@ -502,6 +509,7 @@ impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_rococo::Rococo; type MaxRequests = MaxRequests; type HeadersToKeep = HeadersToKeep; + // TODO:check-parameter type WeightInfo = (); } @@ -511,26 +519,27 @@ parameter_types! { pub const ParachainHeadsToKeep: u32 = 64; pub const RococoBridgeParachainPalletName: &'static str = ROCOCO_BRIDGE_PARA_PALLET_NAME; pub const WococoBridgeParachainPalletName: &'static str = WOCOCO_BRIDGE_PARA_PALLET_NAME; - pub GetTenFirstParachains: Vec = (0..10).map(ParaId).collect(); } /// Add parachain bridge pallet to track Wococo bridge hub parachain pub type BridgeParachainWococoInstance = pallet_bridge_parachains::Instance1; impl pallet_bridge_parachains::Config for Runtime { + // TODO:check-parameter type WeightInfo = (); type BridgesGrandpaPalletInstance = BridgeGrandpaWococoInstance; type ParasPalletName = WococoBridgeParachainPalletName; - type TrackedParachains = IsInVec; + type TrackedParachains = Everything; type HeadsToKeep = ParachainHeadsToKeep; } /// Add parachain bridge pallet to track Rococo bridge hub parachain pub type BridgeParachainRococoInstance = pallet_bridge_parachains::Instance2; impl pallet_bridge_parachains::Config for Runtime { + // TODO:check-parameter type WeightInfo = (); type BridgesGrandpaPalletInstance = BridgeGrandpaRococoInstance; type ParasPalletName = RococoBridgeParachainPalletName; - type TrackedParachains = IsInVec; + type TrackedParachains = Everything; type HeadsToKeep = ParachainHeadsToKeep; } From f721364bdd431aaad4fba03f1db63bb2b80ce658 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 19 Sep 2022 13:34:28 +0200 Subject: [PATCH 67/91] [BridgeHub] Fixed Paras pallet name for parachain heads relaying --- parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index b60b132b9ad..41b50791566 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -513,8 +513,8 @@ impl pallet_bridge_grandpa::Config for Runtime { type WeightInfo = (); } -pub const ROCOCO_BRIDGE_PARA_PALLET_NAME: &str = "RococoBridgeHubParachainPallet"; -pub const WOCOCO_BRIDGE_PARA_PALLET_NAME: &str = "WococoBridgeHubParachainPallet"; +pub const ROCOCO_BRIDGE_PARA_PALLET_NAME: &str = "Paras"; +pub const WOCOCO_BRIDGE_PARA_PALLET_NAME: &str = "Paras"; parameter_types! { pub const ParachainHeadsToKeep: u32 = 64; pub const RococoBridgeParachainPalletName: &'static str = ROCOCO_BRIDGE_PARA_PALLET_NAME; From e4fc870b0675ff089ab45ee47c2d43067dd2489b Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 20 Sep 2022 10:54:47 +0200 Subject: [PATCH 68/91] [BridgeHub] Add bridge-hub-rococo to CI pipelines --- .github/workflows/release-30_create-draft.yml | 6 ++++++ .github/workflows/srtool.yml | 2 ++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/release-30_create-draft.yml b/.github/workflows/release-30_create-draft.yml index ffd33d6f1e7..38c105d8b8e 100644 --- a/.github/workflows/release-30_create-draft.yml +++ b/.github/workflows/release-30_create-draft.yml @@ -50,6 +50,8 @@ jobs: runtime: statemint - category: assets runtime: westmint + - category: bridge-hubs + runtime: bridge-hub-rococo - category: collectives runtime: collectives-polkadot - category: contracts @@ -150,6 +152,7 @@ jobs: WESTMINT_DIGEST: ${{ github.workspace}}/westmint-srtool-json/westmint-srtool-digest.json STATEMINE_DIGEST: ${{ github.workspace}}/statemine-srtool-json/statemine-srtool-digest.json STATEMINT_DIGEST: ${{ github.workspace}}/statemint-srtool-json/statemint-srtool-digest.json + BRIDE_HUB_ROCOCO_DIGEST: ${{ github.workspace}}/bridge-hub-rococo-srtool-json/bridge-hub-rococo-srtool-digest.json COLLECTIVES_POLKADOT_DIGEST: ${{ github.workspace}}/collectives-polkadot-srtool-json/collectives-polkadot-srtool-digest.json ROCOCO_PARA_DIGEST: ${{ github.workspace}}/rococo-parachain-srtool-json/rococo-parachain-srtool-digest.json CANVAS_KUSAMA_DIGEST: ${{ github.workspace}}/contracts-rococo-srtool-json/contracts-rococo-srtool-digest.json @@ -165,6 +168,7 @@ jobs: ls -al $WESTMINT_DIGEST || true ls -al $STATEMINE_DIGEST || true ls -al $STATEMINT_DIGEST || true + ls -al $BRIDE_HUB_ROCOCO_DIGEST || true ls -al $COLLECTIVES_POLKADOT_DIGEST || true ls -al $ROCOCO_PARA_DIGEST || true ls -al $CANVAS_KUSAMA_DIGEST || true @@ -216,6 +220,8 @@ jobs: runtime: statemint - category: assets runtime: westmint + - category: bridge-hubs + runtime: bridge-hub-rococo - category: collectives runtime: collectives-polkadot - category: contracts diff --git a/.github/workflows/srtool.yml b/.github/workflows/srtool.yml index 82b8585b358..ec8ab3bf902 100644 --- a/.github/workflows/srtool.yml +++ b/.github/workflows/srtool.yml @@ -37,6 +37,8 @@ jobs: runtime: statemint - category: assets runtime: westmint + - category: bridge-hubs + runtime: bridge-hub-rococo - category: collectives runtime: collectives-polkadot - category: contracts From a2a630fe7ac4bf9cd4cacb5293a4f94175407331 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 21 Sep 2022 17:29:36 +0200 Subject: [PATCH 69/91] [BridgeHub] pallet_transaction_payment + Event --- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 14 +++++------ .../bridge-hub-rococo/src/xcm_config.rs | 23 ++----------------- 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 41b50791566..bb5b4f55aed 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -378,8 +378,7 @@ parameter_types! { } impl pallet_transaction_payment::Config for Runtime { - // TODO: hacked - // type Event = Event; + type Event = Event; type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; type WeightToFee = WeightToFee; type LengthToFee = ConstantMultiplier; @@ -417,7 +416,6 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = (); - type PriceForSiblingDelivery = (); } impl cumulus_pallet_dmp_queue::Config for Runtime { @@ -526,6 +524,7 @@ pub type BridgeParachainWococoInstance = pallet_bridge_parachains::Instance1; impl pallet_bridge_parachains::Config for Runtime { // TODO:check-parameter type WeightInfo = (); + type Event = Event; type BridgesGrandpaPalletInstance = BridgeGrandpaWococoInstance; type ParasPalletName = WococoBridgeParachainPalletName; type TrackedParachains = Everything; @@ -535,6 +534,7 @@ impl pallet_bridge_parachains::Config for Runtime /// Add parachain bridge pallet to track Rococo bridge hub parachain pub type BridgeParachainRococoInstance = pallet_bridge_parachains::Instance2; impl pallet_bridge_parachains::Config for Runtime { + type Event = Event; // TODO:check-parameter type WeightInfo = (); type BridgesGrandpaPalletInstance = BridgeGrandpaRococoInstance; @@ -563,9 +563,7 @@ construct_runtime!( // Monetary stuff. Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - // TODO: hacked - // TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 11, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, // Collator support. The order of these 4 are important and shall not change. Authorship: pallet_authorship::{Pallet, Call, Storage} = 20, @@ -585,11 +583,11 @@ construct_runtime!( // Wococo bridge modules BridgeWococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Config} = 41, - BridgeWococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage} = 42, + BridgeWococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage, Event} = 42, // Rococo bridge modules BridgeRococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Config} = 43, - BridgeRococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage} = 44, + BridgeRococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage, Event} = 44, // Sudo Sudo: pallet_sudo::{Pallet, Call, Config, Event, Storage} = 100, diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index 216997bdf2e..543962807d3 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -182,26 +182,6 @@ pub type Barrier = ( /// XCM weigher type. pub type XcmWeigher = FixedWeightBounds; -// TODO: hacked -// pub struct XcmConfig; -// impl xcm_executor::Config for XcmConfig { -// type Call = Call; -// type XcmSender = XcmRouter; -// // How to withdraw and deposit an asset. -// type AssetTransactor = LocalAssetTransactor; -// type OriginConverter = XcmOriginToTransactDispatchOrigin; -// type IsReserve = NativeAsset; -// type IsTeleporter = (); // Teleporting is disabled. -// type LocationInverter = LocationInverter; -// type Barrier = Barrier; -// type Weigher = FixedWeightBounds; -// type Trader = -// UsingComponents>; -// type ResponseHandler = PolkadotXcm; -// type AssetTrap = PolkadotXcm; -// type AssetClaims = PolkadotXcm; -// type SubscriptionService = PolkadotXcm; -// } pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type Call = Call; @@ -225,6 +205,7 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = (); type UniversalAliases = Nothing; + type CallDispatcher = Call; } /// No local origins on this chain are allowed to dispatch XCM sends/executions. @@ -234,7 +215,7 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); From 812917765a8d9b33bcac6d5815ae08bc4288652b Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 26 Sep 2022 11:58:26 +0200 Subject: [PATCH 70/91] TMP: fix missing dep to std --- parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index d934d21d7a0..db023747414 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -119,6 +119,7 @@ std = [ "pallet-transaction-payment/std", "pallet-xcm/std", "parachain-info/std", + "parachains-common/std", "polkadot-parachain/std", "polkadot-runtime-common/std", "sp-api/std", From e50628f44b5df490df245f040059fadc0dbdc36a Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 3 Oct 2022 15:03:13 +0200 Subject: [PATCH 71/91] [BridgeHub] Added chain_spec for live Rococo/Wococo --- .../src/chain_spec/bridge_hubs.rs | 91 ++++++++++++++++++- polkadot-parachain/src/command.rs | 4 + 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index 4bef37f8932..ee0e62cd17b 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -22,7 +22,9 @@ use std::{path::PathBuf, str::FromStr}; /// Collects all supported BridgeHub configurations #[derive(Debug, PartialEq)] pub enum BridgeHubRuntimeType { + Rococo, RococoLocal, + Wococo, WococoLocal, } @@ -31,7 +33,9 @@ impl FromStr for BridgeHubRuntimeType { fn from_str(value: &str) -> Result { match value { + rococo::BRIDGE_HUB_ROCOCO => Ok(BridgeHubRuntimeType::Rococo), rococo::BRIDGE_HUB_ROCOCO_LOCAL => Ok(BridgeHubRuntimeType::RococoLocal), + wococo::BRIDGE_HUB_WOCOCO => Ok(BridgeHubRuntimeType::Wococo), wococo::BRIDGE_HUB_WOCOCO_LOCAL => Ok(BridgeHubRuntimeType::WococoLocal), _ => Err(format!("Value '{}' is not configured yet", value)), } @@ -43,22 +47,29 @@ impl BridgeHubRuntimeType { pub fn chain_spec_from_json_file(&self, path: PathBuf) -> Result, String> { Ok(Box::new(match self { + BridgeHubRuntimeType::Rococo => rococo::BridgeHubChainSpec::from_json_file(path)?, BridgeHubRuntimeType::RococoLocal => rococo::BridgeHubChainSpec::from_json_file(path)?, + BridgeHubRuntimeType::Wococo => wococo::BridgeHubChainSpec::from_json_file(path)?, BridgeHubRuntimeType::WococoLocal => wococo::BridgeHubChainSpec::from_json_file(path)?, })) } pub fn load_config(&self) -> Box { Box::new(match self { + BridgeHubRuntimeType::Rococo => + rococo::live_config(rococo::BRIDGE_HUB_ROCOCO, "Rococo BrideHub", "rococo", ParaId::new(1013)), BridgeHubRuntimeType::RococoLocal => - rococo::local_config("Rococo BrideHub Local", "rococo-local", ParaId::new(1013)), + rococo::local_config(rococo::BRIDGE_HUB_ROCOCO_LOCAL, "Rococo BrideHub Local", "rococo-local", ParaId::new(1013)), + BridgeHubRuntimeType::Wococo => + wococo::live_config(wococo::BRIDGE_HUB_WOCOCO, "Wococo BrideHub", "wococo", ParaId::new(1013)), BridgeHubRuntimeType::WococoLocal => - wococo::local_config("Wococo BrideHub Local", "wococo-local", ParaId::new(1013)), + wococo::local_config(wococo::BRIDGE_HUB_WOCOCO_LOCAL, "Wococo BrideHub Local", "wococo-local", ParaId::new(1013)), }) } pub fn runtime_version(&self) -> &'static RuntimeVersion { match self { + BridgeHubRuntimeType::Rococo | BridgeHubRuntimeType::Wococo | BridgeHubRuntimeType::RococoLocal | BridgeHubRuntimeType::WococoLocal => { // this is intentional, for Rococo/Wococo we just want to have one runtime, which is configured for both sides &bridge_hub_rococo_runtime::VERSION @@ -90,6 +101,7 @@ pub mod rococo { use sc_chain_spec::ChainType; use sp_core::sr25519; + pub(crate) const BRIDGE_HUB_ROCOCO: &str = "bridge-hub-rococo"; pub(crate) const BRIDGE_HUB_ROCOCO_LOCAL: &str = "bridge-hub-rococo-local"; /// Specialized `ChainSpec` for the normal parachain runtime. @@ -98,7 +110,67 @@ pub mod rococo { pub type RuntimeApi = bridge_hub_rococo_runtime::RuntimeApi; + pub fn live_config( + id: &str, + chain_name: &str, + relay_chain: &str, + para_id: ParaId, + ) -> BridgeHubChainSpec { + let properties = sc_chain_spec::Properties::new(); + // TODO: check + // properties.insert("ss58Format".into(), 2.into()); + // properties.insert("tokenSymbol".into(), "ROC".into()); + // properties.insert("tokenDecimals".into(), 12.into()); + + BridgeHubChainSpec::from_genesis( + // Name + chain_name, + // ID + super::ensure_id(id).expect("invalid id"), + ChainType::Live, + move || { + genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + para_id, + Some(get_account_id_from_seed::("Alice")), + Some(get_account_id_from_seed::("Bob")), + ) + }, + Vec::new(), + None, + None, + None, + Some(properties), + Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, + ) + } + pub fn local_config( + id: &str, chain_name: &str, relay_chain: &str, para_id: ParaId, @@ -113,7 +185,7 @@ pub mod rococo { // Name chain_name, // ID - super::ensure_id(BRIDGE_HUB_ROCOCO_LOCAL).expect("invalid id"), + super::ensure_id(id).expect("invalid id"), ChainType::Local, move || { genesis( @@ -216,16 +288,27 @@ pub mod wococo { use super::ParaId; use crate::chain_spec::bridge_hubs::rococo; + pub(crate) const BRIDGE_HUB_WOCOCO: &str = "bridge-hub-wococo"; pub(crate) const BRIDGE_HUB_WOCOCO_LOCAL: &str = "bridge-hub-wococo-local"; pub type BridgeHubChainSpec = rococo::BridgeHubChainSpec; pub type RuntimeApi = rococo::RuntimeApi; pub fn local_config( + id: &str, + chain_name: &str, + relay_chain: &str, + para_id: ParaId, + ) -> BridgeHubChainSpec { + rococo::local_config(id, chain_name, relay_chain, para_id) + } + + pub fn live_config( + id: &str, chain_name: &str, relay_chain: &str, para_id: ParaId, ) -> BridgeHubChainSpec { - rococo::local_config(chain_name, relay_chain, para_id) + rococo::live_config(id, chain_name, relay_chain, para_id) } } diff --git a/polkadot-parachain/src/command.rs b/polkadot-parachain/src/command.rs index e5734e4320d..5e0950a5673 100644 --- a/polkadot-parachain/src/command.rs +++ b/polkadot-parachain/src/command.rs @@ -481,10 +481,12 @@ macro_rules! construct_async_run { Runtime::BridgeHub(bridge_hub_runtime_type) => { runner.async_run(|$config| { let $components = match bridge_hub_runtime_type { + chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => new_partial::( &$config, crate::service::aura_build_import_queue::<_, AuraId>, )?, + chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo | chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => new_partial::( &$config, crate::service::aura_build_import_queue::<_, AuraId>, @@ -779,11 +781,13 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), Runtime::BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { + chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => crate::service::start_generic_aura_node::< chain_spec::bridge_hubs::rococo::RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench), + chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo | chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => crate::service::start_generic_aura_node::< chain_spec::bridge_hubs::wococo::RuntimeApi, From e406a37bb39b7223ffc066a78cfa9acb6eaacf37 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 4 Oct 2022 13:25:03 +0200 Subject: [PATCH 72/91] [BridgeHub] Added chain_spec for live Rococo/Wococo - root/owner to None --- polkadot-parachain/src/chain_spec/bridge_hubs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index ee0e62cd17b..ed2f678ee54 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -156,8 +156,8 @@ pub mod rococo { get_account_id_from_seed::("Ferdie//stash"), ], para_id, - Some(get_account_id_from_seed::("Alice")), - Some(get_account_id_from_seed::("Bob")), + None, + None, ) }, Vec::new(), From c60aa82d60a70411a5141adef266c5029e5e28cb Mon Sep 17 00:00:00 2001 From: Anthony Lazam Date: Wed, 5 Oct 2022 17:24:03 +0800 Subject: [PATCH 73/91] Bridge-Hub Rococo Chainspec (#1729) * Bridge-Hub Rococo Chainspec --- parachains/chain-specs/bridge-hub-rococo.json | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 parachains/chain-specs/bridge-hub-rococo.json diff --git a/parachains/chain-specs/bridge-hub-rococo.json b/parachains/chain-specs/bridge-hub-rococo.json new file mode 100644 index 00000000000..97bacbbf22c --- /dev/null +++ b/parachains/chain-specs/bridge-hub-rococo.json @@ -0,0 +1,85 @@ +{ + "name": "Rococo BrideHub", + "id": "bridge-hub-rococo", + "chainType": "Live", + "bootNodes": [ + "/dns/rococo-bridge-hub-collator-0.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWNARtf7sSXKHsdzHctSZYaPMskTw3fVFbBoRK4Joc1KqY", + "/dns/rococo-bridge-hub-collator-1.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWB4E9XQFHerwn3HH2biX6cBkMu6pkkVieMoSGfmMHZsFv", + "/dns/rococo-bridge-hub-collator-2.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWHC2RcMS8sHjicdS9FMTy1XDWvyZDQRut6TwxyWpBMUo3", + "/dns/rococo-bridge-hub-collator-3.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWP3E4oQUh8XejaubDZKoD1bNapu7UEvPc3cCfsNdQngSq", + "/dns/rococo-bridge-hub-collator-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWNARtf7sSXKHsdzHctSZYaPMskTw3fVFbBoRK4Joc1KqY", + "/dns/rococo-bridge-hub-collator-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWB4E9XQFHerwn3HH2biX6cBkMu6pkkVieMoSGfmMHZsFv", + "/dns/rococo-bridge-hub-collator-2.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWHC2RcMS8sHjicdS9FMTy1XDWvyZDQRut6TwxyWpBMUo3", + "/dns/rococo-bridge-hub-collator-3.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWP3E4oQUh8XejaubDZKoD1bNapu7UEvPc3cCfsNdQngSq" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": {}, + "relay_chain": "rococo", + "para_id": 1013, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xf5030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x1094e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe320676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702cc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x0a000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9050f9ffb4503e7865bae8a399c89a5da52bc71c1eca5353749542dfdf0af97bf764f9c2f44e860cd485f1cd86400f649": "0x0000000000000000010000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95c7acbba5f59ca99cd7b8256f6342aa094e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe32": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9867d04f5fd090d96ed68a6355487eb8a741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9aba72baede0a06ab63b4b66340fe145acc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9c3e60052e92d2d3cfad167f41164dd110676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x04446272696467652d6875622d726f636f636f", + "0x365c9cdbf82b9bda69e4bbdf1b38a7834e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058147c04de43c5471053205adaa403e5359022427e9a8f1ce9335b4e6262a8de34023299aa8ef49b82d21f60f545eb8f587da904aaf9ea6b0c0229d4198f28423ff92884fff66c3446ebe5b5dc02ccb76d844842f6967bcb2da59429c9fc11ed0edc0fde2876969bfbd61ea0fae894337f29deb1fcd428f7963b2ddaebdf76f6c9729d86fefce6fc953eb96f672729516f4e4ea36ec5a151d7f4965bfea351d12dcfa151577a74cbdd230d39034bf45bdf727763d91eb7627e59d081010c60c0cddaef0defdc01eddc1c76aeebf2ce9d03e2de3964777ec2a15373c73975232baa5ba1808927bd5b51c1e29df65ca71550d92b4da3a2b39b1a259d7d47a36ece5f696b9fe246737591444001b44c725e7a959b49faad4368fac42e2d87ee5ecab2f5e1b04b7bf9a3dff9dc86195dc25481891758018c1de9b351d84baf28b0e2a5d3348abe44410e5efa8e4eb9151010900a76a4df74caed487b99e47cb4ae4fd08354575f6a425bfbc4de1b4de3b097aaa8687cf66e858228fc0dffb00afeb08ae1430305486852d627d7a7bbe3d947ef5654b4bca94fec6e07fc4e391010d00976aebfa66fd50eceb0a28c2d4e2ce1053476e6b7d797da21a38931b4c4e00a3560b0d34e9d8f3adfdf69edafd339a0963ba731c420c4db9ae47ccff3ac4fecf465ae5177a6dcc3998dba964f6fe9d02f939c9f7e399ddbec53fbb5e5502d77df1e5da7af6d7efb9c9b7bd67800829f2ddb24e7a5ed811eb367779a0ed0ba3e3debfa34b9ad5077e31c907ed7852858b0842e84c1450facb0e3bc36ca7ae73a9d823bcecb3b2f6d51d92f0eefdcbdd4dc619f2fc540404040557638e79d9f3a05779cbb1bb63d35ca7bef3df6f7b8bb1d42014874c824e767dc7cde13a9a16209553c3bdc98e4c0678790f909fc9ed3b7b1d3d8753a467ffe68676fb3da59636fed35f316a8049179d27ee7bfe7eff97bcfcbbff71e74370f044344e18d6a6f3f35eac2eff69b46f1f71057526fa76fe8bf3bbeddb53fc9197151ddea044cbc5b09e1c6dfd3b71be0dd4a88d53b4d48d5e95aebb777f4774ee3e6fe6dfcd4f9a8fcf2657193ce9a9027dd690f50bda4ac11892eed8d1e9db646a47e74a7c13e41a7307b1b3f7d99dca2b3e6fa74699e9d52a7497b552ffd6d77467f6fbbf0a5eb347ddb6dc03fa74eb3ec0de22d7fdb75ab7f1eb7ebfa14c4931f9060e09f5fd7a76be49fdfb8c33fd77971736f6d505e993265c87cf4b7dd1c1e3ae5ec6dd0e3e6de693c582f3728d6477bf99f85c265ca9429f357f5cfe1e6fac416f6893de75d0d2ac97008ea6660507a0146dd488bea564208fd9def56426c79e759a778c7b9a9516e0544e69def78e7eec6e95087436577ee2e4a5da31c3fbb733a728d72b051edce295398b946b9bff31d93869c61459fdc3fe8cfdb9f26a4ad73a4216768d127f7ec6f73d0b22664f6e9b9d376709a90769de62250f7ddceddd6a9981f40d9791b6da92b20307fe1b74ff639dd4bb9c6b9f5d96f7dc8ecc3e3d7fc5ca7f9bdf71efb7bdcedf3dbda146987deec6d6ffb6be621ea6eae7f3b7506a8d4f9a8ce9f3b4d8873d67408020808488877d6f5e9ce6fa79db946b137caf5a95da7df06fbd45e800addd428f7b551ed348d7ade6e6a140b797003828697a29acf3e9fad4d1176e8ecd0faf03feb34216c2f3ffb4ddb1e7f5783ea311f81ba7737b77e3b9d8dba31e82853d96ff64d80cace037b8febd385cfdbfbcc0189c2601e884c4439e30d7e36e6a1580f4449cb44230804f4ec1c10f6fb7c36eaf2d3b74d1ef5b973a70d817d72feecec93739da69df1d69fad1e08a939205118e8a12ce0d9db59e3a1023d7bd628f7ed4e23c240cfaed36ebb69db53dfd5a06a6e33a86bcaee1a759db3757d72ee5cc55c43dd0a48cb3b4b87191cb4450a4c471074704147157480a1e3083a8ca003093a98d00103131a3a92304161fa420e31a628987460ca81a90d5317a62d4c3cc891851c5730f9c07486890939c098a4c881051317a6274c4c984e60ca92638c09c8348429099312262f98b8985c90438d1c6b98d830ad91838c494c0e2e98b0e4d8428e34729091c30939a290430b39dac8e1460e367298615a82090639d0303991a30a39a890e30c1d4b98ca301d614ac314035390494b8e3098ba6042c2d40213174c533099617a820905a62698be9864a0038b8e2c3a5c606ac2442607108e37705001c71938ac80a30a38c6c041060e28e0e002070c7064c1d1829a326acea8a9428d981a2ad47ca959420d1a3559a869420d19354fa881428d136a9250335483841a26d418a1668c1a356ad628a1512346cd156acca819535a8344859924665c308365a6053348cc1c311334c3829915cc403113c58c0a66aecca460c6ca8c11335566b498b9c18c0d66b298119ac162a6063357cc583153c50c15333498d93253c40c11332898a13233c4cc09669c9869622606334ccc4c31338319296670500243c90ba53094de289129a9514aa3748512154a6694c294a6503aa354841218a52da52b4a5494685052a294a554850406921ba42690be90c220f980d403120f485dd444819405090b520d4854907240e282b405e9095214a418909a2041419201294b6da3a251b950cfa86cd434ea98aa85ba856a468d422d83d4850aa664a532a102a17ea95dea1335a8fea04e516750aba852d42d3509b509d589ba443d4235423d6254859115465e4642187d310ac2a8cba88b110d46528cca184d3162c26806231d8c6c30121a61318a62248391969113a33346488c9a18c560c4c40806a325464a8c9218b9608465748449059717ae30949450b3006500a1c811c6940292136f09a630b109738c1c639888985098c0c027e815504b0e2bcc28e820c254055315dd048c024a319f30c9203191e38d8ec1fca243091314727ce17ae3022627b098eb0bd3096c851c5f74bcc084831e229892113422b4176830d096d098d0b2684d6831985e3c29b4222e2e9490601b8cda18b1310af33a960e24a0814846768412174a6c60403e91a900c70e70e800470cb226c030644b68321388c5682e605dae22a815b214e01043d242ba822444da41b442a442a741ad983f184d817ec1d6886a907420c1606cc433221a918d0cc434a01351cc43c233c23b82c5059c018d82b5a050684a68496030c05e80b5812181b5003b02c3820561593025b031d815485c2075b1cab40f2e2eae1d4c2bcc2a4424c4a1eb065307970d2697b91a5151eac2b5858e2de400c385c575459c42acc2b5859211b508389e30a18029e1624174422c238e3189d037c88c508a0295a2a68d9216a514583bd041657291a38c8983b6c11bd338c8a6c88ec894c892c86690c120ab4176456645b625a322ab22a341b64416830c4bf6043d23d3922191c92093227b4106459685aa41d1a0695031990b3226b216644d6451502d5406d48909842984ca82ba82aa827aa5a6a05aa946d42ab5884a444541a55287a827a84254a051995118466018bd31fac2c80ba32ed4b0510aa242748a3944e988984416d439b082404a43caa0060b39ca908050bac1d5851a2ed490916048a00886188658a68221a9c828bc21ba856641679069210343c6850c4c3685cc8d2c0cd916b23232333236b22c646df41bed056b8b55854583e78297e5257109411ac2758299c45c622ac14de02fef090c0b2c0798d065c4d5c4a5e58a01a58256416d60e1a04ec1dae2816171298d7191994d804d30165c83c6d22dc878c057d430318c6883782562118d8835882a8847ec5c6078597858802d786ebc2e442664499066d4a4419fa0341000cea88a3705090d1218a5301a08950af14b5ca375705531b77000dfc0d22286894be02c680d4c42e818c22485d945264492a22454dd2091e9d5f441673179d044bc351a05cdc5eaf2d4b8b2b884e217a616ad4546a5855e1bd91073073307f18de885de224a2142a18b6823a219f1092c4481fe417b51aa01c5628a317bc05f70170ee3b16129612171958951b07210c5288d99802442a98dd10e2250c2820722a51694b24840c906a52d2c6044851e37e40c6094456483f23ba6605cfd78a424e9c0912749787e387284f2f3a109cf9123d80c43f48a70017f44703ef848e1e9c091254f7e043e3a3851e244899309f81b8292234e3cd0840813264e18e0a384870913270c18c0f3e2a4305992c487c787233c3c1e08c0138273e281264b8e8c200a4f08a01c59f2e47d9af0806004498cf8fbc2f12869e2a4c9912447963c791e254d9c44c0a749089c2851f232bc20f8e044098f0f34bc2e4eca08787a8e489132029e224d9c2c9122858702db038213c192271f85a7278a930bf0b0bd1f381e9f243e2c61920488233c21e00981047af081e7031f48d2c3009e174e4a8f129e1f94ac9c149f263c51787e68b2c49f0f9a2cf180cdeb8153b2444a9325517c82e0393202264f9e24391285c707264b42b0d3a3c409939d1d8f070e044792f824f121890f0f0448af0b122c61926489cf92264b6a1e17ae478913261d38c2e31302253c41f094de0e5c14270cc89e0e9c149e253e4ba238f9c1270449661e17d7c4c9129f245142e0c3111f274ca41c59f2e4934409c1d738e661d281243e4d42e081233e4a78a22cf1a1c99223477498a2189610d2107aa7a26e0504f4ac7a3a646c23b6c14fd882fd80eeee2ef2802ed2458a38073cf8628c90488c2f5e177c44aeebbae2bbf8ba628c175fef45be2ebef8f18bf18a7cc1c7fca2bc5e64f8e083ce5deddc74ceb1bc607c13f275750dd735e77c31c2775df0718dfc1e33cf092184ef35bfc8f1bab81f062346dfbbb019dff562e42b0200c638291ccd292bc4e47b96b4a66318e38b1c9977780fce182fe678ddf07b93df53408c175fef001132bff7ae2bc6d8579c9323641823e478315f115e1cf9c5ed31e4eb62d8f0e218239c932f7eaea17bf0c10b5ed7f5dabd0b4e1a79609127e466eec8178c91e3638e70c22a27a4a38961d87b33c6eb41fade833087ab01734ee77a880e421ad00006740dd775f5752d60e75fd7007b5e577d2fc6f7ae4bebebe22b620f4248e183f0f105636ca8a3a3a3331ac537e79ccc373773c618218c317b19c705708c7c29c0547a2f26c026e3f7de7b345e91193ac733f29c1256d1837046f83ac6f9e0285ef1697c5d598ccc30d640b318e373d8151983cee510c48a310241c0cd8d0d31c686ddcccd1ce37bf1c118df631b62b4815f06bbce39e984901fc3031ce047ea00117580c7b061c337616417bb06f8e27c2fe27019a0e6c5181fd4205fda9cccac65314677c5f71e5f1733337c36f08bf14118e373570dbd62bc1ec308b58c7d3c2ec093cc1c737a3c86910004e0eb7afc2e1b183233bf78457e31be17af2bbed8313ed80f42082965e638e79cf34d0863adb0560823a531be37279c70cef7e09cf1ba9c73b4d2ca19648b99f98a6c03338691de63e6680347e6c8968b3630c7c7f4d5c073ce095f8c93461e3346e72e1a775d2e464c745dd7753d1be28b2fbe1b3872cda4939b420ae183d78bcc7c0de0451640c4e119b22cf28cf1bdf74e91497cc5c801883102200200002fc678c5eb8afc6cb8ae8bafeb8aef45e6ebbaa27331c699f81e8c30c6c8cc51cbe6e39b37e38cefc5c910c21923adc1728ceebd075f8c103e1863840f42f8b68b2f8e9099afc8fc2264aef1bd08f9ba38f28b30be1863c422ecebc58b9dd361868e30cf7de089c2e3431427475007f0c1b3248a8f000250d2a1c5d0a324490798307122001a629861861974c880f9284922a54992283e477c968000044e4e3a2cc9a6d484e7c9932433b8028060894f931044b1240ff88440069e1f8ef8345912e5c9112552968060070e2819827022029f195007f021832bc00f1f0538e283a72709930e1cf94093253f4849f2831b80c9929e9c1d01b0713e7c9c3839c5e00a9084278a0e9e9e233e41f034b941c7070260720590e2236549103c02c8e17c04b1c4033c519ee494783e90248a131f29477c60b2e40725477c9884208907787cc0c17ca224f19112001f35e0f0491285a709cf07a248b1a1c6f9f081478a149e284f40e044c991283c403851c213e54812293e28e13952801ed4017c3c51c2232565fda0c42749078efc20654993251e38d18cc0034ba2f0007104010538c0017cf8c073c489cf931144515283c58407081e2647824802021a663ee084c9910f3459f2810f3861a2c3816006e6e189e28123519cf82451e2840993243b4a3e3c521c88263c8fe40a70a489932552a2f00071244a121e264a8e04c1e3439325479a2c49f283120f78c0c9119f20782c104d785ec9f508a23801010f0824104d789e00b41f3454b18644c52ab654a31f88c4613faaf87e543f51a58a2af9e3543f3f2a5554a97e5492fea8e0cfcfcfcf0fffa8542a0c8953fd3412e790b89f9f9f9f87e4877f18898a7f1889fbf9f979aa9f9f1f88e447c53f3f8de4877f7e7e1e1215ffa81ac98f8a55aa87c4fdfc3412f7c3489cca42e2541389bb90fcb08a91fcf08f4a2271ae91385544a2628744c5ad82489c8a552ad543e2548c44c52a46020128869d730c0229495c42eae620b665fe523b1a87fd796dd49bf2d2e336adceb3e2c6650bee113dbad479f2aa5eba9c1f56d158ba8115cf47bc5b1d910552cbdd5feebc6a9aa65975ebb9d2a7ff484dabf5726b5ea6d92e19338de434b5d2d44ae33a8f4673d2d623a219d5ea3434241a2769a45a5da7472417899ca6926834796be04c69546355928b68485ee10c2c4152b52388b9e5222ddb32ad087dcc5ee997dbb44f2bb9878cec96109010f30278b742a28ba7a396a2ade75a0efd275a2492c7a845d748d6bcf02304584aee8139f6444ed23492a6915ce79130176d3d94a4694e2289482ec29c2644b350de6baed3d9767fbe47e4d449aed39afbf48b18bb3ed139662e899c92ace6ecaeabbdf37b646ffd1659cc33fb9c6e927b60d736ed7d125b433cfadeb322cbe21759ee5ea71f94f796759a0eaa9f4ea1b5ddfaaa29ec224bc42eb25ce759db6d377aa2bb674b64afea456e6d4c2da7ad86c84279cf1010b973276a3141abf7f16e1574e5882aa220324143221175aa39f52b72a80de9b1ece50cf36b12892e0cbbae4b24125d4edaea36da32ec325538e1e4823674db739f7e76ca1b75da1076dbad9dcdc78e5f6e3d50a6fc74f74e23f2f8a77477e1fb65ebec6d31ff9c0557de005f58f0060bc478fa34cb59d0e503f06ec502269e7626352196b396f5c9b9b5d1f4c959b7bec8afe92d7b33bfac3f9cb623eb1e0eb5eee158fe5222ef915b5e2d7b9d549d52eb5eaa44f2e9a5cd399c696ffde99a9336e770eae61cce689b2eda9cc3d1ec9d6fb93e5d9b8e3235b33ef1adcba136e4aa7a30ebfc6c0fbbe5b208756825f7f889aaac65104a5917d915a8e53250103426c6c2591a85f9b5e3af4d73cdad4d3e0fe279c9e923517a7577f74c47bbafabaf2cb3c2419a4691c74c945d792bc668af77fb8dd1c6d8b6e7f2e931c62c66318b5996f69f8f1d639605c5d8d995eccaaebcf469396c94c82db7fc5a96c51c94b9b482b02266c5f052413c9ce73183efbd2a342f468f31ab62756757b22b8fb9a6b58f4ad87b334ef3620d8da8e49af52ccbd6bc2c8fc65ef83cb352b299154c0896753167c74cd379a26de4d9569d6e986b9bb6b9871ec44bbd2a4ce64d8dea77ce4c342a0ee91f417f555eea657938b0ca4367276d3cdb6f57f9b69a3635bfd9d3ceb268fd159db14b8bae396b44d8a36bdb856f4bd0437f591ebc2f8b65bde622bf5ce531cdb3a959ea9623f377bee6f76d79e875737dd27cb4c13e69f6fa586fb9c83a2dcb2ca736b38209c1b22b7f5996656556b887845a720c31e22eaed01dd772d7524a19dda77b3a9332caacad0dfe45a3dfc957a46bd997e5666da6a799526e961ba9a6743b5769ce3221ed86704e0efa2b7b7a569bc209490e2bc9de1ed93b67f7f4cc6166d9ca8439d531081d4297ce5564375f89975bd6a83d1b5d985728bdca9163d29fac98652bd77571174f6736e7ccdab21569c92d3add2ed7792c9dc28cadec60925dd012f9f3971259d7dc350ada76087ba04fb7a0335b8197c7975967ccadcc1f93ec395bc12c5bb9ae8bafbc642b6ef5aa16661a0b87793a72377487370a73a9b5b58d9cb791bb97aa24cfde755dd7db7a26bdaef72e6a85835c6e959e4f6a6ffff4e761de72d7a99c9eed9789ede7d3fbba69547587a9a658d2e2317fa5635bcff52cab31768cc5b4d5d35cc22c7b39cc4b7f6ed9cb24bb96f52cbfafe15b59fb856dbc592fe7a55ce6d49d4b4dcbd97bbb3ecfaa2fe5222711616f76cc2de72a1ca4ddeaeeebd397c33ce6a34da40d99f1b63ded976b33dbe567cb56b807465dd496d7584e9359334e2d87568dbd90a67d06d38460589ebecd7df77b5bc37f5bf7ec2a6f8d5c6e5dda308f9be67eb459a38e0e335b5f2a5a672b224b8295a75ff897dffa97bda6ed691ff965e5a651edae719e3fcb24fbab1d832e15df5fb6a7dd72cceacb5ebee252d12fb6e2d7767ddeb75fbe321dcb612b4cb267bf4c32f898c7f01e5f79cce5c85e8e392387237b99647fb9c8e15c9fc7575e3a5be1204c2d5bc1308cc3bcd5ddddcd567a8b9bbb81f38755d60fab38ce44e1bd2e0fcba5b93beecd0d3959fbdbdae9755d17dd7a46f3ba28b5b9e6f4c1412e1dd44773441f162439b82e33daebd78decb687fac8a35f3834aa26272727e769b968cecb42af4b87e5d2e91d3d2c2fbd218490edeba7ec77647dfadf83fc600ecf1787e7cbbada2279299761d35da36218591f71bb36f6b2173e5b9b918b6672aeebda3c2c6f79f7e5837b5834a51a9f0cd7c6eef00b9ff7b08dd30733884119ecc561c7b3b13e5e97a77434dadc534a4797f591d3316c9aebe076530e1cd67d7090abc6ce589215c16b527b71f8cb6d4ff491533b7de080430eae8b03839d2dbda8bdf0251d591f74bb3e0d21843ee8c8457e7178cb63a02338a22387233882237b45f6e2005ddb5c9f321f3e1e161f0c1403e7b80193a1d14b6b79e8d4b5c94f3932f8fdb1c3af5bd9b874e7336b94e59037ea6fa3ee9c86195ce432f80eb7b9ba9bfdda7a34acfbbab875fad9ed46cca3bdfd9a63f6f65faed371bbd14b8fc9ec76f935a77edba38df672cda3df1aac53981969540ceea2adaf9a125f8c62fc956e6d3dd67f3ebe6666f8d12d871ce4b97523187f655b7fa84db6cbeb452f2fdda73fb2c8899cc8d95eebc1071f9ccf2fa79c991e5025d363ecb55c3a841046b7d6dfc6dbb57885653e72e7dcd1e066700e02da8bdb6d6baf469aa9c171ca9163faa183c8f3e8f179b462c7cd7d47bfd18bf41bbdbce574136943d8a3ed896e9ff326b987e5aea9346bf051666fbd712cdac0f675d698539d5a721b68bc811e74f76eec9d23fbeced5883e58891797a45f9dae6fe8a51b3cfa96fd2b7cd724f37f6b9d1a8e3f6ce8734d85b1fce60afc965b0f7f47087bd3936f6fe78e83a2fc540d4af1874d8fb43b3f787c9de9c9c1cf69efef20987bddef67a8dbd269a195b82b6c20bf33bbdfd6ade9748241261d445f64e17f9d5ecf59f96762639887423cfa3bd2d668ef9d8643e3a7c401fed954efd8a9c2feb13bf27bae5ecf7b29cc6ce44229128bac8592342ade41e9747176d77fe656da65b2e9d815e8a570f07fac8ef8f9b73d2fcfa3589fc56a087ceab976aa087037d64ef8fccde9c1374cd5e17f1eab2abcba55f918f2c6a7de253bf22eb135fe499dff9964b6d08bd1cf3a8f1903d75e723db4050b33ef12fcf64033da4976356720fa809b1a2976b8b3faf4dfedcdcc718af2dc29f5684b6a7b558f629d8f965c1e0b798e7f4669abbc9cc718431479f3362b6c78dbc3ddbe0cbcdbd6b94487210e971b6988feed30f1f3f7e3ca753cee6ac95a18ffcf28f7ce29dd996fd34f9e91dd77ebc61de1be68e5447d066aa29d0e774087db61b9f6d3d96ff3c74a239e176e1c3cc210769cfacce83db6d3532e729b23d995b33deaed392834c51431ab79cc6de6e31cf247be7acf6d63ab2d7f4ef39b456e616745a11cb4aeec1de56a7212379299dea2592d30bda4b04ba831c04ea3c9d863acab498e9d16f8b79e998a60d6187b607fac83396dc43ced86b225548a2d5b192bd93da5b2dc95e96a1d74b13225f96a7308370839f6d105a1b7f6b44e88f207cf6a90d69c73c6ed0798b2e6ac94122cc61e9ec937686f9bcf5df730d42ea96bda6cb317b2be6f341482d3bdd693c6456720fcda1cfd7ac8de5d2e37313b5d774d95b1fb3d3239639f53b5f62de935d58e62f7bd9cba8bdf3e373c941b0cba53b295bcc33b4accd3d8410424b5ac93d227c25a6c9c10a803efb92b7971578f903bc5bad20e8a91bdda0ba950ac0fce9dd4a055c9ebad118d5ad5430e6e933ccf20cf4d1a97bcefda5aa8fdc794ea330778ec38f4689dc790e8da2ee9e734ab14de4da36bd3791f366d5d0949e1023d5296d7be176eb63ed184bc7da31769dc73e0db79ecc7fbed9dd85d9ab7acc79bbcc42ec506297d569a7f17039bb4ef3161c0473d933deee341e2e7bfb339fb1b71fba4ef776619697de6db1edf26757caee6eccf640cf1c734a5fe6b421efafb417f676ebb7a59af2f3d2d9365f13fcb7ebbcde2e8ce2afe53edd83f9cfcbedbe6f6f8dc87bc8f6aa9ebd5d6e17bee5b37d5ad727e93a4f6e1732f1d782f2feb24eb7c6c3edc75cb3faa15f9ffe76667b6196b7dca1b3b55ee417f94586ec979d8b48c72e3fe6bdc1396bed871321904987de1b744c5a9bcca75b335e4353b2d7048176692f9176e9d259e3a15d3a0b356a3a3b6bc141a433e63aefb17e797dfad92dbf30cb4f176d551b5272697ba4633e2a6d97bf67c969ec35d1788dadf1192bddf2d97edbefcc9c226d487b0f6b1a0fd9b72361afb13496ed4cc9b6f49979790aa3fc6c730fa553cee4061f6ed29dc643cc30c73d2e6bf1ec521bd22e726b93aef3b42df3dedcf366b968c3fcda46feb6ea943391b42ce40f27baf43bdb6f9d7e4d6ec7b94ea34813703bd5fda57444cea669afa9edad7f7db2c697f39c736ab647bae8b2976967bc0507b9dc28e824772ff59c64297c2addc2dc8a210621aa4f7c6a7ba44fc79c3a656666771a04a663f6326f94763667add7f4b37d4a9fce1a1111bbf4b95de6f26c6d2ecfdc729d9742f270a29b4cb53e9c3baf5b4db73cdb207c915b4e7de2b35f9ff822d7fc3297a75e37f770321f6db34f99bd3ef0a75b3eb521ec3d227bf9a7676e693c64cf8e44e473d617f9adee995e64af4ffce99abdcce52dbfa6bf0ecc47e72d3808db2b45f5d333a7ce42d4a52684c22c8f6dae4feee716296773937f6deee57647f0d2a9e36ed12dc1cbb9118973837f6dfc4e23427f5ade827b5876de44dbc340f18755f4e9c8628b0863b48e1d1a91823bd1dd01e1e82e6e333ab37ba93b3fc628f431460bc0d2bb959526de63821516f0ad5f4d5feca0cb173af852c20184377fa16572733d7b687ab8b262c443874dc40e749a46c187f632b9b100c4ca3c8e33de122a7003fb89ece072853037aa1b10714613426cb9c1099ca8495c113dc10722161431c3a8f24695202351c62863fc68c4958f313e60c7bb95113978f96e654416af7b60c4131050e9c0064f088d41260b59b06224050a2a40a10321b8c0024804a5489121d48bcd1486105599c1156e4c81822e37d08aa042c465be5b55a97206116150d085216e1534c6102d78c21a3b784256c41257114f5469c04f186c78a10432c23481099408376a8a3022a801dabb55115ff8fa6e55441a71e46eae5bbde5d46942a0eb943f1ccb9d3be837c6f6e8d7df8a7ed3272be644772e95e3702c8ff64af73e59d2af17197d8f3c354ada5b5fbae5cea59cc3b9f3ef4c55876339db2bfdd427cb79f33e591eb79b3e592ea4d6777ffd2da7ac15197df49bd8766db38aeadec608957676d2f3db449f9239e6d3a79d42fd72d5ced38418a14205f3ccae9ecae5d4ea3c1ccbaf0ddb726ca25357edfcb089d64602985fde03e69747bfec942ba2ede1726aa7b88773d9fa702c9f623966ddc351eda8762867d8e676aecded384d8891f9702cb7f39aa8d3f1887e391ed11f4e1ce27e66988eb001145b38c28b326290a023084b6071653a530cef565038e14d4474992e40598050a68a9ca7a3fbe351d0e315a425650e2e4d5041852b4284a1821d697ad905480c22221bd39350c2dfd3c306507105115e90420b84286327471249789863c3bb55125624d1c4ab82d2e4000710413002195b44f1052fd4a8d922d3421857f82214060aa2586387e6db2b176d8b2d9a17665c19217c41074f7cc1811156250a0c418526a0f0051486cc303d1df1cb1d20613344ad54e8a0c277ab2d33f8620b136e0b125672b81945476c29620a32f3a74843053456a89914445f6a1686985a81a0415173bc5b4d6185363485135f98e28a288a9abd5b4d0144a5522d9ca06a6266008514601fa3356953a8a5226854d4192e5a97aac3099116ea7cb79a81125ea834ef563318620755db82288baa9de1838a230c0d8c5af36e25450f9e5d0b2345160fffd27c0cc478084dafd33a6dbd5b49e1c4d3772b2966c0ef02833abfed93db32003ebfd0297410429215aaf30c3274370d043a6adf84d030830c3b6c62d061ca81a38666a644aa23919651ec9a968cf0b5e3e1dc69ec784cf15ca79f750ee78755dd0d05db0577dece73d6ead83d15b309de7bce9db3bdcea343bfcf79f4e7bc27bef7de7b8fdd60081d240d4183893eb9870e420821842e1fa0aa62628c3146813a7b2bcaf965777d82f0b9c8fb1e9ed1af742a37d21a955d7a7b8fb4b0af356afbcdfe79d628b6cfe3f5e130628c1c638c9165943c885018a28a1899983eca18e3c90d687a5d9031b2e4286315b20a7690baec6942e6b4dc8c72ab945a597bdc3a46ff087da08ccdd64968696050d9db69840064ce818c31c6d8128d880de179818e4838b21a9a9912c5449a55d1c8a14e63ef297b92390ed248a459cbfebcf11604687892b3d08f1f7f598886856684dcb930247b554cd4983907bd140b056936b377036f0909090949cba2e10b0a90462c76216ee08a579c0223bc8147c418a5452dcbb2229417658cb1e59718e10fa8344566961ca5acd6754d97d1a55b9b8e32d5ca78801eb767c9689d8c02d484b4c74a63e2e2bd1a7dbe146592c3464637ec9a394f6a4e0d154ba022a755b31f1a51c32e2b9352ca2cc78218986f794e8c393127e6408748a28c70e86a18063fe7676196216a60597e39205726860a11393bd319203a95e944f80e86619b740688f3a7ffb2d1f188f223e4180f10a740192d695931ca18e1eb0295103ecbb24e6e534a8a5d11c94b8f598c6e410b2d4b7abbb44044cb7ff1cbc2150e838cf249eba03f971b6b3c3c8f1bfca8ede01edaa815e167a1bc76c84c067d51c6274f10e573e20443f018b94128b9bb9bdbd91d3b37c70dcaeb7663ac29ba315ac8aa1806ead3632a3da8ec9781fecdacb61bf31089288600dd98313289d84988cf0bd2c5b893cc5e4ec1913388c924cb6a8fb19c34598e8cc92491b84fa4a14c99320c44b402b61aefdc706f7c741fc8108394030ef7861d1f69b2d413a682464e4d0e4d4ec95e3fbd7492bda79c1f3f9ed4919d19c598ca4717e6a3ce4f77a7e8ad1171ab9f7e33a5642a169837580099ca0c5b4f3808ed0e9363e64bf352d2c7ce39e8cd346ef465f14191733cda755a3ee950b75ece2e55cf987334fa5be3477b723c9e5f639c1c9cdc4d13bf20a57c334a29e5137266d5a5c14fc627af886058b18ae2e963ce47bf3f7ce45b2ec3e0864e257b73fed61f3f30eaa2cb5eff5b359b994cd45ef39a975fd35f3eadfb482983a0ca7ab10ce8c06000c2fa736372fa5abdc213177ef4e9b9297a5f3dad1ca0a8ecf7c73f787a07c606b1d278a4eccfb04617239c9dba3cc678851ae315a3cfe8d1278cee1ec218618c568c31c2087de0476fadc802ca9429f3113e7cd931896c8cbc529f4ba12f482c767cb4724545aee44a0a6919da2285b2316d46850e88f468afcf73a348bfb4d0f190120a913eef65993265ca00f11edada57a0816d3a3d3343e9dd34df6deb06e5c18f424cd701896ed19c214dace5f4e9b9096ae63767f6e97db4d0f18025b02c861f9fc6004b80510548f33072b4a22628042b6cf5d0a5950a1d5b5d129088adc080f980001f2d36e4783c6f2c2afbc586f82f3684ad5c194020025bd504b580ba9b8b8df9e73e3b831b7cd1f21bb73c8c3270718978c4438f46b8548d557e08b531e7bc34bc74abc62a7fe30f1dac8f16f3e2783c7f67c41536040767e41e78d18338067231e598216def13faf6f8aefc0b7a8b887c28d6b3758ec77d55a00e353ff4d0de66f2841ebabc34623cf41eb26ecc135a838c8f7e697ae8ef5be980e3a595417d7a3eada8ecf7adfeb5187fdf6a66f50935165a20319f901c03c3fcf0d22113d34b7fd3cd7969b287d56409514f58a8ecf71405fe3d39384d025802154b80261b58f5e3a90cb6b9c3895eaf294f1f83980fddc5930e30aa7083f94e8ba71cabdf9709817eaa3cbb4441bd9c85cc43089de301dd6912e81df770ae13f3d036132633ed935badd704fd8797b21c5ad6c264b69055ae22e66f57d1f2cf9f2681b763d91e1346577163fe76957f13321918f4832a4ce6619f1c046a7803899160cca5b2883ac33bc7e342a1de98c4a0bea47f7e61160f1d0a3920d1a1bd4c62d04377d30b153a9c5278e99724f4376a1943c3477b0d7d51d96fd4f22f8bbf518b833306b110ac30c48c419810e4f785143267ad7dd2fcfe78588087380fb7bf340f75b894c9e480583eeb1bf5399d97c2c0c3413977ba347c74cd5e1afe9a328a5d5f5ad3a1793cc7862abbdc9c3b41876e8d7f3eb3da9cc34367ad08a94a97277278d827a60cd9883971078d3bd8a91abb5063c52c0bc94377b2bc3522fed0e15bf02d19ad182d939b973bb85c610a2f2d79f3d0c29cf7058e6a9f68faf4e0c6d0f430de2cdadea0bc5a4da6e9b04f2631153e6bead383d02d67e8d2aa52a1bb8f6e6942a24b18a16b44a0cd2cd42e2854ae34b1b51ddc4bdb5a11b7042a9640c5c308ad0d9186b649f62f36a482c542ffdc817cf4f151009a5fcf62b5d4ef8692d3ef0372797476411ec03d98b82e6fb9b3a41ad5f26724e7a59e3c8083f015eef1bc563724b2a63aa6ff3a3036cce506dd29fa93979a0e27fa73bec2419ceb31dd4d63c600718ec7edbf7ce59f1b79290e7a38cfe7ac752848bac914f4ee07aa1ef8634ea3e31651bb391ed12d662f071e3a6f1f8f3c8d88bf64778af1a3db5b82d78844762dbaac733c62a3a010f81c4eda9a10b9837bcb3261adc8a8082094f1d186487bf4f6689bb82e0f2d64a17735a8443c9500533f15080848889d2bbaa79dfd39dca2851e430c556abb7b08bddb89b4b375aac7bc0326512848962059826409822548509024728065f50378b76a41144f47eefa3464923976d828e8ec3388970eb7c9a54fd0faf98efdace31ed2e116b57828246ec13da07d5665717b21a5dcc1fdf421eea78dc14af57fae7a6bbbfe96e50d3daa3e6e5215999740a9cb7a737d6ae78dbbcc4fa74ed3817f5a9b22d0a70f81aa9fdb7dde439e4b9db636d53f9ffe687d708f5645e62f43ff9cce591db707d162fa64b5983e5957e6a34db4b9e790bab32e77f6fa437b7d220fd23aeed10e3766c93d60977fdece8b4e9f9eb54cedcfafbfeaef9487ce9b8d927b40f8f3f2aa6e6df79b53a3a0dba17735fc3c15d63209d4dd5c0efa37efcd3b772e657a38cf0161e99476c6dab434b57620730cdb32e8f465ad4dde7ca8f7c7bf1f2ff5c3c3793e6715a33925b5477ac47e68d4e5cf993cf109620a03350ae32a5c85af40cc3aee318bc097563e9e76031b06362418d800924659dca8cc9f7760ba8cf6aa329efbe38b92ca3f1a05fd790e3a0d6814f5e8cf1fd028eacf8d346a12c9fef25fc93d66abf1ec57dcc040a3ee8f465df7e5871aff3c871d1af57c038d22fdf31f1ae5565786fe39934ef1cea3a1f9ebc448ee212d3f85f2ca9431bdd48f91e935d347be3f1cfb61d969e18f7f1ca431a50284500b844c409805422c10064168f94a4b871e430c42c09e7e5d8e790ef427724e71a8bdf0a96b9b730d664fed6533d72c6639fccbf664ce2f27bdb52297652b6d7b2ccb5e90e97bef59308ba68fd0da2084d9430b5a4db2ef8136daac4f8f5551ba057d6e390fe749cb43ce4b595bb4ac2b8db23cc62e2e97dbc5562ceb8ab322740f9daff0664108535996f352134218ad5b44a8cfdfbba6291f1d6e56e7c1ed0a74e851e74189f28755f1899a10c6ca4c182ba5e825cb43a954c54a98192f6d3356c254b112c60a7c69c52926b718c94037de9856438af958455a0ed3a758f2abe432a6342761eea8cf68526b120ec95ef8247bb327b994e6241f6dcee19024c949d9a6a34cc56ce67cde639743eb3398edb9307725773e53f2197bf90ac9f6941cc3485e2249cedc6942e874e971933b4e1f2f139a9f2e679f6476695ec64dbecbe7d684e67ba45f4e5f866d975b9b7bd2668dfc39a556db464eda2c92e63a0db5095417cdd196358e749d47aadb68ab0f67c8e5d3350b736b9b7dc2681ecb1ebb1cc3aa3b38b31d7de2c6e13056c258812aecd51f50eab2991996616666cecc903c8783d438c96b3cba0f0e526379a8b157f5354eda48a439493efd6942a60c5eb3c940721f1cc43dc95913429a21e54c1f0f4ba32669ceccf8a8b33473b838448884a654ddc79c999e038729c62d308aa7a599e08f2f27da8705cbe1ec118747bf98477b1f961d6e13830e7b49357eab6baef3523bac4d0c76a5c39adce5cea51c497493bd0e0c93b334878f764eaf99351edd5fc966f39a24eb137fa6642f0e5acd31ad364976e672bfecc50187d5fc62568b99ed21f98c33d7644eb23d735a1f59f4c13d62b446aed3ecac0db1f8dd85b93ec94df36c88e63285646666ce9b45efcd4b47f252b071a46793e6e58e97dc9a0eda90fe6cb48936b8cdac9a1a873de7f9f4fcdcfae01fa8c2f1665c40a9cbfa0a96f7772b2b5e9ed2cea6482649c92c5913c273c667f45269c6f22086cb8b794a5d06352133f6aa7ec6e726a794d3a53f4d88d4794f1342f2998d349db7985c9eba4631741f9db9344a0a89627e6e323e6a6517de9c9f1707de4248a4d47d6d94b4b66bfa7b4e47ee8652956c0196d7f16e850499a72f8b60402faf4baff8a7ede2a5161cc4fdb4cca54ff26a6ef9cc7cfa6d9f1e351d58e47ca9cf38c7bcfa74679ff6fef8e9ae53d5d9de9c9f249fa5cdf5e9b217fe2df96c9ccb47d6f509fa9d331b93ec67a0bd396ff9c8317b35c69cb5213decf0676c0fb533d447f6e6bcccac4fdfc6ec85f1e58c5bde5a9119ab647dfa499753dbc31eff693cc4e76bdab945cd9d2624c36674f6b9f1e43967dca21567e46821674ed3413ae6f465a2adbadc464ea9cb8dba68ab6e6dd71422d769ead3e53669a4bf948fb73e4161286e8a2abe23729d7eda90e9237f9a90cbc23e499f5bdd66e348a7dbad2fafadf6e979dd5c1b82b9747d9a2eda5ce3b0eb349d1bf56b837da22eb75b9f3a75d674c09c3a75dee6762b147ef9e4163ffbc46e5d51b1772b2c437fdd4f299d26b63c1cf7d16f5cb082877f3d6708ea6eee5b7d63ef72d0e953fb7342bd3fc8340ada1fed3a2d64ba4e77d6d716dde9a94f4d390adec2434c065bf183bbe0157b6131be757620f32307d7c58141865550fdf9b53da709998fd2ab6e97891be3341d2eb79ab831df73b9456b755a4775544775347a23ea5c6ac61daa9170a919eb1c4ee6a8679d3bddfa44c9f60bbefd0763c8781fef7ec0841b3ced0c0b42859f05a1c20c0b42e5cf9cc60c0b42b53e734ab3ecda32f2f7de7bef3daf244ada2867a4edc21f39d5322c08f5bd1b3db7198da037997ec3cbb73fa0b7458b4e6d40ed8f4aa39c174810ea97e55b08097f9f96ef6ce5c5101a7f9f15dfafcab78fe06b2e43ab46dd0e6aa146392f8656dfde5afced2efc6f77f9f60e6a945b2de1c6b7379646b10ab8bb47feb64bf323879a10283cb21c45b4a24f7dafd7f1cfb77fce2ed55f1e4e3fec9fcdf98205a1cacf9cb2f6dc6923672d74b4b97fdba54f133212e21c8f3772a7091955c7cea8b787bebd871a7555ad15a9d6a6ba632d5dd4cb5abe9db465759bfd65528749a823cd590b47815df32fe86f837dba5ce749176dd7a46d77664de6db697ff9766c13f2fcdaa4cf0dbe9c1ce780b8b7f7f4b61c356af47bfabea14ffdb6e870b3795e04fb6873fad4a71d6f330955ff3070e99b3eb5bff624d4eb6f555b861c50b783a59e5e6a3e1cf639eb893beba14f2cad84524a282574d041079d73fee6334969ad94485e8af9b5e521c77950ef098aa9d162a04fd6c80fea75432f85e370d80bf0ecb722809d177f723ca01ad5ff3a30ab32513c237140c2e0e59708f36eb584180e08ef564b34a1bad51240ff1c3aed2c43d227e61ffac48ee4efe959d5c57382845b8f847e1d986737d228e9bcc3fe837ce7c509befcfdf18cbdf302cb187f4f3c64af8b8f0e5d178d07e7cef1905b3b745dfe3d5b6d50efd4d8a06ec7c1f618f45297b7c72cb63a87f3a04c990f258887af2d304022b4181de69d3f302dc617313a73304a6bc2395ff374d29a97ccdcbc309ac12c7bcdd9cc9cc368a675114191e8358b5658e65ce634378a399948a4c53b1f0945f1ce6bcddc767bcb3b27751425582abde6526be99373326e34950672838c1b63bc741163a88b972e2b8e420b63e1a076e39dbf312f8c98771ec320c1a1afeb82c30f1c3e87bbdf83304a28e56b96dcef4118a3b4a065bd660b46292d6bce0b8318f69a316b5e1786519a6950d35eb386d12cd33491685461adafb9b228e3d128e35a332691322ecdc09999d73c53ca9cdbee74353599c3812373394cd0647acd26122dcdcc64bcd1549a8c6bb421fcedb426e30d8736841fba1c30478ed79c638a6c885ccfcedfd659b2668a6c885c29df437d724e803522986cfe35995eaa9350afe91ff4e278c42e518c3aebcc6a9db336ca452d514b9d599fdecc669d598d5fa297b88a43d734f471e89fc7a146ddf9a55137822146a36e8d6434ea463022188d72fea296a82582895f7881e4821a2f84e189366a94c0324208257cd07330b001666666f6550eccc959b143e182bff56bde79e10225808961609f3ebddacef6c916841bc3eed3a7c7ce39e7d78d714134ca55575d75d505f1c4e74910ef9a4f1ac58e2dc9ecd85522b0c532053437397d7a39edd3887a737270e814cd8ed3e9defce4c54b313398c6dc5189a42167883105b5f3e154306a037a78165b06dab88ae56d794f3b53b14f729546b17decec70a65f5536a6d2a777bd6b7e3fcb6260071d237d6a1f86a8f40f48c6ec780662a03155984aa3ae23f30f0c1824bec36a59842ffaaffb42070e2204981b1c30ccfd38794e9f8c74f1d2a58b97f621e7a587465df69977ab27c8b8de79e10222fe79c441a3ba37e74e708b1b5ad4c86275a18108377794847ae79019be697857e5a5a64fc8c501698f1eab382096478f48401dc4328cf8e84d46a7aca057a5bf344a5a5c1e15a137b6b8712d2d6a0475188784bf222f90f888c55fed883234b97179634b9619bead6865054bfa0c6f05bd5416a671a2ab91c50d2dd91b5b3e6a54843e6a57b87cc4ac950e352f6d0606a846bf5810c9f1e0d2b3c726af09912daa8e07fb75a2438d4e61bb0d84cc331ffd47033a55c7d068f9d8425bbe3862cbc77ba25f1cc1c5df1c3e9a3e7e1144e6efcd4787dc4e1332248d51d06b6b083b1c107e3783777714f8066666e6666e679e73f2646666ce396143a8edd7ad74e6bff71e143bf837b446c4a101edd6b539e9edce7b7ab65fd635ec93eb76ec6158c5acf51ca3175684866fc71abb90a5dda14fcfd9fee8d3cbe9e10998666f7f62de608b016ba44fa4a1aa440513c4904fa3ae13f3ef3e23af02453143ed9746e7e1b4cbf0ce774ce88f99fd861d2c6fcb62b7ac1d1a65d99eb34e87d36ed91bb71dfad42e379d06f4a9dd32a2fa3b2428e98de287eecf36e7a444bd27d4fdb143a32e0e0d68d475ab0734eaf65f1cbefdfef8761c1ac54ef36e05c50ce83b2f5cc082bff387a440dd0dad09a35e0672ce347028afc19da2e770067aa9d3c3e12d24916c1f8143cd3e45a73718f1e107268dc23cfa9346518fee13840362553fd590c3de1bfe3290e3b0d76bec358d9cc6de5ab2364a7b4ddff6fa0f09a18e6118f6309d76de9c53a690ce39a7bb0dca833addcd134cbd4e8ccb402707a4834077deefdc080781968126e080b0e513007190164287f9cef1905140bab1968d99e853f4ab3ab6e2a3330d80788be3117d4cb5596077fe709885fa8755fcd32acbba5840e988b521fc53486b423a1be204596566ef7669a5b6632bd82728afd48670c49ac4a2b2bdd0564e426d372d99c12863942693d5433b5c420821bb9e3fac1c2093ccc1ecc75bdd9063694378e49e4eb76deee6e66ec80ea105e1a591cc2fb6bb795d1896f5766f18d27f723cb24cd3b2deee1cd2d445d1553d7b868d2e31cee90e7d7260d0987ac2599d1b3a41bd39bc73eaa6d39a1f743c68d6ddddf035ec0d6e50de489359d4b637138d2aa996b8344557f5b34fa32cd38674661a75957a6f78e75cbbd7fc4457f536f096e0d94ec7c345694dedd2300a9fd62c1241e1326f4364d4febeed1dc13b57594c802ad4dddc19f4d029b34fa7852d65a35eb7126a302f8539f416f35219d551807b385a66d57140b087ded0e7acd564726378e8a578cbc3815ee7d004a024a3de161a9245a8d7addee93820eca6316a1286b0f15b635e6aeb07f33cf4b0090e07ba14a6152ae4320fa14fcbde79f5a0520ed2ee2ef6ce9f7090b62d14c501b96cdb80721020e65f7eaf77cea70372b9f3a7e930a550af13e3d99f3664be11c7831ddb7ae7b21408f5b9733c28ccb8092ea5c35278c85678c86d3c642e3ce432d69887031d665b8bd95aa864efc9351f896ad54ca6cc7d8c047a07b30de6e140a76bd4e7cd7ead315a841ece59abc974f9f5cbde13746b0c5bd618698df968fde1b0cfa09f184744507773e3eae1740ea119f5c74b5929e936bc736b93db0caa740b0140929043049c1f683f1c0f48f3e3071728a6fef89183ce0f3e9d2a0d35208b498c2f609c1a5e4c8dbad51b756976dc549ab87a5743c31fb8d9a8d9d97c292ad70e11be73055fc17c5ddd578c31bae6cb61a21e1c0fe815703ce024e3a1d3a7a38d1ac7c4311c4aa74f5f1c9076e811cc8d64500c7027e83792f150fa8d621e0e1979383a8ddaa119e05045dc093a8c961ad9a18d28c20e195aec92dbda2ad49bf3d0735e0a033f34238d5551f98724e07840874e72a909300e74e8ce3899f1f03a273cf4abc388e301ddfaa2667d826eb14b66f638043d0241a61c19ba4d8cfdd07b808e8146d53c4cd140df800c560fab1a541ec6e66d88982e5030a3813b1eceac51edd065abfa5d47a0eee6cad5e5ae729687139d52985db962595452d90366448d4e831ad5df4e732d97a3e02d1fdda5184be344bfd9ac34ef9c36c48667db458dfeb4a0ac4f7de2a2c68fce75b391eebe18a30aefbe1843e87bb391160a9779cb7b3ceaeb13dec871e5e963f72c3741f1b4b3a6019997cedb8a8a2c2fad4f3f14eba5c0b7b612174f61d65b6684a731ebad34c65399f166f3dc7d41461a36cf697e3ab5b2de3032ca3c9d196fcf7b9b4eb54c471b350afce852e8a59e3c9c251c8a81fa94c501618f2eb5482e3c843b45bf92898f72cb478fe1c94b711057e9d3100ec529e8e89483b45469235210afbcf418b7bcf4cb59b0a0671bab381ed2de502d8b4210a38cd7f1b762d7b464848f362682327a1b63e953f4b971963e45ef4d7bdedc175030e259d3017b69312a6ace1b91801b724337e438207c84e3113d7a015c8a91609ce8d15e1d7f39c8c4f6f2131ffd92fee67c641c692ff6f7fa9bc341d617f5d4a7e8d81683ae9825891a81821c10fee871e580e0f828a3d37498e1a190e321e576d3a7e853dacc693907350a4ba3dcca892c8daa59350a8ce8564f005db642e8e373420b7f99cb474d8b1624fe72978f3b97b57cf46949238dba3727248d8ade43a376a06cfeba3010803a2d37f6e8a746b547bfa151cfa3cb55abe0ab408c050a66d0769a8a249a3ed95c796823ad0e5f38f1c6476fdfc184f5ed3bf4d0291c426ea5c4151cbe77ee0f2a7028092ba888c000010101ed5ce1564a40f1d781d13bf734248d2ddb10315cb62162bae85367e1f21dc3f70f49c38a3e6d3f4515df797647e3345b539fda9fd48e427560ca90e24a14644431e6bf784114efbec822c68379f7c5123ff85e39205cc5f1902e5df6db18c9543bce77f4cdc90161930d74a73e45f3e913703ba687237dda29f5e148d7ac7b38aedaf19c87e32f7d7af509b89df970a45779ebc86f4edc713b2297bcd94037f270649409b89d1dec149dccaa769c0ebdf41880aadb999e5920cd339ff68a263b366d6d34309d7a0f57b553ae68dbc3746aa7f0cebc6ef5d2a126245a1b22305a9b22fcd1f9a33fcd88666d32a7d6c602bc6339ef584ee146d9397e22120e6aaf8080bea9308405d0671b073d1ce9ad49a07758cbc3914eb2a29279a90643174140d2dfd04b2179635ede27e6e503f3f2be2f2fbd572f551a52228c971eea2f2da6c7bc14e60618b8004693017a295725e8a59c962d2f25fa42094408127a29de91feb8bcd44b82184ae802b423fdad5e2a88116cb0da91febcbc94db29d9eb247b4d2d77a0740aab50af49835ce5e14817719697c25c3a6b79a9cca5b315070488fa6da7a82e9d53e052548838ed685ec5b4535d56514015530b72a8f26347f3cb4abc748a6dbc936dede5f5aa4ff2f2a979f5f6db2b2354a8642e722ad4475e371be8d5553bdaa6690e5dab76cac8369987239dfa14d1037ae999ab7628ddb24d8c0d74cd553b636ca0b59140e6d47bc89c3a84b607ea9afdd25e1e8ef429605e7ad6432fb35e3d1c895d8e07f4cb5ad63d1cb87ae9ee46da1e08f483a9f851336814285ce69dfbcc3b1adada7903623e14ab4c1880be75508dbcd43c39203a3e7ace4b61a9e9f19a60dc97d347bfcecb47285ce6dbf61a3573cd25b536cfdabc77d9ebc43c17d929481e4efbd64fe966e35c3464e35ce4aa9d2f1f1df51e30a7ee9cda295738db0375919d92f370a24ff112e9e9e144f621eea354c7a3fd723c18f3a7651bc5369b1ea8634e2d66e7c36977da0478e7fe78e75775382e3a37ddc3716e6daedf6913e81d69731e8e1bfd3c15c378c572dabde3dc719006aa7d39bbf31f2f85b9733a37de09a437a8940a17aa73410d42a6d0ccc84800042443150030401c120a06244251d5c40f1400128cb0525c4c17e85192e32885103186104244040000040066363000843ea9b3b4c0f9742ec3511fdb715dcc66180c446a5daf8e8ed2d831a959a78e7623109b1c3496acb55a95d6dffe3422db64c335ca28445414a3bc66fc9c9271bde153f8f589d9eeaac46cde79503efcef2146fddfd306b88f63b6b1126e3fc79091674f8c2d09ca93f06372e48e907d528199e1f5a3d1e9752c11ed95a43f79c3a2edd95601f21dfd9a5a46f5848851e4dc6052b7ec738e30ef7d0b32395e8073d76d3826eef7d9a98d4331df07a0c9cef6904340035e680d9a687347c336bc6e0048e3e42aac9a42dd77042fc774370e0372ad6c625c3228f7eb5ebed0f472d482ccfcedc738c463dd7eb66d18bdb2cfeaa672d084bd7596d924e823aa2e1cbe7bf56cd5d97b00d0e9a1c1c7f30e2f9f868945ac991be9d0bdd31ca53fb5b0bf456231f4c9e4cad08aba09e4be07192e45fb7796cf1a89ea07268a59e354413e3231665b636443b173d1a2b3731d170dbc22ddcc8ca3a168fd26b02d7e778cd31592e01057aac1e901e34244181c79823caf1cef5a6786da1abf226f7fa3e51419eda81d584508824219a50ea80eef1114d590e9009c0794feace502baf3659c8f5ce28f972b73327f1b34ca47291aa07055085bbdd05843a09ff853e9336457bd385b4ad530914bc963d3d93da9bcb8b6320683c3906d20ee0ced6de4675d86766609289a9e2c1a02bde051bedeb90363a8970cc903d1681fc7ea52d653192e8f6438fa4f818375d449640e90bb84fc9927aa25e57238365f3a94e12b293482934313a50fda0a87ef5a15a690390113ded3e4c38de3cd4965d0dc3a07f693222135fa7fa263d21bc87aebb4de63770dc6a27114dc50f81da62583206ec2c5c23c09b17ed6c8ce844e639757b47c14bda9d85909a684361068945c162a335d3ad24c59257f799f4c6c44b2c6a6bcdfc0ada6dcd4477b52451533f8747fbf32a3d2c956990195b9f79d134725a6c10daa3fee05fb368f6c47d62a1fec60ee7e6ee503b652834bb58e73695028992402a4fab66bfc5b38ba2e5e339f74943db20a728261ba9c035f19737c1beeffec8ea2a0d0a9503a1d61012c40b659361c4cbd9621c55af4b5e42a6a596c9a9765542c88119e39e17a3c64fd3b5cdde8fe227ba273a46e74501489acb57277c45367d974d1f46953749cf5bf3717d3c8583e9b784408c78e102cf717c6e77d7e9308c4d3b14746841e3a77171b4e2c754d8073124f76876c8fbb90d58e72dbc57237219874e7c2d7341c8e1c9a1e4c4435abefb32c4aa97809d6cb2cf956b61a93846308327a023a9dd5b158ef263f16ea5a1c4fb8947bcd05e1e67104e9af95f9a101da722dfb4d0bfa5187cf6b060e686ef6dd6952887b2e64258c8afbb47cc319611288b749bb313843944c8ff7246a0ec4c3b21fd24abd8d480eb3de08801b2bc32459f079a2c316645b49e7a80b8dbf48d27a0993642c47f0a4c1d2670b4a2f4e0cfc3de58a92aeb225f471166c39010d5d8a795004e1827118513f25ec9c635c38c4833a118c4734f57164aa70e080c007db5dfaf9ca7282ce966f05ea49d6b35bfdf81b870646ecd8f73bad856fbd1613c74995f54d4910a784ff9041947eea28c4cee9e40afce921de638d691f09dd1a58126562ed4a13a8d7114fab016a25030ba29ff3881ccee114928de1a3c563695092c14902af1b8908b21a4023d93c0cf34bc3449c7741996a89a8d287aadf59958d2357274855e0dd938b1cd86976547097f761d6df342b1ba574af0da9664391e424f083b89c8e8240e8afb9a1fa38338165bc3133d54b9427eeccbfc6d19237f67d684e314a62b797cb1e346f12675529d1c4deee1e9af5b926db2b47bcdc7c9bded00edaeb8ab4d02ac204e8fbbf441bd1e8d0cdb7e5e5b0b063ba98a20aea40703228ba2f0397d80b33b35b9623637080c773d3e17dc442dec1a81d8a50a7224780db8d57517df6231b12872acf57eee27f839ca3581c520a4d8509feb301306a52981ddc3fe19c19a26a06536f444fd7fa63135cd1778b1406af7b2b138039a6c7e7513776e763c7edb1f8f16945ec8471849baf59a78913a90d87751a4a858dd456d08f34620a2b4b9a1912e815927cdb49532d2f3dc5347c3b16924259538a448a3d048ebac9f037d4a5adbd08e0bb97f3ef14ab5dbf10e06b97e76fa86b5b73e9c0b7defcfb6d4dcd33ba21df9b72f8ea06a160e18fd1b0ea59c152e308209cd62a15b0b90e98324e109acd76d7c3ad5f7ed755bcd7ccf18bfbe64351c14aad9188bd57d595ca7e9d89e2141b157b0c438eb56489495b0de8672a769754c499a2d68c0bf1d8170b2f8c226e318ef8b9f4911d510190c0c209216e00a07724da27a9d9f061c32f966866ae980f4039885bf3e1e75e966a0992e2d29b9077939240f581fa8fcb2572fedcec9e11b0575bd2cab7e2f9a58e8f54609f95dae2b7228810f9077e06cba1943ed1749b6e39d2fa3f509243ebd36f52f9762fa68545d8b3cddbfcb0dc1982db87dfc8be03a66353b812687ef552676c75a79d6ecbf54ab10ac4059615ce673c8f00e8b4e388a2e233e2526dc132614543126a10107655d1cb2a3f0bb7bda43333ed46b14f5468e089d0d4caa52832ee3680536831283c6d9724e5289f6d5bdb6c1ab92956d2c8664070ae281a644a6e1575d6668b2f1c17149eb64b9345f09b8ced121d32408d38087603d7897f79c03bc80b7d1dfd283fb740e38ed73cd8050d3204a12124c1a9a45f0c04f6261ee150860ffa6f466096bc0902ea0f2ee4bf1158ee9b67825d3fec1f3b0e9cd2ba3a56f624bfcbf6e323ccda3e9940335042d9dc2f3a0f06764352c69a0dd4b7f0eddb16ce69a900f55cd9648bdca513289e557c9a2b9e31698e929b1798e87d0d161d6b817665dee02dad475310dfb0e86e64393987c11260fe99d5f376857f2d54c6bc949baf27f14d0ff282da2e8df5c9394f62ca2f1baf494eff71431637775c58bd32118b6f2478ac744b5e00a2aae669236d456369e4c0d699a4ef90259b6264a2e84f5265cac051351886ae9be6f3e418fa9032935c5ceb5ad3a6505c97bece411cda04341c51afcbe6da36bc7121566204dda0439c95c9450f49d0b468da80abfe5b31a49aec97e4e2a54b6beb98ad432772bc14b782f87a0d979cf340610daab40b91ad2435635f4e724cd021639aa273edcbf69a8dfb397c538fe5c93869f085e1aeb0419175d11be8ac820f7be162d78bce9615a2a1752c098e1fb858a138a1ee06a353b192d429ab676098c60de596cba4d1e0a05c2ad310f2660a605b6692486acde45b309631fcaf4dacf9949b3349afb64d3c7c06f4ab4c86ebbb376870a384e55e355d3fe6a3c8cb04b358c278784d515b6ec2c42fc4da1a63f1e05ce49607dea86ef5968b9431cddb8d117454782f0ac31e05f91c025da880d8f598b01a0001d885b1093ba83df35abb380a2cfeb1c3406d7df8479e2cecb8d669415f381250d74c12abe5c3152fe79487292daeae92cb2f14cf15c00b9f1574d7ee78063efafbfe9887f2725eb3b4d63b62ab3e3d6fbf96c1bbf60813d02d4806dc2f646ca86b59c22c482b84851be29af9fad8e37a6c6759968c76a667e829a0ee13d3f12db6690a51cba9f790bb442ed285bf8ee8d2e9e4e22442e50f1953c008bdc01e556946125a821cf8010c94e9bec17610d8fb0231f0a7d160db82dfb9ff3b3a9f5e9b59c04b79a60afd5036ade448c166eec523e81c83502f78076ed41effc97be045207b143109bc03febaeb0107e42c31f54e1341d65f67b53b07981bd8722099d3f6f1368be4ec6f1c3016ea6ac2fcce0ecdcf466af316abf290c7dfa05acef6b8c58456aebb5236acba7d6338554bbcda07d42063ea0451212f3ddb75090ed2a956167eefc44043102d43084d148855abfbf7528290e3236297b9e99eb38b02bc280e0a33d8a24a88a4b4065e6c4696e0e91687180f0ed0703842211455b72af9b2b3a14074681f67ce585970f90ff049d9a4105db6d6eeb243ab936e9ec582a2e6e246f453bc9deb3f542fafd743093065fb7a5e9d4bc9df531679cfd15370c86381ee8b28358105f54553218ef519a43950eec8a3b9c0c59048019a3aff8b87c9fb65085633630535990d92658aac6e30a57e7382494217da16f11965dd44d4aef3174c7d3c9cc2197714541abd4651323783d2f1d6082bb4fc25235c9fd55613691b27e17d56671a2212fdb2488056d108b95ecfc2f568e7c1ad163205da12e969fa7c9e25bb98f9f9ec4ac19606a5805010c0682564e2103bbd8b442392a9c46f0c0c00b73ff488d91d1edd69797674d56bbfac29e099f3649177fe90ef9bbc87784ee8c0cbe454af6909c2853640dc0b7771078809b35dd3169f905b827a9d4bc52c6f527d42d529211b232f1c5e2038629ed3eca1b30d28b1af80d3cc0fd4d2619c68cb704e1771d27afcd311eda8114db528766895ad2e89db3b7993df4e6d8ca7d8ada7e91918b310b9a192db90e8ac969ab3146dd255164ecbe75e522afd2dea377106cfda698e95c5e2c8ff17f3ae8b9eacfa021ff1befcf6f05418a465a8715c5e5344e7ecfac2820297b79e761acd74862b26a07ca63cc8311825e09df5b8c30edb531a9eeacce05e9178032365c4104abafadac85f17f0ccb17f8a4b07577ecd191401051bbd11b888fdc9912020c0eb0fc9dda7bdb120a8d4e1dd7eb5d707f10bcd0bf781be280ec089a6d33c193dd9df5e787f1ae87cd778a52692a502d0bb645a90b9f2fad39e7ca281a9a0b29aac4396a84ba9ae452938de1597c8339271a6c69135d77be8b1f01859e69eca8cfd1bc62007991d0e40b9867bf7cbcdd5c2a2e85f0bfda16e1fe31aed38329faa7bead8fb939f2225de4135269178e3c8d61693bc9ef23c33daa6c58ef70cd2ed7c32a343a4b96046bd9a5a4a1f6ba9bfe3ee0aff2a1a6eb524fc1476c4c54ff580197e65a49e849b927828ce7b07fed46d93e1481cd1bbe6a44bd8933914b3a3bfea43452e4004d6c3ef7eeacf2b5ffb68ee0b809bc79b7cbd81b176060fc0154686638842e4121d35b02e3f1c352a2b72ef908a0615df5ba0d0e1e981920a2003ca7ace5931c3288fcf31b629c05d60f51d44bffb7f41679b1d07e218f0fca03c964192712a01a86fa7ad16b06e375bcbc12a876dcc560f23cffb969c7bc7535d67e4616cf745003be1d086a01be2153e55fc5169543c97f99259abd1a2c4b0b87183e5bc4dcad61b6860db581fbd5c66ec5bf2d455681817d8e46e195c0758606ccb628109e49b702937c4de7945ca31cc2e7392027cc955c87454b3f6d6a8ceda85999908e47a36959135382b1f878eecd82802f982aa4d683c2ecb31686197ee4540452524eaece00d6a441fcc0ebc781ed292191e05c15c4489c62b56432ffb60a1da74e29967ba29ad255911075da9864e5df4aa4ec21d9f0cff51dc3c843195c286364c23497da4a8e3119880dddd9e2b334ac4c61628710951f972d4163335afd6bd63177b1d13139597301299f6ac8669f331a6193c884c9d6de2c423406b859e6de43925c1357054cd9303721be504581158168ede5ab76a2408cf06d5852fe5fedee6c3d468b30a7362515df5486a5cb9cd22882eb8e702ea3dcd36e6fd85430e466fb70f94c4d651c43f1fb332bf1ceb49d3311338204d26efe605d164e0f5c2c0967d0ce73a7b5d48affa0ba1bd22ca25f15a0a2022ec7e0156622c409b9f3202f5ae1e0392e048401901a595dd13a16ce15d63ca6ba919c26a087524540426c9a872531120da0cb16351a8f5bdeb3278400306ed9d06126c9f00d00def57c497dd24f20f8e8c67bb3ff900bbb02885c173ef315e1dff018ca929438c674e9b8ac8194e24934e2b0ce4d84fbf031c2bdc6f8467aa763e3dd79e78427d69f03f42ef2d0d675a678870fbf722ebad4a67d370ea60659b7ff6faff16a00e4860a4b6d03f9156a19161e51363c16ed7e583074b1f738fa3a28e03c1958309e6cb812ec215a01f7c44c11f16dc3f61337bb39bf9c446a9ea7e6c855e525ed13e85666f32af7fae3c53e452e8f6d4af4195845da2af5e9d4805f172d199625510ed6f20e16c219d8e88d7cff84d9b9fd1e02c43ed56a36c46c18a0a518cb8e64c331a81952f83468780f2ce44166240fa4e36eed48d483536aebb5869973b5bf05329aa82a38a05f05b06bee89057e9d48ee74b20c8b70a5075abd1069eacf7e661a8c55690a0c7111acae5fc023456ed28f52ee21a17af675e614321ed6f1ca132d59570583dc387aa8f602c588dc064124a3b5d4048a7eeff3ecce876e9f9289845130a8c899b3dcc90416d789715527629c3902e2363578396999d4bd9997bcd72ea195c81a80d58b99ac2f431ee549039e4d62c64148a06f18e69884dfea190e084017f6f83d2611b3e88c6d049f09c6a3a3704a009e1187fb848b52acfccbdb4e9a6890a65837e4b89ed671420338c66180e6669ce0a7ba19128a84282c0564f7610341e6dc824118d9c70936ce500d8bd4142a2f26463cb87829733598747dfca60680ee6b796788c11098fea8db0c507c4846dee060279a6baf8b8d91327e046db6ab8ad28e9dc422f265ce0505319c86cda289840a7bc233236e7a9d015350ebc80dacca853c1eeab3b372596153afbbe872d22d6ef838b419dfae1ff6b84d51a181ac53fd16c7cdda05f991c98ce91b789f3b1298f7c6b5229a301e858f9a87ab2f5516288a559687a63186dbbdf23fa37ce95aab5547914584067a7fce882e20fca2531d44eaca81785b1600e3a2a4012ce374e1cc6b55a1dd35105654c0ba755ccbd04657a8fe733aafdbdee17a9d88118fe26c080fe23d1a16374cb749e4df55a62e4acd80833b659d694d2d424ad6478fecef84b8f213f1bffdb6e87f7c923bdf669f1d425d7e5865067bee2503e23da2522e3e1a455565a06197e39c5fd5326862e26144d114522718cfaaf8d7836f953a214d894af82c89748732238d6f23d89f08dcc7ea8df4e3ad189a6844f065231206dd2d14650f713b6a9590e675dd817285027e38d1a1ed51710a3807c10311782619e46dc7b3ad1cde08bd84e3a71ed651931137f67ba0f64cce1019a42a384265112a6c90f8f0182c4cae05b905b96437409f8e396f4f1c31b4488ec4c192f306d6cf868bb255639d08e69a651d1efa2940fe72a8a92971b07f885054881ac3f012f162e4f7a09bf04cdcf999a3ba322bcdde74b1c68a4fcad442a3ade9b94ad088289507a2023c4461e67f8da363c9a9bd35114f62f6c95beab24a47474f432e05914c512ca15a630931a9828ea0a8cc76101908c37591b9438082a9b0d3b9bf52bbdd571e9cc82654011fc059244e0b34feac770c0c4cd64f12e57be500e2c4173587d9f156bd4d20015502e0ca1a90c225ee5e6464fdbcdda503c6d2bfd38a5220fae1d6a4867457e79b69cadef36ba59ec382c5b8f15d20f3ee95ad670f88a2061c5c333fc65cd163cc3ff89d08dd4f6782f6b248a50d7937192517ab6444e1de2e0a6a83a88ee62d0aaa0784f1e436350821debfed622c61435572b16fca12bd9c817b67cd9886c4f7763b97077a840e8c6583f4da3a3d06d339d30e8ca662426124c14a094769954b2a0887d3bc3c60dd0d973b54d81b657c6d03872226f8052589c57646199478b43cb0d859447458458d4db0615f415e570e145d975cda06ad4b6689a83ce7a9497b3ea94457c22275586dad09a7b3f54d7c556e90ea374f70a21fdbacbb2399db4ec048a9b186cc8e7368977fbb8ada4d0db4d891e74a339216b21a2b15e47592e656aabfb2f102b7bb4253cb3cfab64d2cfa0a349db843fd98dd908c2719bd9351e1478702a96b6e8da99f13f0855b24f31128f6008ed6100ae60ad43e2a10e6fc915a4f3244427fef996823b94181319581f202e83aafce3ce44e27c918c0f7b16e58770e1cb4c6a8893155ed48857924ce667b93d7c7eba0b11d63db166ae73cb62aad0ec0e439cf91c984d9ff7cb91114b935d9b8b0c5f366322a8b50092f6aa09ab558c5468a086d676db61f3625f10214336dc22cae43a07d7ce399d84817ce0a292a0e95a95783067a34eb4ce51f3ccfb0ddf8d880197f340b8383da0a2148bb72f8cff5f7d49a58148626d7494caa227a4f7f7f608c08f822d8184fc5f804238fcc587d11bf4a14646bcf6528bb7d1168a78fc8fc14f9a57093c14cf71cb983bec8a1c7703a252ebac13f864c3898dad88f454c75803155fc18a4a50f4970321059871b1fcbc37c842a67cdf88a1b7590f6227f3816ae3da0a43b65b5712f0a4eb7b20b8c69368d52257ebc33b4edfe0b0a80147ff3cc6a151e13cf082db30d0521a0ab20bd241bd03acfb0bb7a0987490a349ae9fbde5676e4b77117ab613793b2bfd6e85cf74f77c97aefee93fe155f526a548da145a362a970d30aa10319d1b0281738b305b88f546277c6b30c260d81f8cadf1495233a9140e6a1c8044618b25055fb124975b69b9dc452f8e25b8e80b024b0b4de2303b9c97389bf96e4838b5f564fe36599900a885e2ac39d61b19d4750b15806508aec1dc5af3160e5acf7412bbfa93aa22a3e070de87f4048b9fb1d4cbaf3e6adf8f6e0c50e6af8dc136ff6aefd14ef463f32ca35f9a48f257b99317ebed25d7ee7648e5257f89afb788a043fd66c5e8adddc3d61b28cd149b4478450effeb204de8f3dbf0a6d911388411ac5b65b621ea6d4a904d9da11782ba12749b3a2a81281dc7b0832f2ddeb5c11b797af091e24fb325e4c41f83b8e54cc748c19f41ff5a93e8c461911d69639bd0aeff6739dd627be2c7d3c15de41a9221aed853e84e717c853eae03f92a66b99788c24eb86aa035b9923d90f9f6a6eca8ad043240108e116682af562a245c7405e583eabf6273dee3c038ce4411ef0c080c7ecbd005c5ec181e9b14862810877d35d0d36e7a9d2bf8dafc485e2d3d261012f5c83778acb8b03f80731d4e549e2d066b77f820cf392ed14c4dca2a9a0ab9fdab89c2a1065cbddd35cb7269fc4704702e6a593adf6b21f5592400e224621aec4cf607ed5508f5a6da5e5ec47faeb17840d628ad97b78973d42e4c656be4d36c5096942a733dc8a21eb26fb29fca2eb8f8a9d5396ebc3cfd4ab92397bf37ed28ddef722e91e8107dda018afa4169f92674a110d40a998ff7de4597186067baee0d30aa18e8635ec7c369a540637295263c34f25634ec52b3483e5091432963baf4bc22d42200a9c4eda58694bd8b6e24d22238a1bcaa8e89eb524a53496bdc5baa6036be60d55253e932603903711e4b1a974a1a2d01ed82f3dbb2c967985c4340208c191402cbc096daecbb627cbd6b0d0c7125966c527ab82ce6633a1f45047f248a018d2035855028a3ed3a780dff8b4521adf19be8b18c99197fb04d0c0903134fd1525aa7b7a3ac78430ee792b066e903219016053bc918e16021e6cd623df3ca02b99f4fe3de7046a78a792daeb363e4cd59696ef866383cb380af2db2efa0bc3943265ff369d09b1b7f9fdfcb01bdc0d1295b7dbdff34ceff9436f000ffc88047cf38e0c6f04d52eb3de60d682f362490fe15cbb0a9d4eaaf8390fd3c8e917d4f37a7f3515c31717280244cd582039b8e663d08fde037f507bfd19303135f456cd443b1e999ac22e7341d8c701d14fa8c5fa28f3330af3ccf3af17d0e527e03da213dd38e494d16cf3bdc31c3091e00ef52dd9157fe57bd553d880d98200690ffb9b24c2fe2ef5c213de6f5b0fd9f5757643ace8d1b84e1fd3e05c62cd41536fafd375c21ad861dbe56fbb77b32b22d3c985f2268d9e5fdd112f73f3aed3456d894369f51e47e1d794bd6b39df162e534079e3beee1e9066815b85baa09c5285634a23fdcf794781ccab03e99cf8161206d8ef51464ec6e867f8841828e6cadc64166fe3d7205c361e79e58778e4f0224d702b596b33e5b4827cee9d798b82354970e41836856dcfaa146932bf0a5ca6c6b351bf9c79a79da7a5c7299f4c711095f245be4c8c2f5ac7035dc6690ab2dc9bbbb187e6db7e684909663e92c3796ef97bb321343d790f3fa13c34fe524043d2ffc33235cd42e30535a0c2304ccbb32a75ebd2e21d8bcce4a56bb324f0e4ba1faaa082ef552515c400d878f8fb0e110b0e85c139a2f7af7c2b7aa8b49b065e4e23d0cdc136699af6a63363d4473d97d1a145640fb9b17d112b76088bab1e758a62f3ce6055ef34a2f0a807860fc8930516ef805dc13a8c62dac18a1d6f48b72449fe0cdffe181e132b979ccf0ae2250ba608aed691d332809247f69841fe0a1639202b4d7f415ce7eddf9094944927f57c9b143cc35f4dc5821d30de54205d844ff9664a69ee4843d82048bc4c0a92b3b261e873fa037713926b5e5d829f1038379f1c73ac01d48dc7004017a6b22b2814ea906e91e0d0dcd3671e0522ad82bade187c758c249a3976acafcee4403841966880ae9402e753e93504b1e744969badc2d23927d4d8933001de4e8fe23103620559a53839854682e5652dfa6b30b1a5c8e775e8d4f48401d22cfece588bb610fd1e5f4401c702fc803f751d0dc9e8e568384d0edb9154c22a15a5cb647440bd90931405871054ca2abd512c78d3a9abb480423e5b046a409588843118a1aa5ee45dbfd4cd8c80bb34c2991856ca1f33f83d265e0b9499880fff299af2fbb8c968fa4956ab9d3e02e7733c9b71608cf723ee6d50b2d545d4c1bcfdb657fb039d4c4b5bbba4d8f66801b742a3146436489360b0152e40414f0044b8fb80dff2876a9d12bfa8cb8dfc1e4a0f9eb9b3d54eea29935473324807e8e7b8d604c8500cbee8d04d8624d7cfc140ffac6b64c24d93aed18ff54038e463e281a1a2b1530054d5992a4a128ec26392a9ce00ecee235283c913181b1e73643e2d457cfed09f3cb95635408f34e4f2df4a87174712493af61d830eafb7cf2a3f9655b22f2a36e3cff908bab1d63d5fd306090e0111c510b94d8fb044e103ca0a1ba3f4c5142c0fd3fc51a5449f541e60f60c994b192ed80a42f52b6026f1a1130257b318be17fca00f93de8e057c3265c0bfd0bb858304705cb4d4881a54a93f5d58918501d000152717e1445cf9846a8f74a5325fda4a6a90ad04d98887488035d1994f3798daead6a6381d3c69ea3c6446169005da6559fa6daaf99cd229047711988944f7e56b4b41e50003b199bfd04457dcc1726c0f8033e8307ee00706d31027c6c048bf45f43abfa9b70667f28c5d9de225641bded41a8d98ef813544f3f7db91adde1a1285a21e40d4f7c4984b00648ddb2b31130db09569b36ef9d173122e128943b1f3a489a60cc17b1ed652a78e9e80b7b98b512568e66d94204916c268f7e5e5f83b7bca713955df836400ba5599714a0aa8a64932d7be6e6a95566629686ab1d8185f72077c8c4e5f84b3aa44ccabc181abd5cdd1afb085952caa817c1552b3df6af11bbe2cb3e6eb275fc639b8d9e7a339bd811e2f23b2442da68204c03fd297a22c9ec910e6583ffe256f0f3a036c1fb874ecb678c167176d44d223fc8f2f997daeeed3a63373978f3e93b740a8a23bfbbc41d4ac6c40b14012635ae4fd2ce3502469a5d055baaaf2ac4659b4f036ddccefae1e96fc44bae55bb175007facd9ee7ae13d5e3881642577f5057dee6b25c5f2708efad8e5954cedfb5c27b5a581864c788fd102ce6cd5346c37ee54e25fd696b07a2ee428c7c084dfa22ffab92102c135ec1a0949e7b3bb319cdea7f445eeba0a3b60921da011d143ae3c144ccb8c27d20560377f5b8ac2cf6cc2d745354548f1c1df88af27aba7924a7df88df3690977bd36b1e53ef0f20bff181af92dfc5f11aaa8bc37121c6944447749951587171dbe4c4769794e76363241089635981cdc7874b8d970f144864f99986b571fd4e49ca3331fcbadcc0be602d21a92db2a4165b2a49275d71900006f63e043a5a5537170d9900b4e9c690f785ed6ad359a69dced5c7a4bbd7869ab2146ab5456cd2f238d9b5b5277b6f2f807140bc1ff0f81ad732b40f4aed55708a75417556204e0ab3a297281135c4872a3dfbddbe776d5402536d564ee71cbe684f71c4d486c008f16d315c6a64d846e6f0cdcd4aed83dee419bad143e6cf8f42c6654cdb485c43e9ffe670ca1a77caf2d83f98b510e61acdba00cf823bd62d7aa05be8b633ba752e4012ccf8017e0347fe1030c2f8882231a51afd3f0bafce79afc802904b1854c2ebd3d7053bd08340597efbd32151e971a29ba6020a6fe5468b18b0ed92f34a2c5908ac2a5082292d3fc780751e8380b8bf04e06990a6b2ef974c72c7d72b878d7087d37c23b88caf1c02c04d6a01441a4582cb7343502d7980873783b726b2ef76ca239f35e20cbe4be1230ae3ffbeb6165121d58c15965955ba5d00f055e8c92e57719a5680ae9f23c8173c340dd59c9a1be3ea76767516168bb60d8a635c00bb95b4dc57640edaae56c8bf803ec6b0138d19aa852fca1bc170624cd3444d2edb867965b2604dfe163ca174cb164195d3506e4624a9e3314b8b21b639badeb56b34f173bd18e5f23a266bead096daa91792c2c2bc8b3c8b7ab78fd21ea2cb51171b2d3821a5f987ab1f769c13a1badf277b605c5d3633f366450f5e2a6d82a77cab7aed948af5bd76424d985517515a836614e919e94a40d68496d900077dcdecb913138be10923795415c8893004db4990ef8248f613cf0fdc34aae60b903bfe3d8ed0d653d008464635d2b0ae8c41a1c0dc4c5d85507e72d39d9aa72fccee4d9f24967c1fdc893b0644a742e6f9940992f2182770713d207ae9ac71b9f5938c2a8f6e381cce64986ac0d7d4f4b7f8541018eb53fca9b6438161e9ebad82703355bf5a01dbd758a5713f85d311c5f0a904a79ee20fab17b12aa54398bf65813ae3e80b839f8adb2595b1d1d3c8319df12f26d5f366f6bf5b3494109b1471040723ec2ad9e662e745163243929e3f7db6480da4bdc83b241c9db00b5bac9c542f15f93880a1ea9406cf8ace8ad9f18fee4f755a160dfdbf6e9b869082c8942249d6ea4fd88cbf93b5f69ae3e2599a1a551702a7a36d28654e952d6818a424e0a8394d6f7385f6422e55736e5cb8f0e9962c80566ca18fd520178bd73dc1c9408bb651d8e0227ea397f8a810fb6cec4ca98b183f4acfea913f362e6a82697d3c180fc7d360c638facf29f828c1a3931bf760c9bfbdaa49cd1e5768645e164b8d58213163c2392e2b725d4e4b6642c6ef616a5fa7c175dbbaf1e132ea1e92aba292d75b1dc9401f22aa6f651dc52fda7e03ac95705eefbd17b41ed733201323aca4638a76a18a62377300f255e96d839a499c399dbe7cfd967b69a2b9d8e59f4bb45936836ed8a0e8be1050609abf40a9ccc5ede1795ce460c150556076db2c0a622a6d38714f91df04dbdc8176ba9a39548d83d5d76127981e2ed85e2532a5938a21d96b81448e01a9d0810370831dcd1138404d5e693985910143a40a03597ed2e2fb0be3cd2336fffd39a8143a69d0e8e40d5a7bda326207c8fcf72317b061d26fc272acba75bc89071dfc3b0ba47217adf521bf7cb16ebd9ee787290e842368a702630593c01506bdb26f55add3031c10dc6fe7e0ced9074010844f09c3ae4ce4b4214f37552941083223e74aca24ba31b892b8b48998668343bcb3ec1424a9e34922515c79b68e21cfde887973e33111372a494348e900e5faf65b9ad38f213840810c3ac18f2954c103c3849d02b638a958858e8f3500c0e027e04b931b922253c2e3c4682949fff97d5b984179341b1690e8c69df08f5510a2f230e97dcb890274108c4878ffbd9789010a2035f5c0d3cad93b3ec5b6fd2b86ceb2b11e248fc0a1f5efd4ed3c8f462b19f02f6d501405bee512866197d87fc1c941e0f60e09a0b91118a2deb60d3d26046d91e0430f2fde75d1f6b99a6ff06786e44d25b6833452112df3f9ab6e70d7c64a5a91d39662471e1a9b9853193ff5db30f3dfe4c76c3d6474a2209aacec77c945930520a8d3142c0fd061e91f1b96e708fbe58d3ad3fcf7da669939e80bb2e8adb5b4b678acff67826bd625a3e24db5ad0e479dafc8110c68eb7a54f834968b58aad5e42893f83a7a196bc829147364ef9c9762572e0b50756257f4dc0fc3ce6771e0e95f52cc7db7abfc4a4f176febe250a3cca89ddaf931f825af13b12c7f255d9ad4c6335d5eb08917d2e5c706099c5b08c407cd82c0969016cbae79f111d15ab5885eca0bc2db9bc8de52b549c30ff1f0d3715b6c188f50ed2432bb28ae5e65e793cf03484662f8471ad3fb011b7114efe31066a89830831deb3a352fd39c64bdbdf663f4c166a854ea9f867ac9ae22fc36732baf8fc0a399e3d6aae2eb2cfabbe97bfb2676baca561146ceaa00cfb5319d182bc924c5f87ab27bbd33e1559b5ec4caf640273dae2a16b464782e3953b6c8b4de1a9e42f82f001851681548c4d9e8d7a44cf02a68b56353057e2f12d348c615194a1214582ca72bbfc28536ca84c4a808b1588380ab2f3f2bb064625f8ed89d03b2681db8d9fe0fbe496f39c82e1f4e4518e983a76079ce5c1f75d2aaa83cc0eedaac518e542775453b80726db5a88376202c622e02cf8591f3258a943a85dee0948aa8c4af8058162555aa88c3b84ed112f7b2aa1a4284923dc2a611334d7ccc6f0609042bff50fa9112a43a2be8cd163c7a21987db5ce78cbd4a59d2c7396335bd228fb24df91b18a2e65082f005c1d072d0a07689a8b739246893c13a2ef2d6d7625aece84942c811998556f7e88319b765484543af4777f84ccca93d61195f66fab0f0ee61092c91e7f8b9098fdae7bccbb47f78610fe5d630fcd982a523829bb3458eac2fddc72d439859225e34aa458d5a08cf4108b80020eaf71dd3808d574110de082a88b9d871bfc34d0ba8ac217a039531b12872b704802dbfef4f8594e90f86c0e4cc0c6183b6ab4e3ffeed0aab87681f85b98d14a51072f890619780b57f65a3635832527592bf9193aeea76697c58c1c014d90010f0bff32672894fcbe0092b59245f22c2aef6b995805713e0d6d2497c85a83ac9247e91ebdc830175d8e47541e0fbd3efcebc107be151dba137db4b48f6e508ef272056a14ddb1eaa62a711179aa7b9a349ad250fa9e7d3738944e8bc4bafff6d81737d8f62dc577aab7c1e5f75a592a9a6b97512e22194078bf863119cda277351fcc3d681f95a8c304005a8a6bdc88f950ac10a2eae738a661e46d66f9d5d9c466cfcc91ce3db1112cb4fc63f237d306b7e4e7aae2c44ad13649a00de70eebb16b013184790a2b84bdc1fd17062c1a8ab6cd166c0156a900d0ecf2b1d7b17a3ce8535d6e86bdb25b2cb842c86743cb1baaa66e61467a93adb64a5394228371829b057bfb89b95ef88f0e7b344472087ff7f00d87e2337ea508ce5f4a47a3fdeff6750f2ad694de40672e54c82048dd66b2bdaafcd60dbe473f3fe6e6dee4bebdb9d6e3935326e7f5e7f0916ceb99f3342a5b02704255dbccfedf8d4b0c0cbbceaaa9d2d1f739670d861335d98346d52f14c9ce884f81a661f3d81c728f2b61077434e06e6ff89d5c7df84e8deac6dd0e60799e7ab7ee58b2a98ab9c051477da623576848a5a487a17268d7dcc2121d3f2542ff4155124523a1c039263a0817851566c13d28397aecb5e89c8a3115121b0b265c39aba7c4b35ae1f7a477b5b31f0b6858870faa76b9a303204e179739baf32e61b2f5ec87cd320a2d54295a34fd6d686b238e1d4e4d0e1a12aa744c26aa9169be4cfa8e4bfe125e5f0067a81c535b87cc05ead182d158bb25f24c4431e6c216f74e3ec475dd826b745367231944a27345cd819863a20fd4d1b1d7c66c654112fc740190fec8901f6da6023e48d0f1178ba3604629dd4e7610e241428a42217cc539953bda3202948c0c686ee7731c36a56f1517b4c7eb1836c1003a1654969b04844f9598b9b4f5eb6730d434b2032fa78ab8df5c9cefa8f82d41bc41eeeb5e49721a208b4c7911478d828998f4641f4f1354534815cdea8148857977a82f1b8d0c3a9cac2c18566e41bb4ed44dddfad41c7547c9270558274db9eb34ee1ebc3088cd4fe9d7eb92d8294c7c313a0d9782cc4e38f9264cb69cb733e5131afa477fa809648d8a6e95099719200bfb81a6ff9add671ae113100faa14692fc8f0eb533d87b585a4bae90fb2a733c6b00413fe050d3dfa9f22c424431e8bd9cc24c9849a0948d4398bc1f332b4915213c9e63e899c0110790eb082010a9c2cf20aac3c1d7922476e12c595a6592c58ce972de6af0a869cfff56b3ed053a10f238e157e1e0b6e52ab10e7215889809c507eb924d9a4f6f20ae329a78901950057b1e0cc801c7f5aa6a3437717e075dce3aeb8feb54d4862f6721255c4132279bd34e986e00a555272d0a4411ef37e7c44da42c234e14f7b6b13741e1fc482eb55c730e944a4c9dedd2bbb4424aebbfe74a05ac70227f06af33e18d35f0805b78ae65579945d4ca6b941adca8fe80765ea594f6fd72f4ec51eb8d8bb3640d8527c069df25f2b0217cde775f3eb3f572b0331f23f78af4510314f0153da6e055a375011d8973291f7b3345b8c147685f839b82d4cddb17ee834490c2fd33fb6eb995b20bd93bf0198308353875c1b01d016f8a35f6b49417689794d608019d1dc0ae76b092155d32ccdb2cc0b61fae1368f4b8e4520fca4e6c371c4d19c4a1c299670d2f23f82bf820bd4fe4a59f31ac3c81b742f99f47bdc8806e77a52f3158de569f7b661db0fef0c90c15b2cae36c6041e0b8f5785bbdcbdc3c43310c0b840015ee22f97c1916d9c53a822bd0e382fa43aa1bb415608006e372110b8ec2f9a2e25c23f4e7798f24311464b5f2400533d7fb5074c5d48b78e8471c7fd87cdaff7dc5d88f6ed48eef2c441fabee0a8b20e04a4ed150121b785a97c801903af99491ded17e86f8a104c4076094bb0d97ef0af6f145bd3f98ccbcb286812898abfcfa351022c0790e4d99758c49722d4a514f1128aa1fd3822a0ff03e5f17d947700491bd750aabc8e5f82b0aba5da9a80f61b3202d29aca715be5411225bcca00d2f51bec0d92cd803d1915b90186d1b80f58840c1fc8343df45faa4bc6bbd4858ef732477154b31eb01d57f23f3e0d83a9f580fa2d2904c46cf2a81a73b8700b81488222d141ae920cffd5997d2d6c1083733882d73de6146dc9d78010f3e75546405d0c10d77a52ac8e78b0410b0a997671a896a590fdcfb0f9bb2005f53cec630e83163497f9782159638d0bc9d270cb32ea16b2b400950b706efb804d6bf28ca1f58ce71be637419e1cb1019d2e0bac75566bbc67de2ac9b20851d77189b6f178190fd1285cc18ca6e9a48a129e41f1a0bec37e522aa6ded9faa7ba70297536d0b3f9f10f1e579a58635def3990dc5ba0571a12797c167154455282f02ba1b5b19d3f585a45ba7547ecbea2dd85a5a8588e7fe32fe7b8ffa12348320cd241bbf13971fd6e252efb9d0d6277eb0c103a7b3c305e02cd489ab5f7b79745d91ce9dc94f5173b314552275086ba9a501c1cc298bc0a92352b70412e951264ce162dc4d55dd1c38c374852b4b54dbd10fe48fd3ff8be9039d6a155e5abffab3869b8c4a1700bf700cc859186b6f7d16a5dc21efe6113ad42cdfd7d73999f27994f40f17c7d818bd26b190eb94a1ddd0c6c291425c266886108ee48f31f1180fb28544dfd2141650da16453d6860335cb1bc96395cceedc9f02e0ee68b8dbccb0f5a8ca73193651615039e585006544858d34f5efa600a910055997d2cc4cf9fe05a9705dda8a9e3d940c10fd1484ba152e5f115cdadbfdb7435f411d0bacf82b187a2ee042e2600a5988af4075ab98893b80d5808adf4a05e26070607dbcfbfb8cbbd9cccb03ee55c41d60f879cdb161ed640b1a8a126649eb80484a5054657cebf96884d2f8af4de64452de38f5ef7d4a288ece2ad224de008d8b7f4939c4114239aaa23a720e5b0e22fd68bb6d25da6f57090c4a9c62e3ed457682036fbea67381b2a152ac60ca5ad1c20dab371443986c75950897185d932d4de893d2c1cc35b11516c051b22663ca875d0d81d48460e1d8cfdabbdde8cc8f9f286c88087a31c41b1241df85c617a0e2216664379ce1f77aed1ee8c2ed4a97e26cccc199dc9f3e0a356821ed2a3cccc102c4f93efb6c4fcaff2e5b9fb552af0b2c64c70901e2cbff2131e9cd2a98185e6529e6e3880e5304dc6a6de93835f7d21143dd13e7cc0f0c3c395f58e166fa5f309fa3a32466140362ed3f07c936e90ca3a0abaf08c75f6658b244546d5cf4877b4695c4d897f1ac5f64a69903b12e58541fcfd94b70c6713cb83bfa22df549f5a6c141423ee473ba2a80a524afa2e86aa09664ce95702e00a0a0b574bcb640962a87e4b1f4e254720f83f6600d640eab3ec5d1462ed198210a760bf608979124ba99259561da74baabb530bb6979c33960d5e0937b174970f905d0fc7223fa768cad0838a26039755911d1f43d4b2dc79e43f41025954278a837b0d948cf92b2e6e0a2132eba20284ba0ac1bdd3e913d1010ae704182d293acd28761c8051bcd2f0aedad8bb6953141bc07eaaff72e67e3feda79a1e3352cb471e33e967be29f10d86ab31cc01f0d6807cbfa2b1f0041fa0a8a6244be124c8e77839796127b044ce63382573ea92237d263437f262c300d905f66cca8199d85dd980da3f2c3293246e1f518e93dc7a4bcfade82fd9bf2ca2bb78b3f85adcb8b707a61956d343f1fedf1e47b44204e43f1284c098630cbf650cf869e5f7f5fdce6e0e30bc1868992cf02ba71f90121d1c33238354898027680e2ed57cd4c696508be88d2edc03e6aae7ddeb75a76812c9c0164380653500bb07efafce604e7f903fd7599b07a21b881d462f7d642208c8987fb2fcc2bfa22df2e50132f23acc62ae6f5a1e09cbf27b19d9f9fcbcdf5253155a72a395e58c9ad186b5bba60bfb336a0d2ff909b09e7f7e29cbff2b879fca23dd8b07a8f6e849d2ef6299c156ad71fba3ab5a6b4cb7090c0a4bc949da8ce6c22852530fd668b3afdb001c34875e569118107408b7b3f7339b4f150b8434ad46dadc9b6b41d92ea1ef8af30e4e725ad467026ddbd332c424f224d425e86762fc9421f90d79664b245e7137c37b11797d92a9aeb45b30e3fd6b03d6d07add5103074c468469e204398f9b9c9448fb7c52c1201d88d737d444bc1b761e7bd72a054f8e9ab0d437f5cbff856863459d00fbeecc38fc246d92980bae4e04c123e09c15c8a9d98348988a969f05e78865ab68f3ee0acc25384aa96c9a6942dea0a658b596c9a285c0237fe6c4b4a40564b08293fec315af5e26205e9a7ceddefbffda6c6c6627c0dc546f710e782e0002a8d1eb0835676508845ff16e2ae16fc7e66e44a4b880feacaba929f3f88d6a6ee43c3b64414402f1da820ca8a8258d2ccbe2a8b9470163cc0ace89909bce0cf4e3a7745e7869269e72a8b4216e3dfb3857e2eb0c8070ddc183a0a16ed18d064ad7be311f97262cb9e2b2403135eb4f4a3ce663270f33ec524d858e57b1435834157e768dd7130f1eac85b038a40040936eb43fde00428144ed177f2153729cf738043fd16adfe4f7cdccf89927cd97f9b56b8c055414aa1f9c66558d42538512e1c629f8deb900d4fd60c5c6745524fb4af301bd2caac08e1f9696f5754088ebc6f9bfb48a1072f7333c65e13f6bcf7f15f14754641c4ce80827356b1a5ba56d5ca86376e7b4c9268b757ec81010ad7346eef48f258e320a26de31cc015b5d3c7eaee06ad7fa92035bbebd83f3e11999d9933b64becc51483cc45bc7cc56869c0e9dbcc047027a53bab6159a519e2e0c99d44625824e20954d2477b4483761283f6c1e2c843f055eb07327e474a2bcc1b69a5475e1301025c6f0c1ad0fc722909a62182c3dd8f8699ffec82b8bcf08a47dc66306e4b268b064669999c9f68f1d9a99ff78b218f2ffa46095227fb5fbc270a88f8cf5dabe7f25487de01b6506afbb7320eada206d4855acd1b2ae4c44d60911329906866f6dbd6b5e79a30826ef90feaa35860406fdeb013820d1278f43e5048199c83ffaf4e61fe8a0a1ff609d7da26302dda66121c782575f89b49d292767ace1c5d6a6536f49089e6053cd0caed9808e642e4873d21f5c8f6b585651c2a2005c0cf5e1b0dbb034bd3bb8fef4d9ef713622f3e87f1fdb06a3459183ad7b017249655dc8281675cb09c0399496fb64f3db44d17651b79349377f6e660617503756be00483d52dd4ce0417cc14a44a7b1ca8d7179cb012494e84e62b518129a25a6ff1c8dc2a7f291205322108a34deaba758aab1aac8bc0ab4f22151a900a62ae3a65d9a946f0318957190a810a3a856e226989650491613c77924becfb51d26ff9d36601f50c57a19c299c1306b836dbf44b49eb3748a540e01ebd62d032838b688189326ce0ab5087f669f185b3de079785a1b9bcc5d31568347d07579cc7b73636dd4a530acab98b5137188b4eb484f2338fe0f50af40f2a788384e77adff37ce480fd0abea25d952d068d11f07887dfa30fe1adbd1dd21c98f47b77240515924fc120704ef4a1acf9ac09623109b15041130b3c9589148b61c46a0071e7c66d8d1a7e858e74f6ccddf1c3420b3ec5b77557828b3ba5bfb274f40040804454d3fd6f43d3ed2b56ff4f57d2299475052e3dc0b998fea8fa04d1d415fb788c5963fba42af5b7bb3eadacc0cfbb0c37685f11d162e9dda7e647b5cb8ff24d67a71e2389d380c46cb5c7d2cbe1e5ade9a89902390ffec95610c1c227c1e9f8e754d00f6a2cb7d138442ae10dc58ad833691634285ce56c762da1f7b33aed3a2178d64a86ea81b0ed33c7f2a119e97cb38cb41d880c327de6b6e1c66ec48b936339ba5091c23e7581e299f7d31ff7b2a5718f613a14231147827616d78b5d50175e6a432c78d28e9e8e5f6cc62410a11267ad7a97c85751cd8b537d0a82b42346296a86be965cce4ca08128462271ba140853ac91ad3d28e98dd95194a55bd6dd89ddc244e3c66ff0fc20a9849ec30fde58c9be37ae8cb5db1950cc48e5776f6e07172f02c5b8d001813595b9c36a4fc351b16cac187b57b5a7cc55241d767316b87686d487722c0ebb797874e0e21f69da655a219cdbc3d2b52e212491d5f2248d1047c9d82bfb0c5a0be81b6d4a364f2b2258fa6261bd7c911c96b322421c4c843a1e94db6c3be68c81449b15724373baca1b703b9f3e647fa2903b2c8f973e111231bf5d0179d9e4c8902087edee899f0d86fd81eb7a79b8ebe61da0098c996d5c716ca56f3b52aa30316f0d88fe9557b653bd77401a016122565cacecbca41e6892c345bff3fa41ff8bf1b95568e2666cefe88596602f655b8e1d07800a7031627094c6ec84afbfafb810843969273f42700ae0ea28ed0f900783f0417d8450f73db002196ffa6318e130f6ec9ba31a7fa90fe4605054cca744c63ab8676d537f836fc0895721930828ea6da82f1552e2311a5f6128f58112b4841bfdd33e61bc759c3a504053bdb0c05d349a0fb4a935b86a923679b6e4ffaef779e703a91095b92333a4e0736ca19fd4fff07372eec3b186ec6dbbc19b154dc3ea9f2a8c301ce0d42194e06d63828fab9f2aba25896382f30a6a90c9897a3daf727ffd1a04f1190d168d211fe24718c29a6261c2ef53b203d8514fdb93ce4836c2cf77257e5639535845d06391e1add9af8cbc2374447b97fbde30f0b87cb9ecbd5411edcd1976adbe7484f02935b41e9584dfeba82527f59e6bd87962a0fe39e415b876c9b848e1aa21c22509042f50a213092dc5d737e6525a960a1ec8ce4d16a14886fd72cb256028f118b003d5802c8e6d0303fe641c0e61734a01b5e2c78f8d802008d5da5b90e3dbb76e79ba405362040e25ec315b223ce09005055935aae6aca47423cb03950082163631c878dab211f36b73aa878bfe46b470b0e92899182a59872bb6f4022fddcc2231e13ba3aa30e18bcff5828928d0e709b94e91fdd411895c995508caa377f4501c72f0ab93a491736ebbae5b55a7e83ba990650308c2768dc70843c37f8cd0fec9776390fe0298b834c088919964d36ce42cc54f80671074660be02f2bd9456a79a2e57e23e02b66bd2624cb183b841703e0d8c6ef2ede6266f882f750672fb93f4b9c69a1cfdf2ea665a1595de011c8275daf0a30f134cbf37285815755c840f35417940d3564778ce080472720858efeadbcd29118b41c7ba96b7cfa88984f8e335e95507870f00271f4047299544008eb3b82f45914c8720eeec2e22a49a7a939dbc2b1f516133e779a55dce473c0767ef280b36813a3648b932c3b5e4dd23a198271aaf1401559882f889cbd7077ae10a3c65f24c8ea53786f65757af19bc926fddfe7f9b29b72d8d8547e237d68efc27733289fb258afff9258dfa60c2c15562bbe8a84de53a6e311b63863ccce9d36d8b1ee83b10338efac283d0dac4991c4df028908ca6a8818cf958d74425a23f3bec858357699f15d6b6d5685ef983c5ca3e23ab5e1a1b1b1f5bb0ab9caf6385b365ff0ef51f7f2eebed8cdd48f9e9b9a45e16177f3bc2f82886b50815477e3d712d8b876f93586d5f15f4a79d59ebb3c59526aa0a6cc625ce4940f7d540e79fba9a9fa07dea01a8630c795c8bcec0ffde6e75f35435bc03b150e2fe2833ab8e4d338ae2e33e3fe1f2e206869cbe8931a083b3d60852ce78e577ff7565772448076c0a6c7499335efb470f3ece08cd1de703039d04e2b9ece715e4fc83b85ee6808b42abbf9ba792828105f82f657c47ba8ce4fc73c332b5b94ee8781d9e4ef65f70a8590a2e2a6a143f3e72423d6b4a37814737a9d54d387f0ceb464acb0ec53810092aeb0f9185af6ac114c095cfd8e4de107190bcd00c4f1b53be23d8868fbd16973752160db0c4021c500cc6485b58c5718c3ff58e3f7ad61a7cd3d1054aa3e1616f8329c672640b6c21aeec5c04208445f7be58e11ad8cd4b33c77a4fe8fd51122bde0801c8bb40806e5481d0cd73c41c11d71ab4aa7b690b8210525f1b1509a5a01efb297334a6c3995380d29715d17540675f288117064a644a7336e37f302246c1e8c536e00e1346a06c532a1bed3d29351aa2bcc7d8e62f1ef6a2bc7423e1c99ca48eb88acb555c38d273e20f7944a8e4e41a3f8a1abcb392015f751514397a50cce2b3a9097c60d971eec269a033c97ea2606ab2fc3ab299b5fafb2aba72c533fa87a89e5148c6572996ea39229da3b9312fb83517da3b06384c0525b2a1ad46a122d9cd9fa0568e1e2eec75d2c35263e2857e43fc3d712681879584c6350dd0cf503bd0f347078c8fac05856646b6059f058e741316f7edbb171be65ace011e30c297848a262c30f62e5a2da016c99ec101e878760fdda497dd9b16907222dd97e4fddd2c7d3a2a5575da949b5063365d0085cadd25209a07e574ca8b790ad2ab1445ae8578aead028924b19e40b98df893a615ca6a0721ad3df00335e44bad441e2ec87b2c2119a4ee2d555c4d7c4e6500e9990d514c9daa1cee1594dccf8a78e72b954899eaf46ea2ebcc0992832ee01ddc9ccae964313b57787c74b484740f5722b27a4bd0df560be3bd9b745d08e5f25745744fbd9ee2bb658168688e283c4e365301bb529f0513f064676d321debe991b069cfd82667a56746fa1bbf90f3f3b184fcd119672be6f5472c3a7622bdbdb59b3412e223278b1fa5d44219ee9812106807c6fa5d92a4a5ebc828530b87525c2772aca84370a140386484f8ce112ebd650a9dab52c78f231d758e6e836c3ed5b4271710ecc5d5fd55ce2291bcc0e6151dbd003aa8d308810dce7b46668692dc845a7e03c491bf3fb5c94a6de939981e86baa15b3bc23e5ca6e2080539c3b9fd7450a6bd33ce23995313fc7418453c8f3a95c277387dd2a1b1358c90392a9d056e10f18cd59f74dfce3a8873370796079ea1aeee201633e3bea90e6ddaf92623b9c1602fdf6568d8dc15624778e2efb0ab939609d6e7065489d2a64b9cb51a1e260ae9c0e82704250a26377477697428f276466474448a6fa8e98b061b758d31f02136c18ceea5cc26b5822d406ac1a10dfee111455a25823541a40e2f9ad91dee9612a10d08665d6a61e72b6e6a1dc2ba542e7f2cef6bd11a7a817b9608ef0c82100615291cc87149102f875645e26e5664bb9875a3add62373da9a741f0f874b337979e3046aeb97a89fc84caadabb2648919e6664983ee28c19029e70560c971aa133502549272316f4135295acd0ea43a5783a4b50a22328aa99212c330f48958a651c8cd0597b521dbfcbc1d157aa490f35ae1333f14ad62a0f9d67b52e312225092f39bb84643b6bd3fb8dc6534a94260a07879cb585dea8edbb7a8dd9adf76d9dc05cb532ab8118abd01e1fc4a015963e61b5cd99c05fdb60973e947fb50c9708f3f86e69c36a1973fe650214f1c32e815a72de0e939b47065d4585466b772be5cc9708cb55d31ef2aba2a7aff123fa059ce7f6e06a0ebc38fbd657f82a9505615d2c6b2f8286cffb29b7446b7cd840be21418005544b863756eb5e76ad412b4e5c2b579b2bf9beb293346bdff79325ab47b2ce0315cb8f788208ee22852c137c715ba92271cd0341505e716a41bbb3f8cc94e60a6192984339acf97eec9bd6f37ee54f1c4c01fbc8cd97cab12603ef679aadecfa99864f71933e867c006f4fd791a04473a157c5ec8fec0e59cb88cb96a4fd2d5698d3a349b635ec9a1c8cc394fde30a6aa5638b857a2fb6c8b47b123c1d4579644862c7832235d87bf68d0233f1cfc89d957783a22c4fe3f1d429db9cc6acec798d7b957fc6b1526b64c7a64ade1b07a89e608eb5ddd8fd504574bc81bc4e6d84e390b6b7ed8599ca155ad3c689ceb880aa16aa09a87e26e3f6e846f800178d1f001845f532970d36fad421cc7994e0b305800c2d6d161dd3f4a9d40f146e823ef21d12f7186c03cdf24da36ddd15d12923f7d42fd8e4591131cacdfbdb3628e748d9abc6c1a771c054fe2d225b41ad2af1b85b62761e238a919da3a7122ab21388fbbde85d626c72d13848bec526acda98c4d336b2672419688387a25a89eaecfa7296abf9595edec91588ecf975d2607ac83090ddc7a0b1677002e1c53c05e93537d0851dd5077980ca104c3e6a18e247b2338d280d8bcc3140b32e08dc6b75231cf32d29a481c8a5f53cac027215d003ea19a824befd0441215eb577cd374a6c3b0bf381d416fa3a06c0d7d0f978a3acc5b358cc32f917098820079383ad4065c74fcd400ed64a6f810e2e83a5ea1270f8c60754ce2f1989900a94cc418be5ed2398ddf079387580d71a54854dc57113ae033a6340db212e2976bf2dcd5e4c0d086f9f1560991b2a39a600569cb692b34c0f950c1be5d0e8d403987098cfd9ef13325492af6600f845bcf6568a8a111e0e75a937c0c0ff3ffb174b3d0d7629cb242a39a0d14018058682d1c284f611f82fb8235e5d20b478774fe0f04db0861520ec2b4f0c12ba3d1933b1f6b2173fede38afa8cdd5ce2e3570c9365f75f10f665d0bfb8adb7ab7924e217d262e949345dd88aa539751fdf4afef12f610eff0f3533726c37f8cd1b0011596388569f4b11d59e22d766d09e79e004ddc2813097f89817003094d2fa069c1958d06bdb0614c80fa78e297df0986e83cfccc02354109eef2243bdb6211044fda787a8cc8b5e07a00e81ba3cfe26930b9a8f792ea4dfb861f1987a35370eb4fa91fb0653264c01dd10efbd37cc47cded57fb003105eaee7fd7289f2f8269b2ede0422f2344e8dcfe31ad83101972037d04a0c730b1da0138287b9f590dbb51a3718ad0d7e36f38f7a087305461f14d7908e91faf9dd82186da6326ca19d4293e3dca2766b4cc87f4d8511609f545a6504742ed9581c7e7e937f11822a548f93a51b9294d7a816baf542fa4774737f6dae9599c4fafd60d7dbcb3d74e4fc18aa9ce7764f17cdf36d6d1ef980ae540303b79e04df084e9caf1a5b1f41f6df25b868041babcc4bbddcdfca1e0c4421cb91c238ea4d550a53b66062e53cfb7329c9c3bd1be94cd933bc2482dd710bb198019cac0807f9edef0011c4776ae1cd559fd6656ca35bc8b3f3f2b12b6af27a9dae08e3dd2fe3062ee89e21196cc057de5b23155e681f8869e45e5d4bd459bb2636f3e1f78867ff5d49ce11944f1e271e7c4a8365600be125490f499974dbf70c0cc22014dff9194a3b79a460a38f6bfc7f5e9bdc1a6fee9598b6d9082bde2f9fe1637e57b33a76dee2f4e274bafbac5a8a758746819bd902eefd8dfd2695a9ac25700c4b4e4b59ae75326e6ee5c33658ce338c53932d243eb2cd3900335f1c21f71b17d639e033cefaafc23c6c0497675da3722d0157df4f005019eb0f0b4dee336b518b5d86544dd6c847daafe1eb07cd11e7ea08bdfb9b493cb75a756ead99eb14412e4b2078fed5dc3db0ad40f0b8907abdb7b9384ff0518e7fb5eb1c35d1d49af345be4a04ccc6e38049abf81fd3e6b7def674361869539318ebd122294fc074007463adfe131c4fedab51fa724dd8b58d39f098f03862c7ab32c2a1138edd48f548de2cbdbfc2d508c1cd8985ee32c80aabf16ec4b395a740cf4a9f68ff4422e582fb8118466d5b3b17b4f2d4ffca758c3beaa148c302f4e38f5ad6fce62090f43c0dc35bc49bc47b148d2fd4af41a97f3cf1aa103b6d2373dfca033c9003e2a719264835643ea59e065f679a92c1b0de900ec111cd47ac82b9f38d0d4ac1e0e37bd94adf171f7affc85e4cc5d6c616cdd5b41943ce7fcd5c241336af736cb8ee3c08d8f110c48fe3bd7fa754ae8e653b4e9fc84b48cd009bcb3cc10c921b5230f6521a1e1d5d0097d90378aa65955a1a8940960e48527701c14f632c312237de687cecf81db43be1146bd325a296e68199e3beab4542c77fb2d8268fa5c16cf5c112fa72d12bd9d69c1b49e46a2923964a04ff065311ad6d663224c578e5bf84e9b0a57309d9ae2318b9820afe66b2155c40935945947b8fc95d03dbc6c7976a3e13e7a8f6d73914527cf25aa558fc187d2fbe215a7494228737dc052517a92276d8a15297dd01b6e1ab70fea69f4b179afd287e60d92518aebbd9cc467322d0a0dd5f456c8ac3bf8d16d49581115660018b37e4f42bc949b1aa7bc59174b175ee42021495b14f8a915bf6c5e997ecb812717772ffb01edb586d407497981cd0332ea2ff3af7880b000c6531fe792d3e26f1b8ff087aab3bfd5a015f696dcdb177369d9d8dde6f77c11f97f733addb567067a1de3ae95ea7b3f9d1b50f80a33c65e206ad204e3518424254f57224d61fd356fb35e977543afd34fc0dfd1b714d6dffd91caea6a58b164aef4429d4d0ca23d1d6726ba636367170402bb00bd166b23dade742be010913f7ab1dc2d4d9d6f0db97ccb57619f2f51122110da974d037bf62e72f9a566fd1b5383a0fe29200de30c82f16b24b62f9d49a70d8a09cc3ad0672f3a6e8ef1c5648fd944e70804991c988d70ceace4f5eb0f5370f87b8c90eb0fee0b04584394d75224a41df6ef369b00d14d17d56b74ff5d979a9e68ca5904e22a661d18f0a4c11f6dded4587af58f9049a624d67e8b75891322446cd3dd8b4cb104476a1b44ec15521a9c5496fff9d6bcea8e4d04dfa16f966bcdcedc1c99b711b81df04943d9a4534d9c9c3ec76442147bca90b291c389191da5baf1270dc88772d63038bd841b52bb4b813566901533f74b539b49eaf2e683c8b6b10c1041f90b0e6f856adeb083a5f0166b3fc9302ca0839c79b04a62d4ba84c4136d4e8b82f3b83cf1e0fe329d1416f5db339a386d0f2173b4a03811953191266c63a61ef9650686bcfe30ac2614a77d4d2176a018cb095420c57caad5ab7b42c348212cd9dc8021f9e5802763751cc5eacd039cbdeb46702c8c12530e1a6f1228c811bcec2a5a5519884d17844d3951659c3ee8c0768d3c2d75238048ae45bd4d531006e094365201b09b5205f57b78d5b5255d67c66dbbb9c7705029cc9749d412a3d5ed0aea8bb24dfb90d68e6b632361c71d234f1084d2715c853142cb0b35fcb56a27375d5a8b6652f56c23caba78e812fae48059f5cd836c920b6ca5a202121edfcfca1137dbd973d41953849a071dabe01053128e5ee6cf0708b0a967f3ec69b5a64f9577e701e268a0b77cfd034cf7025ad75a8e58309c1137074d3937cd5e412def3d6e9aa391c1ae12e4e2bef951ad5247c984f214f813ba0afd819d7406173808669801100030901c88917b72b84dad3533774078d2bee39192295bf1dbda68c9cade22bbbb764b99a49401dc088508a908a4f939dc0bc4550076e8314944683bd8f8341300874a6a019334bf7487d06ad8909bea47c9435869f6fcd0db514e69aa31d2cae11fbfc2404a53f45266874414991ba4d0d541b7eb3460f95a0d2bafc02876c89e335eaa27f6fc99443455981384e6890d9baac2f884b161559334dfc665b9b298a4f052f170c396c09e6c12d174a9427b820db3c31aa3c3b33bab499a5f91b854aa1d73a9341eac1c03dfb02437591fdf9eac8c7de6f2d28d3db9a9c67869fe45754c88b7c9d54163571932c4ecf935bf97e697dc19ece6460fb307014801f8602b14329a6a4c2ccdaf3188267bf252586db04faad39eaff96063cc31daf1b5918c1157a3b1e00637743c31d40291175e985cb884ba7b8c315a62d44f470c96f8eeee31c68879213d55c3c0f10171c58d7829becafc302faad5f13a964573695967354bab4c8dd7520e6f17bbbb539a8dc6dd8a492205965faf3b672d77eb5e93d03a9bc5e86158920bc49274e8653d316747cc0b1951e2537710085456cdf6976ec0f2ddaa61891f43b47ee8ee5e659e7aad4960f94e3f4694d051987a0da5218b7921cd8079513fc618c30b934c4aa6c8e4b2c48feeee2377175d96f8d1472293110e2e38020912a868f277fdfe1205f892538ea05ad5aa37744526a1c5135a3fae64515c992b581738ce19578e084213b5eca86344919aee78e54988e3bb9ceeb5beaceeeeeed1dda92b21a9a44db8cf233724a5218954e3e4b223eb838b486714e2531a5d6a41c6af3b4cb6bfcaac79a6503da30e81a8767c9ac32b07587e7cd0524aa9534a69a49ba865531d5a60bbbe26b5824a9f9366492b13538768e323f2e46fdcf8619aec25830b891a649c31fd435e9a4ff2d2fc909b7e20800e93edfa46166694738fb6db58c2040e43fe35e6934010af9922a2517d4a7ac305175c70c10517ee5c540702fd41deb841001c7ee2ff3548243c0a856a4552adea3596ac93c6182396e40a638cd1a5eb4b4629638cb1b34dba921289a4922271e95058d2c149c258d68faf7529ec1f774cc53cb1e7be71c3daf37f0899e6caac7a34433e425c5c9dabe31e536e831db69002092dc4d82c7183924cc3174ec801155e5061095291309cf01d53377862d349011254c1450fdca002021774d8f40a5d8851240c2920426a0839d8a005dda68f83524a3dc620e808f69cb389ed083936584314aa26080100364002dc1a1785a08f8352fad84d09684209495480842a0c09c30afdd04c39d9f49f3e69a67c36fd1b3385fe0f33e5db94524aad26702806918d60fb9e130a2d723fc0b18a2ab4305362155518d9fe35c934c99912ab6802915dc1d8fef526e6a03411c5dfbf23b31d403f7b91f5f535edeb674f3f48114bf7906f351020417c1031810b884ae526aa7a686fbdd7fed23d2c7e8bb3b52cb2b22cb2b4c801d96b0f01f9561b615fbe16594688acece38af7372e0652a4c7f69ef7b8bf692017c7df9e61f9971639407bfb10d0de6a238af4c06fdf08efb587803502bffdb812de90c0fded2f2f7762ee5ecbdc7f1f6465ca1af0e4d140a40caa46e624515fae375dae38faa6e6d8d3fc50a2b4c8fa202b1f192db2341142405ff24156422f6bbe970fa4480ffbde0329d2437bfbda5fde03ffa781c81ede630d447a1ac8cdf6945469294d6525fe50e2cc2c8baccf2207d08700fd2982407d292fcbcd1644c05083cce9b4c2fdf7457a749f7d963df73db22b93e991fda781cc934f999a7d384fd944c9f8e906c74f3713a724877205e54f8e5eea6837f6e38afdd04f9e9f0fa4488fecbb0752a407f8d903f18739c0caf79c0652a407f71e0dc4f3dc771a48911edd731a8847cf2ed34026a895902b9f4ed51befe38af7e1acf1b4e8fa202bf526853fae602dba341142a2976a90958a33afc071450401b992bd5cc9defbee8bf4e01e8b1c80dffb1ef83d0da488f54050db1fd7f4c0df69208e72afe1b4bfcd7ee3257f2ddfc415ed434769da88227125741923ac16f5f8edfef6570329222242fb07c4154d43c0ca71e20b19c8e0c8e9b4e24060e6cafd23ec6f5ad423fb0fbd66fb97bc01c7159597dc8d7002204c610a494ea795b872b51147587dba3845ae408422a04ea795ecb30f5db5fd3d26700184a47684c20e3d539a2e70262282aabce47f635f9ba6bce44f0487f58666fb77622c1c37d19397fcbd83dade81d9de396db772b67fbd59559c9ae3a608838a917153a489a9232ab76cfcad1b7f6be5a6b8e24f4f6eaa375e5ac17448652e7aeae890a2b67fa427c7c95e0a705cc956e40e1da723b3230e47e020f4881944529016708d266bd2e93292aa087c477bb949c652fd8879007cab5327adf6f23a63bd7579d0d2f400a69d1d5fab568c28925624ba54144e34938e1331f4888b288d1ff21ff9ff4cf16cff217a4c1387f23f21f2038eed4ea368b44370bb4cf59f5193ae1b38e2abcc5a6bb5ae4c2fddc961b4ac6ad5aa3540f70f6bcba77e043a2fdbb900874e4835664ae82fb13ff66b92a6bfbf8bd4d8be539c0264fb5f165f16bb76125894fe9cc0ae1ff3f5968e92caa7343a5a03741e21fe15638e121972508c393ef9dad7f697dbc59f622fc5bf640e4f05adafe35d1d86334edbecbd76d3b80c63dde5753e0bac1efa71d09431cb5d62dd4b42269b6b49ac1053fd0aa0b15da60eafaa434c75188aee34a6cc0fdd6795130acd46c1d4e1ae9962b263c9d6dc7a0e6d9055ade7cff6ec4eaeb1743770dd968ef50c6eedb29ebfad61d6b3b7b5cc7aeeb6a659cf786bd67ae6b6b66d5bdbace7bb35ce7ab65bc3d6b3b6b5ce7aceb6f6d94e8e2b9eb1ad7dd6f3b535d07aee6ccd633d5b756b20eb996e2d643dfbd64aace7b9b511e8e3013fafc3dc76ad9661b14464b2b9082abd2c4e5d8740f6d447c820753a1777fc18e4053860c31b9c1843129070041cde70c44acac31342f67c27c0f6526344119dfebc2c74d39952b2e7177bbe1482889843c6945f6109445082913288210a2eac68ff1f668ab55d872fd1f3f505f61f6d2993ecf954655a24f878610c8646d8655d17384948c05859385d2668a2aa3a321842b5b0a418e2f1a2970d4e05020ae6beae0b0c652186c625a0aeebba2e1b1abfae1854615f2b58b91efc98239ee0b8432c8416472c1c82a2ee989a418d65875cb27ea40287fe4f4835e6ff09a9c64c095f4e02ba640c5c35d62ede6fda05c49e053108ba5f1f7f3cdffd3efbf835fc598c37fbf8b378c3f1bae2456bcd59859929ded7af4366caf75573b83ea7c3cde36836f85bee1e733aaca88d61864c12fe7879781a67311bebf0a5a26a57abad15e770db1ee7ed316ae35aedf6f8a5a26cb5366f8f7318029ccdbe5494d55b85d9e14b4571c086033721c07126a6005351cec4144a503d122fb16911a41cde9c7d15e7b697d13d1d76dc83cf3dd521b839bdd91cbe80db653f1eec29f9ebf35463d05fd8c371dcdf4f47b9df74f8026a9ab6bdf65d0e6dc769df732fda7359d4bdf53c51a7b5b76f5ffb8d7b2d872f9b5a8dfb8fd32fe0d634ed82ef73af326d7e0137f7026ecc5dfaf9fa1c057db4e7aca659eec2d85ed8f2c8decbe1cbf6aefd7d9ca69097b810dcb59eecaa3b93257097bddff205fffbedf368fc7d3a0441cb795c9669dacbdebea6e02fc749da3610fceff31ef4f0779edbdf7f3a7c01b7dcd91cbeecba79b99bdbe610eb4bd3386eabcf71dc5b2e7c01ad66b204be76f802ee0ddcf723cb953bcb9e864244e065d395e8051fbc600a2fe0ee80f4820f5e2085cebd2834fb5f0e7fecafc3efefdd6ffe63bb0eb7f75ec5b3e7b8ef3e3449dc6ff9b32f2ff798cb3edbaede34f79ddd9cb65f8edbcbe18bb5754efaeeaff97b30ffe9797f1d964ced7d7edef187de75087ebc3c5c7bb8d768aedbf61f7f2d7bbf3cb6a736b49be3ac167defd2bdfd4fdbefac73f6b31c76f8bf77abb9af5cf61f0ce0deb5e87b7ffa5b167d6f7f0bafdd655af4e921e216dd77c9fefb9bfdf799bee224599bc31fdebd65b71c52aa1dbfd52ef83fedf2f9ed5566975fecdede7348f58bdd58739f8f8e25fa876fede96f3a8c21a87ff8f63cd53c1ee75ee332e75ce7f6b7e73cfe147bf74e87d4b9e94f759cfed367f6987d2e63bff965c15efb9ece14cf7fdf6b5f0eade8fbbe0b71588716ece877f4fda9be1fcadb7bdf7f0908b245debbb87ff7b8a376536db50bf61996bd8a6f5cc67dbc5c963d17dabd3dcde18f4d414ff5b76d0fda3c4f7308be65b50bf759f6d9101fec69ee1e7bdedbbaa7df3d7d4cdbff6497eed3e18be53e3ea7ef6fd97b4fc6796e2f87a0eee4ecb569b91c666fb5781b10b7e8fbfbdfdbec3dcddb7799cbe18fcdfd97b7f7b4fd9b5d364e47f7b8cb91cbe1f5388747ec4cb4bd0b7dee376db20416b5d0e75edb179b7d941bd3328d6159bee7b8af1c167181f0c7fc8bc5690eac90832bb87eb1bb0352e6c00a39b082c7bf1d68f7b51cfad81a47e98659ea631dd6ef786cff9384bf66cde5bea4183f47f1638c2709bb96e3eeae5499f5e96b1147169b3e888a4d1f7c8d25939bbe0bf8f4ab0e4d3e5e06809edaa7df9768d0657bfcdbd7d7b66c69211fff60e3b7af452e0838a6a68842d3aa8db7c57bcaf1c0bff1d0fefed68938ddb2fdd5f1e9d7fb5e0eb5057cdfbde31c6e7f75c91878fbfa352bc07b2c2f03ea532dea9efe7d9c45dd6be1b5392dea744b9ca4fb9abe3af4b1e96fa1f69d21e4fe5e65e21ce2f734ddf7e95b7fb5cbf79d76b18f690e37fd83ee4f57fdf8e56db13a9ae81fbe415f1febb044d390fee11bfc0ad2a1dc2098c32336dd5e568b2bb035df9101e1fdedb1a69314fef00dead0884dadbd2cd9531ebee9a6e924e1bf5ad361fd1fbeb10e7f38a594524ab7d03efb26764bb6efe39c75af85767b4fa7e9df7bcdcb21deb4857baaf1a6b75771ee7f92e86fb9fb9aed5fcff43ebc3efa9e4c67897ebc3cb878b54bf6da675af883460660fb7e96f1c78b392d22b7058ea929846ced31de3ef6f6719a3eef7d9c268fa7c3eb93e32cd5ed6ba976406eefb71c66dfb9da05bff6da6be06ff97b30b77cf137efbb1cfac0fa7ecddd7b997b1557995c9ebbcb61a67fd0adddac69bf8120bb5e96eeb77c1fe7d0c7c6dfe596abb7fa1cce21f65c0e8f68d9f4d5e1115bfbf8836efb51ee4cd3d9abcc2ccbc77248c48e7fe570023e765c61b13e5e2a4fa7d369cb8f19f0b17ff88ed2b3e5cf209ee32be4e440ca5a02fbd3fc92a5b0479439834e683453fce9872f56cd969d98235e9ee98d3268716a48c0251a07053cb7fcb869bec2bec03b5a110530386b472ba420644b182de405961fad8ccf111740b2fae103098eb5b2b1c98234d954ca260a118506549cf69ca914864aa5accea4993253661a99355dcc9a92193351136562221a85e6101818199f29649e2669fe3cc998e18943b01443e6c81c0c9512c25723712c2d576b5871a8295229ab93521d9147666e6cbab099b1c1828ac3e1706ce818333629cc05180a6341344d9597b018a45472da4cd21533362a1b1586d2c291a94a51914292e25033a9948d118c20316293ba68666c529d8f362565623cb286a60b1a1b0cd5c529854921a7931c022361240ac80790e4d44e0526105f2e8064f5c307121ccbc6068a0810a100dfae6544a9f388d065a118ca060b2a0ca5052ca269a6b648a552b20d1c6aa626151c181c0e8763a9b0a905d8d410f1888dcb3263713856a498104d1c0e8703059b8b868ec1e170604c93a567165385a19a7021545256a7a63a99f45d7cca4dd5a6a66c523765c0386e7471e3460f73140af5200009621c53a2096b0243d9146acfb7294bc534552d541b19549b0b9fe83c11124618383bac313135688116a3c37830a04579058eaf7171d228a34fdf42973d55f3a469510b5dca5aa59452ea90fefcedf247fbaa5266a1c59390abd9136692865498e9f452cd13b63a970acb347b370e77de077a3e36be060ae19597e66b2533f2e666cf1c1c9c3d5f2bb126f69236526562221a854420191f19333c920d336c9c48e0f052b9114f09e1ab9147bc3465ca658e4cd57c2e22a112ab3d8bc66324cd9d91325e9a2f6550a1dd59dde063c47367648dac3111e1d08d64d85861622a1238d879c22717162e55fc0b8b689aa76b8b3ded699e68587905feac43137b9e685016e69aa72762623655d6364a9f12850a5d37c8c1ca2b74b01e634f1ff0e1990fde1979923a904ab0c31aa39da038c53a02922d614544c0a60213b879990130dca7b3f1414dd7759dd705cf8acfca871f800062c4c48889111316c7e6c807c202715966b207e2eea0dbc68e9fc3c5b4b782020e3f399f1c37dd7953573553bc9b79b188295e1722a95bb9095b1971d385712c4453d74554b9a9b3f152cd4cb13893e4839862d11049f341a01d762bad5bedabe33c9e116e7b46bcd567e5a64bf37d567bbee6e591954539381165becd3faead811fd8eb0218decd4c1199015bb94974452ccdd7624f6cb5853db1d55e6163885411653ece222311653e974534114526a2ccbf306e120d2196baa8e96cdc24922296d0208ac2e2cc14110d2269fe141d71c19e2225ec09b3e7698720994f1b22cafc9a3f61441ee66f1974f3478c499a9f65d0c94b13040342ed09c4d541b7d49f1c29e01949926cf9f367dccc146c48132e6488182e040c31c400c6961fce24e10c31ec49c50e473b1ca1f6fc116afe2866a6d42c8f9a98a5f92997e5ea11ca34325d96511393344f23d4895e7a4f3147dc13b4b179836f4a7e788f78c604603d5f9a0fd86581b0d6da5a96212fc9c7ae3c03f32bd677f28ca5b57f1a97c64d7896e4cf4ba39d5c9b93cb72b580c36519a1aecd083542792b920e3bec10006fe5896192e6a35c966b1351e687ce80c34fce6acf07c1cc94f0fe27e72306e804123253eadb502dcad032ecb3c62188d29471946d1a3218d53a30050dde70c5a033ca5aeb0d431876a7e666891a297cd98c813529b8200583b71d53353dd8605033736b50330327d4a8e08b9a11bcb58332b000072760700b29604b8513d6a8c0620848d841911d3e2ec30e6aa005c6764cede0053bfcdf41095620fa02c66b4083143a0003d64117b5d6ba81a1832cb620606ec7940e9a60691d1841017842f242074484a0833074c0c4450206e5b801df2fb6256c76f84028120525606dc7541125dc14f94111299ef427a4222e2822e48b2d05f8ea821218a4c211f05dc20e1fff80081076889f0944a0d8e19381c8123bc4d70753b86193a620c39f4cf18529ceb0c3bfc199c07eb6852f61fd8a5f36dd26aed966dbe6f605f4742e0b16f268a04ff75a97add770aef7b7af79f3cb23538c5fa3f4b7fa5d0ec14db78efecd61f7f1a5a48f9fd2d7a2a896ab0ac87e7b2e6f4f73f8c280aefb10ef2edfdf6e97b77875d8adfbced33b44e6547bfa5d7e01434f53ad85823e1e4c71f7dcb75d168ceb36cc6af2b67419c3977bda7dcd74eae0b69abdc7597b8c1fac1f8ffb3a766e0b96c5af5df7f176af32b52ebbd8cfbeab1ec6184facc3eda30eb98fafe22fe00bb8bbbfb90372dbf72ecb5c97b71d5c6ce9defb78efe3cc7dcd9f0e5f62dec1cddb8259bd7dd6cd5db3bfba8f93747157352176acf4298883eb5e0fca6296619f957c16faccf319989960261e0fa85d2eff12509661210f8685de3fe4f18072d84449c8ba2c5768c62c799f2e6314fabed3be5c5f9b9a46ad96b7ff767ce0dedcb588d33abed77ec37f7f07ce326cf23bb0f758ebd644b760ef79cca339f58f7701e0739d6feffea0120560df7dbd0cd83cdf65917ff69de7895cebf0bca7c397ed596bf1c5d645347e87e84dbecf3dffd1d02da2377991e74db20dd0ee1dc8dee1d1e1cbd6321dbeecceeb1d42624fbb1cd2c774c918d85fe301be6b17f039ed027a7f2f872fe0cea0cf2879fa2e63142a799519ca9ce69a6b3a742fcbf5ee9b7665d8bc3cb29dfd2cf9f0db9eef621056f230dfe3793a5342eff96ba698bce7ed4c01bde771cc01bee7ed7bf2bdef3a7cd9f7b5a9695aa4bda8a57bee7774cf05d19efb1dda73d1cbdd157de87d13bd6b5ad4920139c606df358e285487f4b5774df50b1864db3cb7cab43208fad3f3fe26394ed25762f7f4d0dc451ebefff607eeefbfd742994ed2f7a06c338e28df534a5ffb1d54c788a2b7b6fdfd1d9bbe3ac8f6ace5d0bffb90f7f1722a33f43de7e916d0fb83be77fd512d6a01df7f03ae6344094ff6f7a0c6f8fd7760d7e0247df3ba2d2aae5d565e1e970e1f7b6c675a96b51dd9e3f7ebb99d47b7648f3fc3f772d7a5edf62a8e617f6994ebe2245dfac92ade79cd33ed723d865d8fe9f088ee8b5abeef7ec7d7e91851f007b9dffd8efb5d77f1376ff637cb3176bc9ed765bae53efdab75578b5ae8dfdfc0d538a2743a6a570b42e565b9b47d2e6befc9ddd3bc69e0c67e871c63b3705ac4fd0e4fc788e2fdd531a27c1f64735ad4c2fdfd1ddcdf0fb2690e2f2e7fd9cbd62f7eed8ab06eb9af717a039cc611457baa7144b91f64c7cbe91ddbb53a3c62631f351dbe8041e4be28a6af2bcb970850c7d8113208e8d90a456ced350a8218d393bd5d172e62fb6e8849aabfd14a2badb47ef864573a4b272f272f271ee8d4ef63ed66cbb395fb7a7974affd7d9ac3934d37bad18d6e74ebb446b7ff7278c1d7de37fc94e6fa15c1e12ce2be7b7c5d18dfed763a2c222c62d7bb811fa86917efe69793bd7d3dd9a0e7e9922d04f2783cf8b7d7360fde366df3e04ddb8a18d97df23ded6afc40804d879824fadde7fd9f7aaa0e4f3e9f3e99a497135cc4c9efed771ded3ada1b18fa726857cfe7e3f91edc3e9cf7797ecbe07b720ba8bf223e4fbf9c14f172725f4e823411c4481138ccd085232738ccd0052b3ef7b8e0c11cfad8a0e7711cf79bf62ef83d2e83214c297eef823fe8b59f8fc882ba37b93ef8b50ab2dad35b759801bc3b20da1b089243bcb1e75d2d02dfc5fbfba0beef61af7bf062ece5d0c7f65e5e06e0a75a04be08fcfbdc7ba01e226a0f6a0fbea6c1fbf7e6d0070dbdaefe57bbbfdaa57b50bb803cae7618c48144f6eb73269e8ffd7cb80fe7711fcee33cefaf690a75efe1efebb4c912f8b9dfb60fe5ea794fc15ab91ce2d0c72ef9ed2bf82539ce12a8c36b73dc8754dcfb7ccda107947dcdde779fff38efeb7b5fc1bf0fca2ea0deb6e7f2f79f2f871eefaf07fef6e0dffc7dcddc7bdcd74fc7dfb20ba7bdefbc4ec4bd4b7dfc5c7dfc55e3ab7fd0f7f827a3c5134dc34fbba72d610870365cb34675586b5771add66ef7fb4017f4fee3f9f0d7c7e0a55d7dcc637bedf1db4eb76c5aa38f9fe28c9ff37e7bc7da5aeeab7daa45dbd3c75fb368eb449b6ed15e0b01cea6df69d87b95c9051f3f248fecf85d0c4256217998396cdc245772256df6942b5945288fe4b0392e0bfd1c7707ddfe391cc714fa118b417464240ff3a3b6ab901d4a3054383232080986c8c3fcf9850e3b35702d577e130401a0b03131330704b051805a821dcad5a90c275fa90a6000ac07113187e738800d151f4285767eb8410b204401ae4a6adb58912bb922d5b833bf83cab62c76c80ee5aa2323863d6d54aa283a32ddc5039b85b599241502107059a810f48a36a674de8ae101cd520c9354e3b2b8962b271020840eeb6df6b4377bca150e1adb8a8182ca4df2ba3096ab1a5787b5e50a8ad50e770663161d3a76a876d818429641aee417d16455518586ad7262086b83071bdec9a238606e6e56339b21eb4bf9f51473f8cbbca7a42faf18c2d81598ea0b587e586532191a1a1c4e0090830e31d403168c8006224802091224489020298185fdc98c1faf8c5e96eb02b1ab339aa419fa21df38e122934e02ede427a5b54a974a4fe69ce2073f901209cbb24a20234aa5524e190324961a54f49a5292dc84030709470fb06faea7d3288560d0ad8a6f8c485e9a38bc349fe4262166697e146e0060bb56b994c001806d59a3d01370487213014280635f57b43a96caba41cdc21347aa2002121b648142811380e1850c1641a02255638a2431a2d4d8618c3146771dbabbef20778c31003b7477a7a51a6374235e908c5c568cd18d78a18391ab63c48b1c8cb8538ce6d2001053da1bb07c3fc1618731c66869ad1a4b926404fe8e275ef30edddd65622c4a5121a5d4eb6f16bb820637638731c6e81d96e175a1bbfb07965c5ea834f401c5187a82257efc943c1172f71230461359533156f7e2f324d5b2648cba1932b08c28947b1aee4e6536991dbdf0618c317a16b3e37b925b63dcc38af202ccb6daba17703801400e3a58b4c4815446139fe45ea825eb5ea8b4521a008b4901cb77f9d58ad1ba81a3c60e13a5d6903650dcdd064a8d1d025022e99003004e70d0f969cc906163003fa8dd0e3008a1743e81fde54f77a7fac5e512a6c728e394d137eaee33029306a41d5fd24827293a7605963faf1a82ea5ed41a96f82eabadd4ba56906d014b18e07a992c1bbdb4038b28f2710e37652bb34849e8e30199709d4f6479ddad19364d428a6c4f1f4f3c9a2960a733f3fd9667b8b73eceeb6ac69f1cfbddced7e95c33260f9a701706fa7840136bada73f62ccd2d43ad85ace7218e8b395b4af6379b762a00fde3a11673b5fe7fb3a35cb33dc6f79c68433e1f2270767908c563b9f9565d93d978733f9449806fa74ac9c79ddad977b368bb1925ab5ebbd889685366dd3ec07037d2c6722ca3c255fc7c2bceed68fbdf0a681a45a37d07e409c89c86e60367a2d6b40bef6342bf12ba111683f20ce4464658c64bc280b11fdd3f819986f3654f2f1809ec60cce44e475d7b1cd6a19a36a7d1d6badb6619d4eb5ac9a595054cbaa56ad49585f2d28aa6555cf66b33c13fa2dcf943c16fa4e16d2a139091fa7d3e9b45272cd9031fa441dcff6b18e812cafbb170e6925a0219354378f750cf4e136ed566b6da007cc33a1121b0a6ddaadd6da387b61a08f07fc3a9666dff2ba5bad8537ee566b715bb5b56ab75acb6698b519a66579a6f358271ea9c127e326908d9726e866cf0f3f1a9c4f8e8c285eaa9655ab65d56a59b55a56ad9655b1207406e91c21a7f60122d3091a323775a9b82969a7135803c3ed6564c7cf6a44d9133b7ef6829812ca64dcf4f9f8198d9b4c3e7e161373801ff315db058bc28e8f3921a65c0053c51cdfc7c78ec41c9e7864c7af46dc14fa08aad991e2245187ecf8d50431a59ec0104d15c60c3b8c1dffb4e357548d7193f7f1ab8c9b4a3ec617c2cd8e548b1d9f5e114dd58a589a1f522544534da5d4901a61c7a7438829f5e92ae6e83e3e55c51cf8e3d354cc6124e6a01f710829d279257ea5c81592b6c76cdc5ffb68af297b693b9d8f07bb97255e9a4b64c6fbea7d4de20a2db6b0fae44cd249bb2c331ea745341185885fcf2af6a08559f23c65ccac962508a4c2a26241e5d5783ee868b3ab9fbd3c36ba3c637b76ccc6da567f724061782b9fd25be56c99f4b1db96e32ec1bee4b2cc80be7636dd169fd5e70bcd338a9940ccbf247b69decb229c95976e22cafc6e8b243345b485540c4a0a9917c4d2fc26541049f36d4057ece9e101ea93e325fde1928c41d9e36ccc724b9167cdb26a8c75813a790604fa0a230490b73a79d963bc243519a18e3f46e3351e5cc045c6cb9f1cd0e969d0f0809906ea350d1d8268478722225b73c2a743108d6357bd367875d9c46c9a9022f695f895cce25834dcd46542edbae86c3031ccd27c8d86a57159bc2cac9d4e604d35495e16933404879e6acf9c2f8bfd8c69cc535d4cd081ad46286c4503157b98ef834b5f28408d6917328100c184563544051c7a2b4f68cb4b25351a3468d8c0fbed7f6762f2366b40befdd04cf15493e4611149f3bbd5055c62cae70bcfc83ddd242269fe85b934d78a9b52617161ae0d6633424dd25cc2e2581c0f0b95278b64a2f8e47846b0d7b29d2ec09e3d91e84bb29092192fe347d8cab5abe6d1499493453836a290b462872229f6fc30eff99fc7e5021167cf9f913f63c41ee6cbc81f37441fe68ff2278c08c47c5006c56410ca4ba04f4e8c396c3ca0ae94e84f4eec610a2314b6726c758962668488e06ad178497a0c4a9bc8d072b0d309fc17d3d7465b4df32cda1a00ac67139bb177f28ca52fcbaa54cb32ecdecbe289a17a38933457d69a812500ab811f2fcb2775063a1b7bcd4627cf7c3e6f6521d62b71ad0851e25784288157462f7a93afa799f2594d521231e5f34524cd1fa1bc5469dcd4adbc54ad88262f09b1a62ecdac58c4943b4524cdefd411aac690db61a5a934a37cbb883dcc17e58b24fa30df245f2d2210f3bd7c6ff25dd9501c375d1cb7d9d786e2ecf9f23aa0b362e96b137b981f5282095b796982dfcfffb4c1f3f33f61c4203a1bc943cc813de8893d3f4e50137b8286ec9c8832fff38518ab99220a2392e6dba46a22cafc11ca4d222d6249a4842444235bc4833853445244d29d82c89ebf85110a0c3823948a73c28a46267f72bc34df66d06992404260bc04427d72686caa4534a125e0f0da5c5f960f46146cf5c496a1ab60cb1f6427225bbe6b8f62cbbfb4b3a0cbc29e1fca6ec44bf24139b5e56b3ef8e0f520001f7cf8c1e2fc70592c1843706871f6fc6bdb98b13f7de599ec434456a36c57b187f9f73453bc9a494a22a6783e88a4f9ddca33e256449387855802dd9b58a8844a1a4b8316754cd18c400200006316000020100c8945e3e1445002dd07140011739c565846964ae38120884114c6400c638c3186106208328a285353430400d02b866debfa0f231168d8db4d8b8531c165fd36c6785b90349962c9f67f2e3aef7fddfff3c9aab03f2db33978ba689278db7c0ce518bc9c02b2acc313861f6b50b408932419fe023ee62c8ffbd46931fd8241a018f6b546f398302fa77701d9048549e68cfc51281a752705699a2b7839ba5e8fe7cbf6ba01c52a63ebf9c22fffe27f04986d20699d2568ac23b170fd9107704026e9814192b16d4ef2696067462d1a8181aebc4476d33a0e8dea9a59bcc5745275dc1a13c724e37066c2ee793c11e2bc4d792474bed7db019b5a82bb33b2b4ebc5f85c82c1640609c7780eb48e9df8d53f66e8756cda879f030bac679dfb00cf707ea2d41e8803c00727c03673b94dd2e413fccc82f892547a98ea9951df9c8e4901aed55e47b9687408385dfa8a0d2d0ef4a9e7ff09e8d9f580a7cce2029abc9823736a16f91cf2858d990f80f41457929f8ddddfd6e94daada357615bf4b66541e5bd03c7de0a421954b7667165ab3850ed90a42cd54ab0e0108d6fbb4b911a7b748104c721f4b87a5553abd3f997f32e970818b3dd84cdaa611f0377f3c879d02de04d38521f6a6ea1d8010786a800c97c6d8c9d92140ac7e4b7f4821b051d4c038028c8b1c6ef5a80e5a1c17d4dee852d178b55bd79bcfdff4b6b4b2f6f42fd2573aa645d026897d8ff1cfd00c709529b1a4bb7d3dd2c13006c52e1b93265923f7b0574c241c48a4df99b23134a3770f78bf6a88d7e0980926e5de01319e61a267c4500e38cbf4fc6b073b26bd91c2b249aab0d8d92feca4c046809cf37ba81bec1661fa1ab12bd3c0cdb9d2606e0601b8719c3e9b12cd7b054edc148884a99fb34922c6c46e4a59e161fb044c6f77413eecec15d7c61b231dfe38b3161745f6268740baa0501cbeac29b69f09a8850696a52d021d3c99e37ce245df64755ba2a8d8063062d4643664863a62b81d00654339753184676bae3b463de480f1592b3d743ed56eb14f82e53d07726eaa751079bc7a8d117519bae6b44067d60242c41eb574c6b9c7911677cfd2d2fb05f2ebc1e0801e0207d2e4bc2bdf6eb43d9376f2416399dc71b6338d6fe6375158f10a19fed6cd49277120b6c8eeaa2315d626642d21a014ba9f0ea3d0b23a1a616174c812843ed5a749682565d0c97e7a5955498550dcacb4f2b5c458ef16a741ede1de0d046d79daf6e1de799cbea3964ae1d7a70a54c6ca98b4e57095f479b6a6e333d033a6dbdd4e45b216d461d60ca5e59f74e3a7daec5b4048271f8008ffd9b4549e9ca68d3dd679ee1b6422ae53591300c94897947550e4eec1f6ba7928b1c90fb97b1adff01b812ff1fd244d94b43b974faad5db75ed7b292caaa106550fde9ebb73ecd9d95043d9cd2c1aa0f732cdc327e99c7a0c64808c216f754e397e9af04e5f979d82bafb433c42b9d0e80218e8e7e39334b6f7219486e24b99a728a797f149a0ef263562bcee152ab78b6c7260bf9ad982e50913c6d1a870b25f85fc2eb9303998acf4ca8491ef08ee4caab2c1b7174e7f643f08b173150d6a22f1dca6127dfda7213e4e79e783e07f3b19c41e30a18501eebdc39a639718e84933b48d0c54754dd0c286195a0fce6862379da34dc2f64a820b9407e7d9c2ceecd4611417bace016805c3d31ee807b7624af57a3828408ac4768987c8815f48b3e0a16a3c2ece64880a43c8d0968fc56307fb5914003ab05a233647c506c84fb85dfea8803c83775e0a94b7483c180e8cd8a54352839b60c66c2aecc0315fd3041575048e0aaecd28576e916d92e0d02d570b10184b8b4ad00f32d581b48310310ab83d33c21d0c77902c5c97c70b1d917d9c70f2c8c8e7e0d9fda24710125daa7461a0ea432b398e2af9c755c0a62f74c591af50b06542d1a7910ea84729b011a537ca1855380fb66337592381f2fe3f0390fef08dc20dd15c635690820b2225f3fa2de346bb3e9a60af20344df92db77dfbc72f524de2bb3f2c1f91a7962724eceed83bb9c1a20993d5b42b5fa166002c546be1b74c1e2ebb88605c7d1a3f4bbcddd9c7b58f174b206315b68a778bc88cc049c28014cb46140ba1b728117034b9277bee29eb12c4e6387d450508c0a9856515c286658163729d9d90acc93426e20ed8d3f195c604bdc9681560c6d3b061a3b5bfce8f90d3941800a1681cb2d6137fc0532dd9f8fe096a268fe91cff5c6e7d11c2906382e92ff822737eeb419a3f2c1a93163b36d800e923638101c8b42eaee35c95e4707500d69ec57befd82625527252a551087bafc0d4f58741873c262d376b35d87b5831f8ad1c6259fd1c48145bf153cce12e2fc2d01e5ae19c20ff1d79ff933ac4b23c1eaf6ee60128d11eb269ba0c68586391dba6a28cf86a809d857b1a82175866240b80982b150196e8cff86a0f6b3d675c4519d6cdd8a023f7ff8c149c0872605519247bdf0b40a0a8dfcf4d50178678ac300e066754452be32ab007c5ac4ed5691060f224e375dc2cd3ec05f67fa16d77875593cc69c7ac770794629c9dca60c53bd4cb5ba968aa42e5788ab4f1502e886fd1fb536845a966adedcbf5d9192838b91c8605713bcbfb39fa21f2aa4273429010b07bc48a83325247514c27fd1dbf12683e57fafa0f97f099a6aab49c6c85d9469e4971a9a34ea83dc55444adc9bca073e6081cf5b2e36e901ad49310957d37eb494c71bee5c0f060e1af6172032855de0f9bb1d3919420da69269ccf2a6025738232cca540598a88fc1abf6c0f295550a2b8b1308bcbe11e2b1561eb349a974f11bb18e608ec859b9ea447993f4f9409b5ac5139cf942bb9a8ecc60b12bfa69d32da8884d53a18d9c7f17b2425c29f66f922e15fec5d638e62fa9b293b59464e6c32d252ce1bd9189840c38b76e3de5d2eef8b4d7fb5870f27496e9a2ce859cf7683b21c9298c85b44a936e7a1b12ba5f625150b52d3e54961ee8e6aee7852611e8d0cff047fc1f0934372bb4219d976a44e7466e14722e5103ef758be00acf6f951d7e31ab87d508506ae51821d7cf508c1721f057bed55dbc12d622b55e847ed6fde7562c3cebdddc83bd15d01b2250479a3601003239cde28c51998f07841ef1cc86475cb18fc678796804118750c40a20113e389d888789a4551e4e1a59da6f506e755cc67631d3019ce4a956d202581b00bc62a383d8bea96aba697c120607ab620c3861635ac2c24516a45034351099f52fbcae550414b21d1127dc45a2eba98d5984ed069b906c6780e4585298142974577e94a9f924cd074545ad44d14093038e9a239630c086085f9b4647ce28950226a031ed5002a3cd71c922c196452a853b328b116c5f70c72e207b8ca63f9dd20fdd9b96344f42674c695511605a2842b7640e332c8b4a805e1e18a498067c3112850bb544a9450c0050b755c01c6e03e28a1f4bf052f6e0cd25a1bc5bc36de95014474506733c9925ec1f52c2844d2959d496b69a087d958bfc6810aa2702ebd58bdd4412bb93c8b042e7e49ae620c4d81299e4f0901c144b70281e1cafd271b07ad9baa23791daf9507373ea68beda016920be44960a53fbe9883f127d3e28245ef26280a13200cde3600cc3d251288821960132fb0223802f84fb5f2d7b8aee10ae936235697e216d0cf208c272859f0a07540c822f41a158359314f6701c1fdda42c79392f8b0500031ef408c6bacf111ff53a7a71720e47c74e2e6f3843ca0f47a36833eba2bf5901291b9a12780c20dae8351f7ad13a118c84dd3a8d4397cbb52f4bb384c7ad192165a2a5e1ac1403950094480c10780f3111de8c48a97e3a129ea0545d339b4b0e9a5d087428bfb97af475cf0ca7dba1f071068868d906873551fa197464ff5e799f0b1117e8f7699e016e36767a2a54d1f6eb106604f067bc764094e703392614eac1a807142ac328003a7964ec77901055ec5634972844e1eb5ababcb78be42cd2a688ef8d795ad1bb19d80615bdabae0558515668bcf769ab243d9d704bb19bbdfb12201e76d66dab9c715ab1486e1aae81536ca5df48da6d0fd7f59c1821b6b3f11b140bb435d4c8b10162ad2690ac511f22532bcea148cb968f9a01ab84bfc158ae46c0fbe43d31f991c95e0c8b493e5688418c74d64814709496d4deed75f1c6adc5797296ce1bf40b82d40120a69775359acfe06026bc3a0e1cb46ac34d93a133d71b01c0efcae921a7eed709905d37689cc3972078d6c6780eb7800518025fb7bef0a11f4ffc5107486687ce04a83fc8d476e909251d26dd2a514bd8f0660cd003868ff18e1725c002476876741818d8dcf1b1130912b36d29889ba9f35f3bb4f113c59b8ddbd4f94a8c577cfa048d3f75415c20c094860ac20b9d4589bbd8405fac27e9d5c1ef09fddedcf849f7ed109fe8491651487253e331c925539292e49794738d2f4a453f49b96467ed687fd7d8a8ef1f613baed1bd78f798bab90414967517c104c783e82edb2c67b44760c8fa50ae69463a01256595d3c39120869f57668c7344cfa43288be82cb72fcf75cfb3c21de0ab5fe9d8834e6b7907eb2bba9c8c66ef1ddd40976196bf678e932b0425f74044264cc35b8a95fcaa8b94548f4134dc03d2d5f0616f828bdd002d29d74e6907cf32e528b60f7be16455083ff859420aae5d55346ac9b25b1541cc433549f26b7ef1d94697288d3466912b8ebc02268db2237738ec2ec90667ea5c57880f2572859ea21cc714f344dddd6252fd62993f7ef295f2599796955dac92a7b66c5592fad8bbc8e2ca438a1231c4486d0957ff3e9435c342a059d840dad29c632ad1f838a9fe8f66e50798cc329fdb75fa932517bb88b50bbd48c19939dc2f8ec0563e513f95e93f544e2b691b433865dae146447a50bc90aa46d0c7dc6af6c9a1d964cb889744025fa9cb932f518c25e04f2cb15b10db9df672c33f6235ae02b2eb4be06f7880ee4c13912822a6f32118c6b99fad217223028a5bd5ee994baead0375863a520cf20c41ee02857a083c0f06fe86a058d153f0ab504b4da0e43885eb443ab69575bb97998a59d4d3cc87503d5ddfdc53c3817cacd91656a63aa598ffdd459393cdb2564d70760220554fbf17c5794391e758149b93a7523b91d6a6996495a6b414f289e9812a8955b624b44c92fd473a81edef67228a698e1aa3a91218ca84e800c876d02694d7040d190ac4e43f400c5e856aabef4c8972a0682b78af1c71821225450d3637028083424fafd95c1f3534ff380b2327320f6319c263b116bdf94980e4177d1506675a8607242075f70516560b969308cdf413d4235956cd3d65029dd5178f7071257f4a7bbd748c9bbbff3944bad0a14b627ab7a235e39e15eeb6e56e82ae5b30a29711d37f41cf792be471baed7ba2bb9b8b5424a0eca25d12b6e0df781d2d472491f0d80162519f62743a2faca646c77f899641d114e255b7193f0e544b1e108505f196aa1f7a734572f426187c1a4c8d0af58c877d36a357b61364a50cec11057556a4a93063a76fe21ceb983eda4a14add6cff562c52df57ed60ef3b49949ca2d9351ae483e893aadd2e4099a44f144ac3b25a7d231b356ff11ff9c08a527e6b42a22f9f57db7028ff35621758845f7c4275f3bd816ff468d0f6f5cd8161fa853ba0148989f17991922ea782367f0325361ce1298a908a80581aef162110b3e3702ac690c86a61f2cc267175ca9a7a341b3cc243b98de1a121463392d2a5d275b0e59d06e58b7f09b85d90f43919b9aec020d3878f31bbe640cf6b902563769cd7803a99e52ca879efdaa5cf8077cf569e38a9ba9f09429099a010eddaaaea663e23c05e02583034ddc0e10d349b05da3767980d15beb33a22f74e8532de71456e2d582bcf259db1c1b6fead0248ab2ac7139aab17c547b121ce2a591b6ec01d0a6e30e164e739b2d9ee6adcbbf7c3bf64878bd93204e95a2d7fab074455334bb907a9f2502986fca04dba434ee742e5f1812727daa90e689168ed2eb2d647e6f0dfc551f7ec0e6d3e096cb8164bd25ff11c7ea92a07de5a50efde429ed8a0bf340d2e78105183f833d67ca4e6437d146ce5c84e0b28a1f07a0180b7c0a61e6088e5af3f70c41bdb23e34fbb4c746a4d05e0a211b96b8ab1984fa0722262db030a7f92d66779ff1ca499932a820793736a0d81c64e27fcfdef2be7fd8f07923ae57008592c82b7005b84477030c1666649564ea1c5263d92eb449bd15d222d16457bed9f96db2ef5327a214a2380b17e98f02271465a630630441dae554a1aea6bec959f680f4656ca79aee431ed584f08637fd5b687394b054a34b3c489da0564ef4a23b70425b400c8277371a2d1406e202a5dd2c48b6508a733655bbce974de5cb2ca7e5ee9c23fdb2bb6c8933d097d1a532d1aea8fe987d778421ba1424e6131a08633f1dcda65ca17cb39ab5d3d69a8f3a1009945ea168c3aaeff3c91305415fd14b7378e32bddeb13c79f44025ae9b6eb75c62aca40e86ff13fe0bf5214c9a14345f117f252cc9695617adcf3a030e478a50c5ff5ecb47a36f1c2e91a52c4d8efc1a6fee05ca718f41b6e20713f9578c5bee0c6d3870f16139a95958100e2e562f7c47c4dcf4955fac8b106b42029df69a69c567981e7a789a671b7524494542316a497b9dad6480420b7411c7052e02fb56059025ebf9182952d9c1671ef9dd6bfcbb64380dfd2028a89d9648fe6894d63bffbfcbcfe690f5ba4e7cf3a47371d7215734f42d41a4f81108b2534b0182be3d2b48cc1ba9bc47e2f6e0098a258457bf1020aac878844a1419316e6803ec6fa9f61d3710b3bd5451ae1c85994d5414949334e97271a74638730df3060ec84edd1da26452c7da44490297ad0d6bf0a65378083e2d180cf22c2ee78a2304d95aaff1f76a5540ec65f7aa5f2ad17a1390931875911d4ee3ac65661183b15092473f59621d6c7e16b7b49c064cd67ff68152509c3c3957bf1a3897559b81364c6c3e78f83a6cedcb77f339bf3d9d1c05a4f3ea2f6a800adbd308a9e876f51578dafe65a27cc0655fbdc97b54f937e84403d1f3e33e8636aec71bd2c21f7315c8c74729ac58d9bea3629cd699af74a592b144c0ddd1aeb152f9d0da3d34487824e782b27f904cc51fbfd494475f1a1e5bbbb0b281fbf9b7780ea7916199e5d13f9b0ff7a2f4432e3e40f28d427134d5a1d4bc7e09d3d05f86f01c9c1bb8c0791a92cec58171506f649195f5912fcef75ee4f92e2532cdcca95971a63c393503bf5f7a39f0cf547427823259d719bf81d46636481a6744fcb3e6387ae5859e545879de8b45c8007966380d92316ffe102f417bab38f5670caab668ab536e681b47f5f2cdde62df673f72a90ffbde3bd9b3e90e9f486b1f936e4acc906181197f00f70618735bef00de47b43c4c1963e372e2216fccde485e3d308dff786a0275449744d7e8368f6d4308612a2ba6b3d2b514d03d7e368c3dcbfd0e378217dd61b8c2ae6b17831ae2506caf25f9589d9906cedb2745653cdc43e52e775a248fcb9896f0b37b55750699db4d57cd8e74a143ac28ac783866761ca44f3df9eab0421e635f22fff00018acbedacf5f6f69c42fa5ec55d9986eb01b301d3fa763e12f0df3db596e57259b5c09f4a0235207a939e68b121032264984418b7fc516c62b517732efc16c9b9316fe3dc6068051285eea71345f09d97fe15d3616f73855adb7a9236179ad41b9bf6e0c2ab770022407c594058ed9abb5924eaec5c9b25c18d1a720ba3bb731e3b525827b616e20fcbebb43f28e3ec8072c2e4c070d184dcce6219f9c41384dcb4e5819612b1950cc13bb692d2c6f517ee5a0bd884ef168a6c72ac0221bc08c6eabdf1bd042b0f8b60ebe832111246b5ea86f0152d30fdd7ca4166fb7cf5de82f1f8389cf5e7dbd0fc966b8b597c9b8086cd5ad1e5cb01f884f88d4c5d06c0e89a039d4f765fde7dd77491bee58a585bbbb216eb0c71202727b6279ca48abb861b1290eb38ed352aa25bf2a355f25024043259be6b4cb1d67ae9498bb7c277633ba2993a4ebc5de60370551c916b368ba79d3cd046a72ffb4d3d5836683cdba751eb682d0a3a486ca8cd8c3e92dd5a2552bf440370655ce95bf217fc2d45d324114e141d3409922c25e5261e626aed2f7181a42f07c1364fe60c3795a421ba02bb7c9188f34207a1e309b9c9984b1c7f0d78dbc03bd5524be6c9ff08b43a8a619cce556c631818f3f28faca9d9bdfdbaa219ce1011941c87712f535a3ef2d65e73268812a54b3b6fe1b3c574dc3fe4419ac8ad0e04633ac31cb51c01f67f04b5f761875b0d43c88189baf1ff954bffe77b1b6500d452797d151e656964cd6f07d94671407663daa79dcdbe12d85ea7692b00914c702f81de16f8fed07bac793fd1f958236fe943f348e8061f20c712393e9f9e1ca94bb3d8802c9f4cae7102b41924b6c979af6d3404b73f552cec8589a346b1c4f5aae4a7b04999109f404fbc6b2320e006ad389e49bf2702dd6a1f35d03721cf2fffb7d9f9e551335142945bfd07029c995cd580e694d2cc5b588b1b22355938b2dd3449369554041bb25b5b57e103f819488ee3d8d0d30c208f2b63f3117dca78655560cd735f4f17a3d3c32b97d8096253a7f1a188a96a465d7a7c607b511032748c57e0a3b653070d916ee3fec3832104360c558f4a63c69aa64983ed4b7ad6a32761bb341014477eeadf81779192296bc1d8fad4e9336b8f90ab9372ed02dbe167395ec89e7179c4198bd1520cae884e22733a509acf584edb54ccd04071dead1a54275100a532f51b97e455640503bf73a979829fd408128b97c656354b9af8d23dedc78a58c61553e5dabb4b1147d4923ee7af42f5d7f06d99ede0c2a561a2ebbc06d5e132dc9c55cae4926457b78ee7eaf70f1cc1b01e60103c66f274445386981cb146ad991d7d10cc4f639f8caa429e62bd55c907a51a50804ed225f7ba6fa9b109028619754b595d959a13278a16bf2d5f0734b9049c3b809fa28d32bc1b1503c88f21b3beb6206c51b74cd2ba0479d0e4f0a45790ed0cab5ebae09a684a3448bf0a523f03e21f054b728c63b6d5d7f0f206161325a4546c0ee7d6f4cd28f749ced9e20dfac9f889eeb5b6b85b025af1aa9f3f1862a54c1580681349d7a67457bf7c354bbe4e253c0612f650b9a9275280acb58011362e87880f8605bc2c8c6d8ed62634c3395e236f4f2070b1453a8d7fb6bcdf0c43b817f0f46733b9aee7161e2c0861323b47b9cb1849d0f559731679c78092e6b0da01b4124a99d88346341d50bc415dc004df2d9e32bb4f2f60163623a0a4e02235288e2f5c256088c6849cede9394d2747890f343db1a12d56ce1d0d26f58c3cfe999e4d8aa8282f20837cdfa18ac4099b64f2bb66594f8d200fb52e81601638e6e77ec0f7bd35a1d156462009bf0d598280ffcb9987dd66f5832fcdfb3a961d3398caefacd728b43811b3ef94a5df7a06f8efff6c5f5cd4ce3fc07200ec2afb895b2b2b67e31156324e0d4873df30949c15933da6e52e09f3ec07c8647ccb8cff8e12795c5f8fd1e361654336825ed24b4e5164175d82ae68f63ee416c9e0e4c49426fc476a7a6ac81242ba88fdf912d74a94d9b95c583f959bb894216a1391a96f94f44dd18602723a9c65e7badbebe60ad3cda9339c42cf2144fb594dbf4613c74bd39dbc0dbb3b0cea603a80c7ad1d79fb9244912b2dd24ee7b1dbe013f2f4f7ff3d6ff9db09059e30fac193a984eeb98b356ec5b94558713273a8614d21d1fd26a957dcb09e0f9dae1daa88188ef144acc4207c2d3132dd2087fb9acebfb5d13e470cc956ee26172d48cc31c2fa9cc7027aa38072dd1e99e543ef6b8d86821d511e15383c87224a7b6be07c2ebf46f2d1e7dc951adf913886e5c02c288e79927331d199838b9991ef54b348f3c597d476cbb5dd79b2c915500d525629d0b928ea0900699019fa0c2d06230b88836e0f65cb8838784be2f70245592fc26e87eb44c0a61837c918179b50ee767f13e4946a49b82414b0c50de7dbd12d1a39996401f404668cdf60b67125b976a0787fd11db1acbaa9b2acc06013c9b69875dbe980dbaebbc7030ed5e4087a5924abab8fab2f4ccd8e6d44b844289b8871dc777d5ea50a1e758dff8e0bee1c52cf5bf366f02b3d12e6a2ca9ca13d309a149d139fc69107eae75629263663c583154cfcd96dd70175d75233dc1909babe510836cc79d73c930ba67a42759e758e7475d7f049417a6aefd868420b962cdb1e365a4b0252d4fd331d6f6063c78be230feb88ffa1f7fdddd9bfc8527046434240e3430ffd39559d156375be11b884c6a7cd498d883b6790bacc1b9cdae34b2fa35f2b9d204e1866c042ae1349b859d8d1b461b04b1ab87dc3189b988a4e497453e97d37eb1020e6f41487424fcbfd2f9e048535eebd6423d99c4b8b2bac39aae7822cbc345a3d252708bf28a183140ac73a1f63293c672281c8859a921122e4431ca21a27ce479adcbf55b7514feb95aa4273b416d2836e4513455af34b5f1ad792aa5d61261075d1c1f54924e0bb39be8174b41f6bf1f3a360e1ea6a94920b29211f013a2a381ef7b7ab4723b40d185ef888225aab360a4d8612b822527197e006b8be5b4fde7be7dde5e0ccb6f7dd6c508097e99d4a28a5eb04f01007314128a3610f1d12d0768abc50fd31d2c9383e699107679e4af3f2056c6395f2ce35c8a8756a288f78255637f6a575d4c24209880cf6e15634559490d7616b8e222d225dc49ac803be8f5dfc1f238bf056df82e03a952a9d5d05a12bc0bb26c4f59e5a23d32515847ed70e6cca4a3b707d09c3f3b3f61bf75e799d8b7e067aa7531b5671dc1756bed93717f205b79f85f469038892b7843aa351c596b61e06dc3010383a609ae19e1f7a01b32b31a68b61d8a5b354312b0c60b4f45c6021b3efff3150a6b99d44eead0ac2bc01e5168cf1334e3dcdce78c306ae2cea81900fa72868b9540fa7f5a367b6ee84813396d08ce50b24b050ab06a5436b96ee68872688020fd0534af7ea112ddefa24745922d89933c15c2854c1e1bba0180ad0ad9d9a74a2e52a967712c06f3af7d30199d203ba545d2bf5e5bcb869182bfe6742120bfb15805e106c987d2877ff972216cd0c49417732edf79f2081b0213b91ae330f56a41409fdaaac98626823ee798dc1ce6c4137f8a7f0b81153da1191436d80c0ba7c7c30c8e9fa536490adb33ecdc5f107a2629969b9672264235c2e4fb84c1c872d99da7f00980348ae450b1ab2992d1c6ce0262dbfa25d70abd4728efa106b1beaa4e6706b7ac0a510580078539d6b39ec8420da3eb2d2e91272d89fd03655ca88856c5c8d0a51c64909b760d127789aa43937895ad02b6a490856d915fd79903cbd6644702248cf5d6cb7cb513bd703c3dfbb76f33fb7ded519cdd9a8a886fcfe8664b53b24469b375645153b38420cd1676fc237ad9df8e9289cbb2755cc9452a69ca4a590ff9e2a9333349e15612f9d324dfea2112006d09e954c6842b5e4988ae5e93e3be3fddfd566ebc9a6b1684accd289cd4471499207b454172d56867054057bc51a75742a21455e5faeb1773fb29640fc9444ef9ba01bd5c609c7435f68c9728c2d11c36966866d08981579aa6328cbae372602852dadf2af4f1b64195db85b3489951921fff6213427294649e243133ed0d96791e6d92afc86032f6447eb286ec899a675cf74bfde423011b219a796fae6aa82e539170be887d72b81663bdd128f214124655ce638c61d3c4db2b3694c61603e050d204a68965c20352a2ae14d068e6f2c05495ee9b21a4d2640c1e5c9a7489286f6826d7d44db0e4ea5d94e481b522e82653e0e092e4d2b53392bd14f6bfd9c2312716d190507eb4386b66efda5c9ed498c14e6945f728b38b5995a7445d06e73270b81f0ea4d2515a733a75e8d6d702a85b98b81821bbe377db96bc5656e9eaf4bb5c8caed453fa6b0312837225f2dea4748904ad6ae57d2b8995ba9ff0b6c54eb0799801107d1129545e312bbba83f11e014d324461fd9fecb5dfdc3413619665220c2a4b7a9c6a5d383394f20355e39431e64e7bcd3f791680323f24a2d201baea94c5cfabba30f0abbd73750306e0381385f258461129096c95a50174cd23fc826e6405b8756e5da815c4509df0169b635c92b0a8a2631eb87ecd47122f9409acdb14bc12609e49a364dc294ba9f1195d23a662ca62e0cb4dd25626fed14eabaee9f24311972db6bf6fab8820942406dd69824eee5fac160e8341eeb7353c2898863ea287b1191cf80a176f751799dd78de3a1fd890acb427a0639d1521d097e7ecb4fd6bc152562f016f05c49d98f2571f4826ff9d08bd9302db0c3726b6b805cce5b34a99e38d786e0955f510d249eef1d1b1b4ecb903a15cf1457fa2764c404c962f346d17a24cae39d487a21d6550fc08d2805a444e93533e829dbbd3c223658a33cedaded4db4ef702d7226cd2aacd4f8b7808236e52c48b42cfe7d40e945aa21fe57f3f18bdd9e780c2474111c8a13a33b1bbf42c726dbd3c90cb6753f4b7bd28b549f8961ff0391ef8b800312c8de6455ee481b74ad090353213eedc40f522f32961deda57eba5d9914952f8069d1f39734c4e7bda0ce40a6d2a09770f4e35e44df0543b10edb8be0ed3b64acd4fd117da00049e1a895925c795c2cfe1d19460006f7bad750f33fb0f56762fb67ba06a68a57ef84d4f6672bce98b7709b2d4f20e2eaf98e8c1c01274175db56e421e76310cc508b4bef72f85c84f1210d29d12526b90123689ed2d02a73a6e77387a14e5fd2000fb7a31b0f91019c0f6b1c1b3a501af2d95250031cf00fe5bb40023c5701d47e109ed228e059a43466007839ff9b1859bc76b286817052c7993e3a7af0abf09799835186b8c18dbd5fe5d308e8cc4d9fd2c9d96b780b6da5f6b74e815c41eed0beae01aa8d8e441506a5f1ebbd0d28a7c00ea26fb0f0e95e11b2db730b186f6dcc05cd569897850b639ecd48a1d7502a4d96b2bf6b6fca9eca8ba20385d3cb2adc56a08b494ede393c5d6a944044618530c342ce51eb55454462def1dbc12884d202bfa020842d4baef71c3def27a064758c2c1ebf2b86285c672fcd7dda37f481baff566d5773d800d5854aae014294bbc84116e1f426f0440b4be368a7308a890a78a0c828c8412f1ea542853e16635187f0084fcc6fefe278adf671402c4017f3283d330edaa690f8359a448f8d8a4b29ae5a620fc7c6f20494185cda48b159ec7e6682dce1ba2606abc404ef7c5a643a72af4fe3802385e7c49bbf42e9c60a839b4757972987aa7961879c55e8be3c2a0721e8fe3007282612c194bfa7606a628b9497d22c213319335932b98eb025cb5485083de7c5496bd852cf37ba8783533bb51bc8427b555f799a28404852d149486cf44e12aa8c11eef71fd91a9c053c9bbe40b718790d48250696307827519c12457be0184c8afd51ffcca85320307c454834b55f288679f6926987032179a37c9421c13170d2d59ba0a4aef36d0348ad5e9cfdc34ea8754b9cda8305bb9dccb105f4a351d8ca179f95bee1bb53dc810bc1bf3852a1c894bbe9273a00d2b80c4a807a1d241c3acb11100c8d792dc2b4bf4650df14158e12c1e9daccb131958092ba2d77ffb2aaaff5a7eeb3e6f8f5a915889a3da403874418ac066dd422a80fb40a6523d8ac3875e23be60c624279eea2246eefb03c19b0399df0536b82253ab1c5946043c98fa1a8bbaee95849d003b0705035366543d6ae451ac3980f9e9d4455d9a34edf850f128d42032f1a605a92583a43987961f319843aa8ba799f83480e3c0c393b266e075df884be75811c87753e05974118d3d5653fa296c3c724e0498a7361905b05405a139793d359609c5d69a8c10a6f698a0725807906bcac1d6e7917f2612ff5a7316ffdc131eaf9fa4e06bceb13fa41aa84dd65d1750e84a4a85d30541a83b263f38d12d1e0856446b6be08c83d3445d075ce4107b1f25c9ecf4f422e75108f8d29cb4bf8d6c253653a5a9d9d2d073671756d2fda200d512a09f04fb71a872e01557cffd105eb86fc3c32fca07527235dd17f8dd874ad55eb1618cd2c49baf99534bba8a2248d5e252c499203101888479ae04685200563b2ca9bd72d5b06652de62588eaf57344b77a2187a7d680e98b6829944d1a4f263268aab7f2e7a877a08c0c4ffd12226fc53e43a1650891c051869f88aa8baeec1c076f3238f4cc80f4a41ad81451ed0f88166add166ecde03d92b21def265c8535b818c4af122602b02ad7053822d2a8705dd574e56161a21c97d50e48d04c3d8c036cac8a5e2448829730ab42f3fa016029929733abb2745bd800c11dc0369585374962ab78b0df032ba61d1c89848ee5915995a873f26a811e3dfd35b2925591228c076f596e745665c04947666dd986326e64632310d1364773a01ae89a9d3158155d749370edcfaa28feb3c46e3cd38111a5b0638677fa52f491e4ebbcf488c1645508dac239e24acb83c1eac058ba0a03c540bac708cb40a92934461e697aa687c956c54c10fc1c4ed0a25133a541641792e6ff6c524e2186de421482c4c2c284c422e401cbead0406b17269308288caaa8b556a5ae73e8353d9940f558e0ab0ea16d4a8a989cebf8b69b766e20e4d3bd53d10fb3e04a3282731172646da2bc275f61fa76e7eef189d4e2bb68fbf85e8ee52e5903955d58deeb94b7c1ae9006866d336e97382baff4db97adaf037a1a5cfe26116241b2855802f3306e8d5a8f72db3b346b310e16a8085e6418e2e9b470d23f2a39220c81216ffd5b011506509573b11de91a6bc418340bcfc67aaf4f31e26acf9b0933817e1c97381841dc4343e15f4041025ae38c9d877a57a3128a5125953f122a33574209efd16e46d10e795c263401d0e68831bb2018d30440a7c338f8371482815538f855219b7903fb725567a5562584348da2f258f4234ea8b019b20b973c9f47d59999385e3814cb5d42c07afc8f72a318c4867999b3d078f8dbf0b32901b2089c348e7428de73174e22d2a9ff95c5ff1d699ba6be0ad44eed4bd10c05173f63a65e32e80cd61c91405077b2ac02bef8234537971a98bd1b663f94188f7887fe59e34bca22693449a02cc26020d421c862a0d542108eeb3fd1c5060af3593d5b8c39080dd6759a08c08349be29669b56bba98ac1a27648c6fb5e98611c65bd3edaa577c99ff071caaadd384258b23131587d41600afcc3d1d4569d635a8f30c73a3983871a8f097ce9f2577bef0f4e7d1b8e4c039e13a0c211d0748ccfcf344d25d829f1e90fcaadd37a1d16f5199d4c63c0c464c7f2fba9cfe850950b490e7738a44f4d4604863281b66037047247c4046c965e06c535bbfd1eba56c02fdc8095f9f440153acb76946d2b8866e78f82ff835e2cc57504c97830f4788d30e8fa41d03c5e00badc8ee3749c16fee5469ca5f2e47a70501b1da115de3bf33baa572dc01583466e0de62c6df6c2e54e1124cdd6268164a73b1e322605c169cf71a2eb39a2d32d6de76a3abb7beb8e44339b1b224905f574e8456e4ea9737b85b7b488c60445add8b5971984fc160bf86aef94aada5168f56cc06b249005058fe002e318c365fc81820275df77336f8487e553722fa1aa2d962826730fb0c5fbb61638fd19f49dac6ceeaa33a91f559338e2f6a5484ba71b8a1af68dde94ce3f58b4ef00d513018e1f190081681a1be228a028906c54621317e08815fd621dfbd4b41166c0d8e63f18fce645f5b76c505f84b3f0b46ba11343e53df37452536bb1f8aa993de1a4ef5c610cde701d6dc65f7105fa01b3698617e55fe20dd5d2b8620bb66c488b4c1fe23e987b69019974a66762215b2542db98b3c6c3492c459a7bcff64875a9e44f0fd4fee05f53b243ff7787d6324e88b6dc858a55ba2e9f8a5388f3b22949a8f3fed3ae3dd25622117cc8cf70684f829aaf2f2eab3b56a3825dfffe013dbe263f794b3f0aec674ba816e028845a8e2283c9274b0329f1d7ad9fe3387b7157ca02d5ddcc80d881e0aa4c0d16fdd76f60357f2de6418d5ef53c491302cb93150608bb3b23c48311c84c3bbb4636b7260c9c8b0952628b3572fcb00707dbbef39a8840f46a1c908ac58dcd8158e3bc2c9353a72355211aa08a083902c64597ac4328b44009211070b530f788f7c9d53701371dd38f2c0a943f426f494e75c8e8c7cb16d44529b6d11a5a62912003516b1e236537d50bcb2cead68884be03b4bbc0218c7655d9ca28e500f41fc7d6390f749cac75619abffeb7bc0af8a6a4a7789f0b9c8d23edff268170d52736da8c300c957805e1ef5037348566c4172fb67841b239bb562915b88bd5ba6b7a7de18c6f2256ee2380500f7b8acb675f83b63275ad68154baf85fc918ea505835e765607612ad8091202ef368a4811850a9f82f9c197f1824f9de9fcbf84d14c804722e1d2a0adb43e853eeb1e260d80ddc2e1324460fdf2f05f735cb3cae420c496c0f2878ecc93c7d3d6e8348e8da81238e55da79848ffa81c058ec78c227f4262428ef81f5462dce5a796ef1a7a32d81c8bee91bad33b12e8f434c0cfe68371f8341abdcd6126b56dc8abe81801d4ad91fa793532c8294fe5524abdd6ab72ee5d7cc12ce0dafa683a10af2a7822f30ae03d9bea1252b46756baa51f5dbe47a84de1add9beb9a09acd603e48ae0304ce9dd0a75c6e4cca35733ea87ee110613b02528140c1d6520212722a88996d81c3044fd00cad381f2f5ca22550c204f4f3261ce7fb70e407bb54cb4396cbbac66208007e801236e9a653c81e4ba1d7ecb936697d7d9912d59321d0f97258737339252b1e71e179f1a2d591750b5f94da8f7d992cf149b46d66330bf992bebd99e59df7176646cecdf7fca96d597a93d0e072f16f5879752ca83039bc8c8f0878fa8a37763519bc98716b617738271d243e387a269090783713464fb4a34a65b712a9febb5c99bc66a741ca9c81a7c33ef8627c49e53c80040af0e87249335121e557bb13dcd6f82730a04c8a66b044322ca0f59b08ca342a9cd636cc6dcb43fd72e1c05308bafafac6ccf524c2af85bdd239aa351f804ec73887bfc564ad2c2ac0ecec13e23aaec956b100564468411017871af1d534b9f11178bb063311e4db9e837574bd4caffbfeaed66aad6cf14f482c15f68735ad1f1adaffe87385a6cf9133044e69f261fe3161c11037af8e564b91b910bd09cd84987320b4193e27f96872ef00a1c9a3d3eeae11eb24301dfc3f175e0c0c8aac8588e992771a50dc739ea73ae5280871e4491c0cf36c059c2ec91157eafbb68183443cda40cde54ced727aaf05f56ecac9a9224af00de1b889489249cee42688b2c83b993666c128a5f200f4265b852c9f2370b315dd4aa679a5f6870180331a905a47837a774c4e0e0b771802e8b8bd5c04c5e013253b097032d441e6a2ed768b0fb25691386fa09cd62a22b7371ce7422370c80bb29b7ad6542c20f414d1dbbc85549b1ff509bbcb993ec85e5aea381e0659eecebd1180bb9cadbc08a0324db42327517f2121d41cad25c8979ca389c1f0ece5abce353b53ebd7a8dabd06087e788dc0c4bfb2dc023d0b191e568104ae29394d337f51ef37f4ac91b76db60943f48454c948c292d60293a1de1ee98d0155afbc57aa483f1b169ad3dd25e8b88aa32b5852f622611f161f8e76c50671ed0b47486a76a014f098457ebcca50894b22071f4a641f5b4cfdf21ec15931cedb00fac23ff8c38f661b95d196a332814ee9702fbf9bae9d17ebd88850b04d7d6c8d2e099855a86a301bbdcf275b555f3c8166f660dc036d45a84424b2a0f81095b7de1909b3f054ea618da1c2826ce5523a989724ccd5f69683fd1c5d1a866fd181b98e620e880ce9154313e62d155742edee3c3d52e3b46129b78f1c11e1436f2fc5c43e666eafb32e389aee5d9efe75ae53abe10fd401aee0ef37def93dbe57ec8a54b22f7572cb202133ccdada5db44cefd75a68f6a23930a1f5b9aaea42909efaf5c6120d1106fe0ee63f6b3753e69d58d16b854deb9c4c39ed2c04b6aed80aa2094890bde2ecc5191ff8ada0392ca0ddcd8a7bb680dbff0df5689e21c253021c2595473045f0beb9becd238fef0568ad4ebb1658183a8ea03fabd3ceef37fcb9c41e9857223a82d6686956b6ce01f35483825cc09e0012aaacae3d5eb2da729481dc1263c0a26c44d95b960c483189642a29b15be392a9af31cb81539cc51802a4a0a313f5c709957747c7c62fc54e661b93c8a02bd0573db09c55ad8c82732437e42644c042f4879c1221f00847058c51d9e42b98bc7de4ff56d4a5cc64e93089cce94887c1f9653e0ea1ae561f2a0de0121892e92367e757c33076aee7289a1e8eba25554052b8473c8db397b590f09763130d9669849b51ccf09ebbd600fe9152f6afbcf44154b3de795c8318d9f60141a194f4d2b8fd20d486715478c90647cf8d7b81ba29b19ebc6fe9bbbc28360cf47b52d76b653098234f80f85794b52c17f798f1240f37fb50d9886739d78280ec4e9144003b678961472d129f2c707ba01613a8d435c9479c17e38ec2c1de484608171c503e75ed0c6a5480f94b574887d92c0a89253f980b32f726226ef27632629d676ee06e75e06412a0edfe14142e5684ef64c4b9804ca0533170124325323942d82f0b73c495cdc379bb801b3180fc6dd55558afd3b9f88fee51077e54291b63fab97601350d6817f525890c4baec25f97373c9a8ff6a00de4b57494c56247bac712d2ea552d6470aa40b6405c37017c77183329b1b5cf07f22b003331a4550fc65518a90ad9332f1aea067a0391bb5411014e0c9edf636795f5235616a08b264a83f457480fea61d238b69589d9ec07516a0b61a52141f4a75da8fda82b3a58cbfca8cd09841c5ca3fcd8c254c3584d6e938a05fbfd24919bf1a9bd9930839e1eabb93f21759a5f10ecab6ede78300688c2bfabe07cc0863be2363eb429bf68db4055661e8fe0676483b579ac24fabbc9ba9d2ab0e7103cfae23dcfdb43839d3f94bd37b5f0e223b4e0d235e84d2a65b3976476ad3dcdb403522e526a056861bb270ce47e7cf7625dd499692d42e669bc5fe885d3110e08c3f0e529bab8e7fa9dee3f232a560a8327ea6725b29ead01954e222101826ce9848a870616f9db171a920bd064476983b44ce51bbc05b8e522ae9defc6355166d2eb740cee24860f55ba7d9c415d1a441d5d8ef6acbee9c73d48472182110f67f7ade4489548afea25e712375680f87763c0eccf6d603356b4403bec97ef6cd12d14e756d313d17df8bc7a22e22f15bf52b29be6a5ca9f982eeb3ee4ea2042b3cbebdcd1ee8bcfe854a980e7bba5f48db4c5fbf1e759cb3277e518ebf372e693c3dd13dceddc34aa150618a49339e31a7a22e3570e694ce8d454f62f68d10c4743c421b0d7e0caa0c9c60102623f4761324dc2426418b8cc15b8237f3b434d72c871ed4572aa7887696a2f4405c1b81c996843490e12b1ed890be79444f994e41207ca5c7f0a34b6bbc688c15a2539dbc1577e531e8690a62497f40b87fa6e5e3b3f172e20070c37833fb4dc3c0103fcd8e0c5c65d9267f4d07d9855fbf51974a6fcaf2d4911845c100551afcf1464cb7722ef374b40d6ff44af19cbdecf6a3131e316ca18ef78a4d65b5e18fc9364fa2edb4093ed6132c2e3b9cd43617978757914ad4da7e6433dd33ff2b1cb84b29021f5e5909daaa5c28e3826760ba7905ed36ded55f4f14d3152ab859379a025019c35c1d8943e101e61fe30634c89ccef3733a9e189abe072a6bebc196a893c17a800148cd8ef53f382190abe7d5217b789903b5b529b8e32641375fe247578496f6cd780aad924248f71f838843e4e18c1a3d9819205eac73592044ae5ace285393d33b36e7fefbf2da532d159c2d47e7226a584853bc8744045edeb7100efcf70a113470ae48b1068f9056b6d90c36f56eb60fae0e7b6b3499f83639bfa0fd8206d00f34c1dbf46945734e55e374dca4ed56a4713ab1b16b06b6e7ee64f1b8ffcb494329a37b2f562940409753896282b63846126ab3a69938d19e62eebdad257feef948300ef765933aca0313cce200f300ce2ca42e2a339b0cdaa8aa3c43b8c94edc17b8e5844628752dd934bb26d1bcd7d64a2fa2a74e677a77ba331d8528700dc1a2d01436ca7d6aa422a7f9c9d106e9c7337a78847a9d58c9dc7d048c16b097861cb518a996dcbf6eec5bb9a5b931b229287ced614b1372cfadc0260aa4bbb90cf807ef4b69531bee45018c3ae976e48a89692dc09d31f6e685cd28cb4f7c1d24414a5f197a4f4f993ab0fd47d0dcbd9db21a032f5fb126e36c39cb43d4659b0ae271c14c18cdcf8929f6c30c645a91d49a49d7c7a2409b72c867375741284ef8bc3640db5fd21c58fcbef6e0ca3ee19d8259dfe24dec97993f54ae291c42f095f521e493cc9f124f392c493845f129fa43c497892f348e69584272118af21c55d04742076614c6881a1ce9af60ed34e515c9c3d0eff7b57c5362b5cd7be6b21a62e9c2f9b8c77ca0c373183a7b7628ab0ec747685e2829b524bb450c3bb8aab4e4063b610d2e4872e98df075f876d0e51fdb80e5dee508959cf8d99c55f8e62cd4af1e2e3872fb959f53fa8b21ec909f43ea7cfa192119637b16fd0918e58a078305e80ddf8dc62ff2043e7c4bce9131f9fcdcef2d19c7e47f69ca56a044d20bcbb47d5b3f1b7c2f97dc817a7a5cc7167803aeccfb25bac19ad3a00203a516ba966ac57330cc5886316b5e042318964c8536f67d9b286fc69e2494712c5b5ad4bda15cc63e6ed0ec393ca94164a1315977a1513e80113969238018fd767540bf540db74d45fab842baef30cf1e122d0853581ab80650607be695f0b54e6c7ecf7e349a29efbac1b4db3ad975722157a91c94fd75cc20f2008ba639d894ebb1b60bec7c3d9181e9982a11c9f03b99e9d1ccfd620ddd3259ced33a034e27d0f54b57a4c408f9501bbca420c7bfaf45104b7c94ef49a254b0ae196f369ea637af3738453539a7fe151a832748511e57f024fd79b01c0c810ae4f762ee3dde1bdc22d8b9b0c06c6a3a5108b10336ee9a40f281909bfee74b456e0cc116843a3285a4fd54bc91c4f8616ccdfa0fdfef9148f8ebe9d520be7be1b5ac4d0ff38af6c4e2b991631c95341b40824724c5cf980c0ccdbbb255dd82324f28df007a1959c0a6cfc629c8548ec1b58f472184bf0f38fe8b08c52f30dd70439ad43946e84baf56f8d2e93ab487f85ae425fd12967965ab03d186247433c8113b1e9dace4017d6cdeb23e2b07d6a1ec52b7ce5ba046c0c039ac6de8f5920165a4d073d4b8d73f98fd08ad5bdc4a8f2f67aff3c004389db73fe4f6dadda1fb1c5b5c56db83f6b8de6eaeb823e8280a0001e789614aa9faf158526e5efe9f3a05b5cd4f1f6a5e21a8d940b3c4cba91dd6dcbbda59429c95109a208e8083fbadaadeef2eee93f39a1e6d41256aa4020ccc9a91af627d25b0a00db0221da9e48a2e67b31765d60ff663745cdef9b5a725703e96ab7816cb7a35d9690283b7d632229750e7590876e2e4740588a293504ff309c60ffce3b5a8cbd504ac35d8ac961d308981a4216a930b0774dd87f4e77b50b741775d70401ceaa996a16632f61c097ffff6a552b7c0903c607aa66f2ae6ce0fc12068409c24e03e79730686097bdd022a52fb4171a6e5401a64c3e4411e9213abd90283b5d203220933209e6f4533b8166d044e22cb413785e1ab63043bed51424618be16c062dc04c002d41654603256c83e16c0640d8e6214e7650c32d60e864074d780a24b4253587e50ec2fdd99d8ac37a8a1f97f5e778250ace5e05f7cf685084690e83b3192ce1fe48c5e3122d5ec17d4aa2e6989d466b9f0652c2ed34b8b90001638abb20ce3efc43ae49cd7589c3e025dabedfa35171576bc008fef6b186849a230d07a11db0028c1e60085db9fd210c538c750ae0abbf89a62770143e16e71e02b222480b21dcdd05ee219863829a7b6808f2404bb4fd6c24de410e934ec55fb0835ac861505ea7c928ea6c8b0e9a6e59d44c71c7597cf5e7068ab31893b348835a7076da18187e8e3449a34a78b0956787d1ee038fb5289d20a796b9fd6027beb77137a8ab98fcfc155ff1db427751cc1da166388bb1eff83e628cc679617cb98ccb41cd70a6e331269fc22e958de0a576af0b6f3e6c4d7baa52bd66757c570e87c1590c9af00c0c3f8708788c1ef9f8f8082183cfc361702603244f721013c835c9600b9c61958e0e1f7e8538183e7c9c76a131fcafd52ef8432bb09560d47f0208f7cd3b18661786ee440444c0a638848e8f8fcf10337076e9e868a1a4c30998f8fce00b2a3e579220129a206403a4114031c20c64d00640f4800b0f18c9a0e8064180ad8e420a2a8c70c413b526239064b00614f0a62e92d0c12b5882b79f50868070f771524a2921ed5efef4bcee8b373ec771fdd1a3a89ecdeb2c111e503478a820f3370840594113540d16023778bb39b8cbfb12696c81c6f1f68fbbe27b9c2bb96bc6126af6a52b0ecbf44fefef4c0ef3f725c8e3bdbff700f26c4f44b5fde9a9f56c638aa26153715121942121dcfd6621de9e4803d93ee22e6b81c6f47690aa65f0bd197db694787b89b7eb5486a8190673393000f2441b362094f2392b24f6307b9838c35ac4f14aba45edf7a6d31143280179a210e867c9945b5347083b9409b7cedcb88fc6616068664e97e3d4b9e0968190a75f2857ca068e9c499ba4b8dfab1f7cbc13741a9237af74de25e3e5fed76ddb5e06aa47a650339c28fd6d4b6d8d7f9b73a637548f54cd09651286320a7994440385a2150d6ba6cec4185da15063464d960f63e16c068f8a82dce52724906018e4479c9080488e703f37b4dda0393453309540ef143495cc14e09e47b3889e824e48a6127ae790176d5c5af5a669d7e17c53e84bb9c0ace8772e30bf8271f9d509e6746bd74f6da0e657fad17163d9e15e79c32853dde3afbb14e6606a3dd7171481e0098774987497f4cf5d30d7ede1afa67db9b8593d760a82a7a0159d41f1e51b13ee334867d61914633488b330be686ce17ecf718339bb5998ad89da83c499fabc5e0adf465433cc8461217a4bc8c29692d753c15768e931ba15d52c31ccf1de5238c2db9830c21340525a106078e154cd2a74120568ce0fe055ec281ef4995c3dc1836f55532a84d2a4099426533084d2a409ce144a1326184269a2046709a50909b00b86509a00e1f67b81f628e7c6d1eee47da8aaaa5378609c9650eeee22e389225a93f122e3e58924ef8a179817980d0b6a06130313338f90428a991133635b82473483c60c1a4f38e1092ab443018d158d952c424a02fb7f484790365057dfe9743aad6490a6d07e31c2cc8742a150332c981db06858342a17b456f09e10cc0b7ee8c4708684e5cb8206051a145e00e409c92778e52e17171717146a4cf1a278797979a95153857e4631313131354f83060d1a6fdb8922aecdb579a17509361f8bc562d9b47ea84442eba67563248570f3b517385f4d4d4d0d8e0d6bad7541264ca4aa068d4a7add78dda0f229253d51e509bd81e306cac3b1115579c10c288e1c38706c44397272e4e0b4d41c1d393a9e78c21531c4161d3b74e89043b5b64367c70e2657743e1c3870e0d0d141aa4253f872727272524801a988a298b4d7c40a3c5658a12bda544002194978b0c0834787e10c06452cc05860e173f2d52728521618a8406608b0180ce5c5beb62e614e0c7e60f0041830c1a07685531fc4766231a426387f70869424319c31c9f23059ecb4b0b3f38410ce1f9cc1e00358360c674c8824152d7c4d445bc7b006528ae80b5b6801d533610ecc7941152e063ba7253a31482206486850e18cc90fc570f68231204f7cff0979e08ae78bc562311e9e194ab4d0420b2db8e0820ba81e175c70a1478f17604139c1833288e1f90b68300108c450c0c02b546358032947f0ca5d282212439803733c3e8696486fc10e73fc67545354383bd2223bc80331e459b9cb95b0c4125b9229bf907309a429bf9053ce0d69caed8b268230ad4c2b56baad4c2bd30a5785b6c3c494534e39e544e2e8462db7d907c06e243090c0400203098cee258220c4184dc8b9cdcd8a958dc96422adcc299b989209c77151ac489284a3d40bce0b8ef3624e8934651222228e526e09607cd1849c4b204df9859c4b204df9c5b6c9b92599f20b3997409af20b39e5dc90a6dcbe682208d3cab462a5dbcab432ad58e194e0bc50c2944b204d99644e893465128e5a41bd18e2bc489224c99c327645899219d6216b2dcdd46074eca3d19aa90a2d8ad7eedfddddd1ed1017ee4ff648d972f6171e6c65193e4b6afe68ed514a39e7dcb68de3388f7627ef43a554d545c6cb0c08728066be1c3a70a05c64309cf62110e4b18e1bb7bb6bd4bc6adef5d686bd50e01c01cd240c63aecd4d53191a726e08724391c927861ba2b4d6d5ea1fbb300cea87879bc987c946470b728173334148fb6830c773820d8e12574808e7e3a23cd1ceeb25ee6a544fd74545cda48f3aaf99dc8574a2d1a4936cd870bdd00c1d10c6a60bee6a8cb004ecdf5bb0df8f8683bf9abbba29af1b20a8010454d349b98103051c396872b0728274e800021a1212d2a16387cf0f5a76ecd059a1a093820a33545881e64cd0569850bc1cf1a0c311875d6a426001a6840ca1a08fb6246475ecd869c14527851678aa0a2aa07aba157854b861f4e1d386750fff6b98f71fed851baac1719f4edfef9d1a0be554c083051774f4685a5bd15360ac6b4d851b0a36c7f7b02ef0d8165ca6c2ef581d1fb3391e6659e061570022ca54b83f4629dcebe3aed6b9f7c75dfd3bac8e4b84e3736c8e4b04f4321cf702c557ff0dfbffb235efb235de8645e171ecff8dadf996adf1361685bff6dfda9a57d91aef1685aff6af5daab9f64a8d6b9950b8168bbbfa696cf72c4b7fc6762f63e97bb6bbb6895eab25befa61ec09afecd6851051b2601a969302a403243cc3461208a154041c63b70e3869fa0186a820708661aeadc517abe3ae7e1916e21d174bc4bfb6b4401cabbe9bb2f95b3a8df8fbef73c5a9ef330c4e7d1cfefefb88fc80e367239f8518658970383e0f87c5efd77158eafb5596486fc1a9f752f6433d447d288f0fbff830beadf5dba518a3df6f9962acfb7edb1463a7efb75a62ccfbfe7b8170fb60883d3b73b2334b33d4ced0946a9ea1e11a2d35b59a255bc3145ffdd20a2935bf8c41cd3535dc4ff002e71a26dc35b51d9c9156063a950169049501193f0304792e46ed879c07655042f9c9126778fae079d18b9107772d3da4b49ea488400ecb435678fbf8f3e38f2fb9fb92fb120f9bf5fa73540fb4b2e9871fdcf5392dfee0b04d29be86ac707c1ea2fd41881a7d70d7fc787fa8527fba7c5e6df576152aee9aef42049aba99a6fe74f36a7695eff9ad14634edb5850732bcdb8418df3b9cfbed44c30677e17c5582bdd5c4fb32ae7c79b610dc7eff9be7485c961dfcff726cf82faf9d1c743d96e3b59ba7557ba8bca3723ed9aea4f8fd363f491b2f93f386c42a9b9e21967a71f5c99d9804a0d6a18caf0602b6f4138be078dd87a0967b8043f77f2a5d43c99a61098204f7bb109c18518386611068edddd904b650c602235b8f3a187ed4e292494c49012158fd15852520325262649b329454c4d58b26c4133044660f8d0a3745284f3ca7301c21d7e7b682ceafcfe3091de82b70a73e24f0bd356461014a52985075bb92553abd154a3c96113cf9efce0171cbf66a86689c7564b35c6a8228b4c128e9f6b8e04e1f82409ce3542b32750e05ca3a4c607c797ed32490ecb32359999c332956922f34406ef8a1a4d1ef7c9d4fcfb11711827a16e9a41324b45a8fe364390a73ffecc523fdc6a30c5d7aa07ab2598131fe668fb2c53ab1104736a9b95a9c557acd154230c18eb3e7e8da61a2370ac1102c71a62e0f8324931d6dd1a74b5e2aa4cd27665925427211cdf260538c61aaa211cfbda0c41a9b946138e0de304cc893f656a383e6b4e999a4c1599a519a019241e9b3e3257a08c51a349268b8c96199f9923324c38fe945da3e9488bc3328c1335b2d418a3adc31ce1f8304e401efff8b4e513fd5bd284458d0fa76c06c19cf810358339d1ddaa604efca2b92409c79f470e9b8161cc645284e3c3274a382e61818fa8c082f3ac3293da851c96eb90c3b22a0987c12b709c82f35482a354411e6f8a23f07a38bd072dc4f20baefff49e9f7e7e3afa3ff18388cf6d97059ffb18939705e90be92e0bfef65448bcac1bb6a7cf8257c8f69425643ef7f33923b2938cb350e6c54f02a1a20ad032eea175ec350bbe0f8d59df0b89df3dea1b53cb821f8491f741741f3f0823d415229f7e10f1bbe72c0b5ed6776fe83ebe90eee365c187f17512425f7e10f2b2e05db9ebf41f5fa78732faa7262acb7beea18ce55dd60ddd738f43f7dc0dc248487cfa41c8ef1e07fadc733788f8f4a10c72efc34f4f638cfee961eb7477d081e2b590161ee26b89b6135fae6e8bdadf1968f7a495ceaa4eeeda96bb9074db1da283398ee32007e4fd6fd6f5ad8f313aef67ca715f4f8039cc3d410112981342c67dcb87e8fccf8e0a0d38c1e2a48a9c5b781d853934e66ec433d0de533ca7542986f8a235af3094c950e342ddd5839bbf7d0c3334c1fe3f380cc6229c358109f687572a76269f2938a8095830b7cd159482fd63804af1f2684b24ef67899707b53fca265a47c4142d1993331f1f1f25b2d5ca5b1842188e9284f796b4183cd8ca3dc30ddbbfbf9b386c761f41d69c108840bf0e0e53e1243cd64866fd43a6a882db077723c1dd700cdc3f9b40888dcc30f5923e8a4f64b2a8faadd7afe899c79a4781c63528c68f800e9d94bbc9caf634f6a2a83a409e19d89f07c813b14331b0b710d8bf8d80616067f51bc1b307a8b4ea26eeb2102b61a8838c7e458535c8bfd802b66860438791534e39a57c193b507f7097d7da27527afb879ae06b39c2fe9e2f5525ecef4b3ea76b45335dd86fdeb6f93107761adc71db7ce352d55d9ea230c74f4a38504f45a7215ae1e7d349e8742499b2cc21a812140fa812252e0bad055dff3a3a3a5186d011117f1084da290b0f047086bf5de2f72c3e9c6118239c617973c5dd10ce8e2a0c6752809c88608ad804112abdd0b7a0b48da03a71c7e5c809121fe04c6bc0832850e05cb113245974904c8f33334015d230c4b0887ae6b8b96ddf403eeef1e39cef538725ec0dd4479c7490127329e698f07d78d7755bd7c9e7b66ed2c96d734a1b7210e23006ecc458bfbf92c72578b095eb128e14b60ba776e184e17b5154c83303f2481cbfadc0b1b5c0b1bbc031b7131ca5caefa455041c463f1b19c201dab851f41dd5d303c77172081f2a9c84f3939de4df490e6b720547158edf4c0ecbe2b12662a89f380cce9c20c95dc3b16748389a620bce8d05c756c2f1616e2a387acfe8ed29ee4904ac0e7688bbe2fc955d155579eb1575c9e396c3a9531a650a9c57998621bb63cb8edd1da3c718390c67529ce05c91209131b80113a648185b9c11efc3dd9de3e836a9a49c84f05f2787e7c09e632685c899107fe035ed0395fbad0789336ce2c1870c5a5515f5ad4eb4af0e5f136aeea3a10c9b329c454a7be8e5fd797018cc771e53bd8c77f969612c0fee7ab93ec031b07fed1fec8fbab97eb78fac705e7517fa0bdedc48f04b2354cea354f899cad834e73ad985e7efb94501f1188786640f9039513ddb1614b4a1807038bea78a2da859caa39ea5250c63500cf299cf9a9a5a50fb21c5447a0b9d1b4753d988430c513fe4bdac2019cb2398137b2b02cc5c419c4337cf236073053f18fe1402c2e610cc02be3cea19b8bbbbdb513d31fee88931ce1903714277c518856494d1354004638470b5c2102a718ee3525b445102e74fc9a0cac07016c503a91344f1d1f22d2b80707e1d0ee99dc6177cca4122961097f9bbcc2f1041c8607c45153ca4b5c2ac68e28fcc0a9f09a12042e948159ef7b09577f858287b77350f77f57f370fc15f2af59fdd61080cb6f4037507d5dbaf436081b22dcfbff79bf3902158f5a9ffbe513d2af8dd9cfa1a5376080cfede3e44fdb0ff7db59efd141038f59f55f9f00af167af0a15f51f0edbb37080b2530df5ee8c81ebdb2ab8bebd020b8618622feeecb8cb7ef6c1717d1d77ed34d5fa5907088f1ff5ee60f8ddd3f75a852312b5b1c39c2a83ca02e13d7dbf799b9b9b8de09bcf14e33cce6d54cfcdf57757fb701c190b71bd35b51b2c8ee0fa37efc56f7ed536ee2662806a54b666c95daaafaaefdfbe46d5bb29a1962584fecccf5c206282de88aca6067968300ed6f7d734c5d88cdbd42cb9abafcc50f1a933de45657d9ea1f9db7cbdb9f9fec7310644cb5897a57a28633dcbd629f59dfc9800f390d9526f5ef53bdee3503d3b5c503318a101b6c3369e2819eadf5c960cf56ffee6ee00bbf521f6fcbb3935dddcbc4f8cfdc420189c4d116a19dc5fa325c65a168339986131e5e6d474cae230880506328425311603344343e570fa996f140ef2676e4c50c76e2853d901380608490d82fee95da5c5c64bd40fc72a2d4c35d63efd2abb0d456a71e882f0befb3aa4c46110f7a6c45db5a7de9a1a924ae3ae95959ab7211f7143f5d4a716ca8844acfa4d89c3eaeddf861c86f3fddecd43544f4d151ccbaadfb8be8c65d53b4393cd50f157bfaca2d6dcdc9a9abbfaa1549beb9f6b6a12e6d46b3305d7b716ca1a657373eb55f7ed1548ae3862e1001f88edf43e341fc4e9e1c387281b683a7b4239ea06f85fad8b08051aabde4565218eede43ca5b5e6bccae6a1c36c6ab5b1b9f9c3f5a9cdab6c6e36826f8ba9c5e4309bef6f61f117805ed63da402f7bbbe15f4fa9a5a8cd1effb3ed40d5356538b2fda6a358b6255d9d8a85436360f512ad5b5799b21545bb66cd922937988c2c1653217880cc8a88d25eaaef8c1f5ed7bed53b2e0faa8f7e407a938d1666833588e666833545e70ff4cd38c967e960feba79f7564a64affccd291c3724d2dc961792a3169e2303885861820dcd916537c35a4a2498067d12c9a453784a1d2129b9a6c7c3edd9a5a11aae33c43bbf1353e9047f5313f43833c15a6208c9d8ea615386f43b8ff741463275a7cf5d3239b7b849bba3e9f9a5a4130e7f559a5a5a656b30473be6f9cfbfdcdb5b99b946af399465d05c557ffea6768335a409efafd3341d1e6661a3837d79b209b4c8316633347797583f399de8899398a3837d39b9babcdcc116ed5adf181393d438352677c3e35e1a6f159a505f79f68f729adf57e5ee17ca2d91895954982398d638a982ef55936aa199a857117cbe6fe706c53bfa6d65f53658696f7e97fa03fd2ff35fd354cd146556768403587e598223095fe992a2d93047968e07e95a581638020cf87fbabfd70cc1187f9f7cf00419ec6fd3145401e25c8e377080cb6ff35d46b518fb210dbb0995edb43d3b0d9ce8801c2fd2bdb83c7d86c6fd82c9ff86cee5e36bf10b8df6573eb08dc9fcab13df48ecd3666b37c8287cd15d358205a363fa21400ebddb150e6a55e8695c143fcd4bffcf7281f1ccbf89777d48ff832ded660be3f651dc3fcffb09e50fb737a223306cd7763382cdb260ecb59b4d0f8b092baa75a1c96893cebe3b04c74fa54d2cc7fd736d999c33291cca7aed552532bc0f7d754b9e1fb6b966cf8fe9a2b357c7f0dd38defafc1f2fafe9a26d7f7d764b1f1fd0fe4b09cc2fff8fe3fe2b0acf33ddfff410ecb30fc05721712876501fc3de2ae16224012340ccda04486a2ba0475f431497dff2b392ce3977dff4f71580ec0fbf8fea7bdf0fd4f0500dfffb5017cff5709f2fdbf14c3f7ff1520dfff4cf7fb1f8bfdfe6f527dff677120876517bec71187e5169e27680749ecfbad90c372ccf7db241c96677cbf1d7258a6f1fd5689c3f2eafb6d91c3f23fcef7db250ecb357ff3fdf6c861b9c6b7bedf32715846e16dbedf2a392cb3f0b0efb7531c9657781edf6f690ecb3a7ec7f75b2a0ecb393e07082953e1fbb1075130371bc130efb2371af09fad0b7895bd09f896c5b1453804fccb3eebf50260fdb845a8ab00df0d377facfbbaf98677b17eb004d0ba0c50b1de46035e34f7c728e37ccf2d42f37d1860b0e166570d3660b845eaff18b96eeee16eb8b98687e116a13f6e8681b27ef4bc8be572dd7ce36db05c36fec7a8869b8958ff8306869b0540038300fec7a8e766d7db70e3661b5fc3bf184073294d4b55ff06cdf7af5be4a3b9f122a2f91603540cf8d1f33f46f5e61b5f80efe6d7dff0288ba200c8ad97dd2294deff312200a5e166d47fb47e3894007f290068b85f1580bab9fe473fb50054777f8cb2ec7ddc2234e3fc0b2f7c8f922195f2f12fdc22b5fb1fa3ef66d9a76e11ee4bcdf02fdc22940037bf40a90cb2ffe8f7d12ff53f4633dc4c445f86ee05fa00b845ea0b00f87e86d4cbf02805a87cdc2c7b1a3a1cdaf968ddaedeac7a545751ff6324bb99a87b1f0ac0e9bea500998fff31ba37d72780ea66d4d3f0d6dad3fd31ca06c8b1ef718bd013ceff180d20c8cdf6aa7a9c62370b799c532c08cedf1ceccdf755a7f70458effe18e5013ccf2d42330fed11c3cdaab740dc7bdcdce37778fec748753310ce00bf738b502137efd0530c405e7552a9fca4f2ff3132c0cd44a78fc1db397dccdb89fd8f11909b551f833f90b7390ce083783da817c4bb37b7de7ad7fe8fd1006e26f23e480e3dbce7c9a107cfff18e1dc7c7f0037db0ff2373864726e114a0d00bb69c9e0b40e00bb45aa0c0e06c0b945387c33ceb764dec616e13011026e66ee8f51169279fc8e5b84c276dcac6c6c603c6e86fd8e5ba4cef068dd4c833bc0af64721e875b84caac687c4ba6d5b29169d9fc8fd1016e2692f91d333932bf9ad991f33f46346e6e3d0d9b5ffdcd2dc261221c3384504c3403a3330688f919333837cff89b5be4c377033f463837ff6324e466a29937000ed8cd3c6e918a37f06304e3f13f463137e37ccc8c9b6f7ec6fb8805b8c1861a0840c30c320c20480c407ed814bec7ea3c0c56c70bc0e67899c53e6c005aa6c2bf60753c000229a425237c319c4981648822a9c2fd5504788563cc69fd2b9a0f0700c0b99573641e3d70c438c3293638432ab84a0ccef00ac49202ce300b8e708cf87d04c7d849a4706e255d0ce1f8323eed4209387e5de940081cff754005c777ede0081c5f67075770fc1d1f4c70fc18aa5481e3db80031c3f0708380cceaa28c14a07707c1e1c066756b2c0f17ff001fac1f19b07351c675e3881239c2d155184ea69312a142ca3c69108b418c1111b50a18407448831ae562b0f9a5281244f5871628a069430c2ca96262e1e93320ab13f85311f4e61a4f20da1814fcaba8a4539a858823c5d0473b8e79ecb3b3116f3f05f3e3f66cd372293f1f3daf053dfe58dc8541fc3c5bc7f4c6c21185be38bfb17ebd68be28beba6f8e23e29beb89fd6dfc5cab0de54adfcc2c8470665469c0df1c5a9fc55fe9c8ff8e274e28b53f905e29ff387b2caa920105fdccb1fd4acb23c58ee3721d8c412a27a7fd5eda3f8e27e165fdcbb2ce1b897b129c6da87934098fb6e963e988b5a30f7bec4bd337993030915cd68b1c67d5ce23e32c5189471be145fdc737fbab98730b78a947b2887a8a3a4f0604b892abb5b87f86affed8dc8b60bc4e9e703d1e50aff643b4b2dcb597e5937d09f2f84fefc2e3e5dc5577c1454eebac8a06cbb14cabc5e411e1db61d5479730be17e186544f23ce9ee34ab6dca888567595fd061e1eebea2107687456d08698a1504fbcd0bc01d0a3ce8b0ecd8a3cdb93be536773965f794127b52ca0d42987c77e9eeee6e1bbb7b6329a5a41ca518c258fceceeeefeb09d6659e275453a4abb221cdda64a4a9751a5aa31d6c1977ce83201ecdf12250387a32c1284e2f8dda98db87cc28fa078a2ca5802095585e1ac368227d505c3596d03a929b5c370c6c518292db8388214556238e3a20854b808820401addfb4abd48bd4575390c64512ec8fa29d931f13a83e548fb45409394802fb7b1546151060c0049328b25851852c56c1f1c4e3ea744930cc3034d897e004fbd32d92724004f6ef7e85859415f8f4608a23407022dbc1961f789122b1796048d6afb3136330cacc8b201f59bf8fa801597f0c5490b0d700fb53548faf7a157f15a3d052735e978e6c2a1c7061ffad7f80831de0f8b1d6f8bee4e3e34384bbbbcf40fbc209134830010646c85c07d85d0676777797a81ecf1f4ca67982738b8a27e0f8323ea56d81e3d72670fc95165be0ec065be038c3010970ec2bd55593707c59abcbb5a40545243802100f8a64b18b21b070a1d1d6af66972b474724f08d0cd26438c1614105044a58f820045b68582481881e1f1f2c9a70640b5316451cd972145f106f5b5d75462e8c571245846a319c41514331018518a9a34a430448242d518120a90a29ea0ac35952edeb46f3f1f1d9c096d484b78dfb6ddb3e7f6bbbae229ef80b4ea1546a9515e68ab8f29c0b3fe4a2f8c1dc4b11c8b88769cc711cc731f90b36f90be21e24b4f80be25c535844e1ea42c86289e1ac6605ceafc3d1c083ad3cc307cff7a2a81c0920ec070c7049e0f99b1830d643f1353f7347e00939233821200f0dd714372d9087fe7c0ce44d0a3c7f4b82aef973abc5d7fc55dfbcba3dd4dbb5814a97c30d8ed26eb5b9f2ae0df1359fb337985ad50deeec4fcb523085f95faf309f3a0bf3b7da129e31b86b2ea950263f6f459d85b22d29c67ed88a624c072679ead38956a1d9212a469014b75a8d4b38733e272a46608413ed99c332645272586ea1260e8357d470fe3017386f53f09c34c84303cf0fcf6f25db90fcf93fc4980bbee653ee66ba5d2daed57c1d86fca4b4d6183df89adfd917f89a4f2d0be6ccdfacc59cb581a3eda1f1a9367fabe1ed5b89bfe6f790bb5a89bbe6b66d43d4f89b1d722a72d7eca12498337fabe1f91bd376a5fbad8abfd0d5b624c803ada4fe8aef6db5adf6db92c364f07c0ec861483c06a51c6d57b06c59f07c98391f3c7fd382e7c3cc1dd998f07cca6d5b92c3f2968495380ce68086789252e3673ac3474a95141e6d723a53874218af5dd0b6dcf5dec7984e7cf94b1dbbf33c1c063d3e7cd7c787afb3b3e32ecf3a3bd212d16ff953cbc35ffe2e5945ddee8abb5507be0fce14cff972630a02e7f91b9c367f79fef0e13efcc3f15219d4adc8f377316aa5cf59ca711e4a85e26c86c128cea25e6589c447ad50967e47d40a83214d13953ef73338ecdd15bfa1d415ecf4f161f0382698fbfadfab5e421e0f65b354d44377a1ee576f7679eaae5b6b90cfbd1791a81053ff5ac63b2ac87ceecae096503ddd4ba6ba7218ed1ac614f73e9cbb2cee2166fd68ccbd9fba9c136737954aa5ba99aa6c55a9208fd7c371af7a94dd817bd57bf5250a48455d960ca8a70f04755d32f71668aa823885fa2caa7baf83dd31c53d772da598c70f2baa949a29ae4da59022846d307412a5087b2ad8f2e1f4487d9cff094cbccc527798dfbffdbc7987f8fd7d77e032cd437cd83e75877cd8f3bc9bbbadc7d9ee1db07764d9c3dc3c88bbee3bb0fdb410f5b5d6ef3f067f978fb741883f033cd644e408c2fe3a3dfceb970ae214d2fce1effbec1646fdbe0ff5db43dc9685ba3f30c4196ea7d4b6f9882877d77d77411fee4cd57b291b2543eaa2fe6459a96f9c7aefe4acd4ad32594575dd7797b752edf73e3f0ddb0966e5aed8315593a85eae9d95bd63fe66b7de316744fda03f6df7b10335437ab99e1b9653764719d42654442541feb427e5c805e6486056e8819626922ce184c9165b4821eabcca90c0164d28c1114a58820b9c504942f439e18a129e0439a184832b70200548b58940ba5085184e504212b4f869e206528698ad04bbcc21e4f11a0576500c2c2c68011012ac18f2428b219c56ee729812c0b40cda8bc203d46aa2c05f9490a81e96ffdf149dbbd33aaf4c7ed3f830a9832881842474c164041216c8244d461428a8bb0f47d214cb30b24489147ca1812704a140053299451427e8082aed76c2a30e83f05db5c298bfbc2dbfa55b77ef8007614cc6f78a5d857770b8d37103ddcd85471d5621840e1b4a8c1dccf6f29eb4a8f1b3ecbaaebbd45d316e360fe981bb9731765d8cb1fb2ca5fcd87537d31be526e14b0969e04158e3fc38637cc1e6807c08e14738699c4f638c787e0c9a47a844ebd7ca9a20778a211208001000007315003028180e09066391204ce338f10314800f80a050624e1588a35114a3208a3186004388210a184088314499211ada005b780be41022418fb4b7ba488332ad5329b004c070512eb92c3ff55b1e07e1ecbc8a92505d822be8405c80e6964f85e0e9bbedf78c3978113584dcfb1b651a73905d916d3bf3e176349a02f5dbbfc1e9001f0cd8e57a11e65b8e0d6da8e397af58b4930155448fbbc44d4a074306a89b5f0e0da8e513b3461a2edb71f54d617723c345bf9ccc48c61c726109f7b19749bf5ce817115112addf500b8f00022628c5c0a160a230235c21ec853e3671ea068953c43255fbbe5f7a0d378156c6487ee9e46241967cb95b85f9596675482fa9891d862ae5a6ccccc462bc8e713e3f5c270ffabcc89c2f7d3198d7015d0ef4f361ddae7bdda3e46b8b8a9881174d8bd7923ede7bd5d85eb3689e03e124e12719f816c24e0bb309352dc5b0be711bae95b0914289db1e17c9359eead6692b57c5d05d017793f63be75d41498c0e0452c1ca8490fb0498046f9a0f8dcc2426ba92b89817c5bc51b72a50ff06ee1c6d6d315e3891f9cf7e6ecae1621743b7c5df78fd98ace2c26ba345685db52a8645b885be48848c5f5a3a90858218c3d13d53c453318de9223e6e3a3355645370d128eb0859c88305267358727f5ec0992f8241f135a40c513cbbf2033c6e72443890e9194e98e08e7ba98055d529c3829799d05c2050d9060695911bba4c8647a88930f9c2c164881e4b8c75e105fc0fe3260d3908c32e5eb133bc53f7f16539ad191e8374ada51973728d28059485cabcabf06e51d45ec92f488e0688379afdc0871cf3101c9ed89328655b51c11bcc3c16bc550cf72dc092c7e073c11b52e3eb06cce00d0770cd9a5b576cc36e34c4dc57ee16a2e751abf0c8075c9de3d5f869363744cbc58599fb663efe1f2cc82ad093474876419d73824ff288638ec57567f3a0ac27b0c0ed743a24f07df288715adf25ba955d3138e645dd4ab66d8042da58a267c66e29b76a1e6862de526319910fcb8421b5ca0a08f35e6344e0c9e13232aa4559d53c8cd225e0f347a77059c3ebf780970e0af080c7b2594a707738eba4e09ee1b62539d295a207a9463fa07fde00d6c325814877a8d8ef39aeb76b1c0b6a27d830ca24697bffa5cd70a2115feffb541518682a121fa2b249a2bbd90bdba1fbf0c16fcb29aa7b3b24f2f011393a777d1ac7f6d80eb1fe3aa2919ee8bc8ac1ede6072f9c1b200abf50523f0e93871e957a8e66f7eb8eb5b01628c68e0c1f3f9f2442e8507edf8f75156eb3f4e98af8e33e3383980b84c489fec055475f041e65124d205a93099a89e8606df195074733b6315bde2f8805f4b73b4db3d975ed612ba524358c3fb6849f90f1be54e0b66652d30857f09940fc0346ee3cbce22962d9063496ae2e088042a547a40d47279509a02e1e53009c727b12eb4061bc304247817cfef9968819d8f7f780818b112121bda7893ee3740cf138a6c2695acd1011f142c857e5e48d6e583f580363e72bab2208c00c0fc0abb936d146e10f6941c7e669d8a304d3f40630ae3288f75ec3f423f7011b858fc91904e6e702ab8824d33067c88c55e84198922d164abb5ace4a03f4d35ed5190d485e7eadd6652b94f614d9aaeb97f2b64d3a2a07d4fc60f2fa8d1dad0c54ee8a9e05f2b9acb2490420597833d10dba8fb7de2d47cbc1f2e5ec1f2400857e2254e8573521ef05b7c40ff67880283045db55fad8236e06471be4da9956d77870d804fd85dfafc63afd1e19c4083e6dd017fe4759ea59806fd9c067aaa89801c26c3fd8a3c1c1f583325a4fe1e6af35f4b5bcf4bcfb061c07e4ff1b7e5e207b7d2dbb2656bc8a89cb603696dd034e08039f2808e5eccf1b62d1c9b36056b010e75b3e176e3d9a3d43fa5693552ecba2d5b56837810010a298a0c1c7d4c8f7b9b1f88dfecdca837de5b1c4f362523be0debfd416fb8329eca371e7dd550719afa824b6729181e7407ff21528c6108783e39ddec6f27e07bebcb741c2c15f906b7be026bb7bfc1be56f70f8a4ace07bb399b8d51b1c5843685bb1fa00b54622a41111c0373805ecb83b2691bb1c289f3909a75eb61edd639796715cd9185168c08845b4a84611b29818b280989f7568ed22ab3fc553a1b4439cfd1a84b5da38094a337ed9107bcd496db82cd3387f08b82fb50b08850489da8fe061e1d51073c980c8a0ac1ed627a1102efc9e5c2ac0a10e5a487da7149aa3e4e1b1b13900e9c0fab0ba577f4bfd1eea22dca69f97ef925899dfc988a278e47c832c7e5b0688dd006ff21568b159d3db0788c0960a3b4369e01b22e3eea413b7a94e969d73d4c2b88273e6992e8967714e42f308cf852ea83a73f32e7947cd84d5d5ed3db40fc9ce0290c2e1ffe865daf2d1a4b7826c19f743d5960d83c55ac321003cf420dc312cd6df99ad7c9b72a055a94937c89971581ff4716094c5b2b064b587a305fe551e9573d9c07fe754b5a07e0a8fb2ca441c2578c718ebc344eff589d6b2046f13261966cb77e1976576c3ec8af8394c34fbc97e6e84a1bc261ee92482fa059dddadfef284c25bd637e800e39ec351afdcbbdfc062d7520119b43466267df3779319f7976ecb730aed01aa04cb4406f492fd138c5095a0276ef9a06212516f3d7a6e827755017159a125c757d8fae1b7195ce8999c89d41bac18df822fdca6fb1130d963edafe8ace3e802e838ab4aaeaf366fd46a602826ca0a8543b2eaeafa10ba291d70b7f9c2fecafdbdff70ed6733fe8a9f9e3105b293593ee77871248871f397d8bcb2d1cde16cb18135c66f95dcae3122b654d43ee0aa36e3b5db41182fbfe908423305dff2164b4569bd942afeb6071c29993bf0d7cea626180deb155c6b9f70a6a1560cfc72e3a21fd4372fc2ab14946e011fee8dfca39bc6035adfadd604096427f16d1374ffe15c7a39cb9d4e8be7f5ddd97739b77d87846b5e832c22e2361d1b82a7ec878a47afaa3de964481fb76d57e18696b72c52405523dbfab50600d9a67677022daad3875e17f68120f4951ff4d1607b8a0386e512ed0c7c44239a12499cc9a997122f3ebbea3ec6d67ac5550f8a8399b57e3c5314cf696b8bf25daf9444694218a51d707075ac56d4f8306ad0d70150ef93668a956ce2002a75014fa5d30a318322efc803f01c26005b9e4dde339f30ccbab09f29e9ecc0a521096d5dffc645a3200ad85404ea3d919f0b65af8efb45ac225ae9e8933e566d8bd10cd2f1fef4f642984dcfa63524276125708334569b4e7352d74beb07e65c503c0107240487e59eba768ee73ceaa0dd12fb1a4a24bc98a460152ab8527780378162ca2d9f7fb882b10a01e3483ff85116653ed79726dfd524bf0478b45cf1c1dc926fe97a34676de2d3c366ff700d28758a251ed68cea30febd1cca44c4473788c4f80fc31bf2fe54ddbcd4a964a32ba5a1e59a271a0a65b956baf82996a48c50a90ece5accb5cd6611baa7f579ceb9663c3b8b6ad762128775372889cbe95801c748bdb3c1fc5367aea0546b612110edc0c3a2d595c8efeb1b4b480c125e554db7f8bffa1a72611810fe28c84f06dab703a0c51c9335ca92c567010047130fe260207c30884009b236e9f520180fd202ba0db9973059feae66659026619af8e2c0f7014876b37c3f92f642fbf7bb7ddb7872926fdbdfeb8f624cc133e82a86010a0c397c6818f8c0ce269cd33936ad4b77855691e88556bcf4a4b7f00abd5c48352faf0e92af0142840c7c444a1669d05759401c1a5852f276a58d77060929c7eef955e56fdee3e84fda3d51115bcf8bb604087d719b66a02c2bf1101dbbc7a7602d2aaaf8b6413b9e5d5c59f76c40f371140024f2570038541caf67349a3c3835b9a657b2f97639a67aeddc7e22cfa262f62f8a028e700deedc99009aebaf2568ce302aa47b5547d45e10d27eebd0dcb550c707f989063a88fc2a3a42b21c708329561f698d0967887a22471cc9caaad15117e5b38754cd71fad72c16a16d8b50481c375cb8ece8a570ec5732a3585e9e64e952fb7d65bbb2c25c2e3000b835d4815dc76eb4e00d5a490d32fbd45c7250cd7010eaf033fbd705057cd6ea2de701a38eb75e90fa20ec48a788507ab319672a8194e5b096a758981d1766382f3a0ac49cec8e26891daea550162b69d4d76d55f61be861c83691dba7ce8b50eafa13fc395b37aae5bb6b8597b417b1c3df776d0d8452f4262cbe56c6568d1c8e1e832a49a3dfb1f471f548d05545f3eeaefed8de0384e7e69dfd6f0d0fbc573d41d3e5cce54cad8e14192ce6c036ba7742e598dcbcbb30a0b61a4a9176fa32f1c810ab6e46666909547b688200445b7be8751020bda8050eb00bf6b2638d23ecf537c1878fc635b9a0985c91d842ce2b0bf715c1794a17a9ec30bb9a4baed9cf3568e9bc4a1d993a4515c1d7b3ac95bc8ca9a3846bcc1e5b6c0124e1a877e6cf9503caa3c5798ec70456145290384e7eda6d3005d0219093f3f31672543f8f388917e064e776f7a448f250d6113dd0c28fe0f4aec92a5cbeb1bd05cb4c544001f12c4d5d633d6ef3a63c6bb2963384372ae6a1380dd3cd911e5af90fb42fb32f2d66b42e47dcc761eb236258e23483b2c7e90e9cbbd813c23926123c5965d1871d88dc42019844b321e3051e6ac17896cfd791c9c91081e6881117db27db82426e28552df53624af9beda800d527935612c7b25fa9f54626c950c3c3fff54ee7ac1a06c4c9dd90a69c9ef2c9902d32e80aff4494303b29f01e127b06084480412c1d2e800a5777d343aa0114f48140e902b7da13ca2cd0f11699aaa0a4338b3a9e6a2aa16b8ec6e400132a6c3c149dd505e5cef0250d354e6cdb21e66ae286d17a40d47fb962061c78422671b0703fb01212894802d499cee857aa1c2f9143039f2d7de913cc47a415df2ea8e57f2640a6cb503c38def9cbeab4ed9b7c43a900bc3f32c4ef8e88cc18d72a08df21bc4370aca42c342ad3e30321ac2ffb6985d083cc101867598093ad0471918d03f979362bac3dfc22422030704e5158dc8088cd47f826fd70f8adea846bb386b80346d62086ec1a61221f44094ae2436d7d5ebc6813f0d805122f8bb233599a6a0d8b5a750d90f39ba3c4dcf097e1e2500298025f6007c1572222e3906464f410be4b68e55b958b0f76f3c40a99ed62cb4a849f85b1b3cfc661cebd2b03bb6cc914727145143525e83b6f8661ae17ad469db7a2c05ca2a665986f5d88501f2cd0edba2332d3d756cc8861aea62d72e255581af74b121f571772e7906087dcfe070fee069d417f4c9f8ba14b657f448453721b5306841d8e93eceac9c9d0c578f11729f2e70ad01387a6719c95723b1ff89f3a1dc048da4c785a906ca491d1ca970532937f5d0ee0f171fd14e51793db0ce096cb2549f58aa4bffcfccf7c99a5beaa29ebb9c8e933dff470a514b720f51dc72a4a533884cb5165575e9a853906b7fb7f81bc8f5d25bdae73590f44f453e746469bb6d20c9d2bb8f7b79c74e6493f102a4930e73625a77198f53e9929cb8c93f587aeccfcd313220e2e6ef62b3e344b29f56b47e7fb89a5b46dbe8213c20f16e1a5a6c4b80e4d1fc622631169fd0ad1cbb4eac441e81d62ed27baa431c4b61d93bc08e87054bf46f5615b6a72713d2a8545d6828f6b5caa5b45d9d826f6c8361e9f6e93be759b2bf23846a96ef9fc792c2f632b7963c2c1cc8f6a7aa359a66ebf186e4962ea169f625415336581e766e1b56d18a5c440b86b66369c6756ae5198ad97b63293d1af486d0b9c87124a80d646cae3472c99bbb6e24a5f84f87a49f979d6b43a97df7115c191a0660a137ee70e4d79ea0a6c58432947f42592b2cdc3d59071b4d3f32a38c2d03f765e2a6d635a749d4317fae3374ccc91c1b5ddbb51862ccde262b75ef27463ee36ac081cc37ee664d185da70ac597a47ac45290e187388fedc0decec8c17595135d769b9d091ba457fd8e5edefa0714733deb209cafa7711e8efad7ea480b72f3411b45b1184d76515da52d20a2bdbfa52b97c0dad8fad03fe8e36dd93bfc6ca62ce354275637482550c85b916d78c1d5e97d690d2dbe671d85e790e1de30bcf608eba62615920874a179207a1bf0713ade2154a8eef9dc75e3f735b7b1c7b9f39512068ecb5451306230db4939935385282307f1b29b5a01467d733881487f39f978d0d84d208440f158ce3a47e52c805b34f12cece122717e5ca3f7925fd62cd6d9b454435a6c67da91caf88a0846118e182038ee879801668b21a74cd9116a1bdb73e7f2310b88d5ca1571f7c6fcbbdbc91724f8d70f0c2c2ead649c3c7f5faedda358cf8edb6c05a0be98419d19ac65f3bace7af57b4974b7edf4f7b21ac15394572571733f73e4c8564c98c18aae3443b9389bc9db399585ed8e08eb60d0bbffb45efb548efb0268db2a8057c063a4c498d71a2b7b8ec3bd5a433b84e4fdfb4b25e38e1de1a9bd705192cd94dc966f50964d2b74d1c0eacff550a43581c0dd3a261ab9ee73f4c9b51863ad53fe0d8a88434b2cbc0d91735d6c24cff2b12599a5f19fb8531ffeb5cb1ad38ff783ecfebe388098841c5f6afee7c1c07fe13fe3d3479c58eea0bca668469452fbb86623380c137ef62ffdd696b06dc44a844d806c1d09da090260be1aeff1fa3f5c555e242948b274551b0fa3619cd588615b0238ac77a6f6c05328b067043ac30dd5d0a9ce3cd150f535b9c5599c7b0a43b83fbbdf2610b4fc9bfd6ba0d3a2d310e6e9ce056603db95561670ec639361e94cb0a26992ef803bd07ecbb5d77e6e664aadbfe18c2498e53eb8589f3015decb51dcdf51ebce704245dfe6f21bc2b06bc608d5640d707282246d975b307b64b6ae80eac2262a0a4a2c60c57a62b56b30794a8f19fafd385ab5c00953b61bae2bd0473f94919dbde731d03adc95476a8a766a1935136c6084e4559ebe3a2d288dc99e8629c4c7a510fb4f630d0d56266808891718d1fa43d718fda7ed8da0010c3c1e500b1d4a9343ebc0b1656d7873b8a40aa7cd562a90b57dda623dbf31a9cee71f0fbf1a31a81b2fdbc86eb1c0699802c37b73f08f18435f75c9231324bad39b52e9672064828c626644dca71f0cf56a8067675731291debae6adc623f928a2c5cd978f29dc9aeacaf3cc7d49f760a3aca00ee20a25e993b8bc7bd166a4a09bafca84ea8562e717120e4e54c6ecb9960761ed5a008d56865b82284d4fc2aebb9ffe169d57f4905cad938b647df1111707924c42658655e8d14577e28de4df21a2400f025a273623848da4ffb287b8411dc596f04eddb5051936e88c7128bcfee9e7913b0a0a55bbadb8b480ca6638471c1190d44a545b07730b61f6936597d10a76d4a8a50573c2862103e7b2179a7f95d5ea560b6d3cc276bbbfca02d26bf5d1558e444910a98f47b7802f2d1606af1d623a600b8e139c9b09e664b50a5eadcdf4d1974e04ce5777b51423d8773d3aa93099fc46d9142f56b30a262f0db99dacd770c6469c892efc177ae094885db975ad7255b77128765428c294a0d74b1e4269076ce68fdbf0d9c5217f21ccdf98f7eeab798549ac3e5d88c10c61f1efb1f5fbdb7597d4053ac20a931d88137494229573342123e1d09c3e7aaf9f4de60e96748aa84ff679f9ed58eefd2bc608f681a662fb7f687bdb6d27abadaa94b3fced00f6b1845feaa631f31b6088aa18445b2fdbdf007078ca4f2ce8e9d68f88e34c7d20d2056b1507a40639ef34c02b09cbaeda189aad71fe319ca33c16f3c7a4990df80c35c3693816c698720bd923a5a0f94c80f208a2dfbd9782c6a743fff00b047122ef1419d69e35cd30120196ab9d687ddf1954856f588dd091ddf9eb65f5df4fc432c0738d84ef37c56d509de3d369d6016549c10c993e89eb05f156e7f702484ed273c86d81f86ed175f7c73c680fb613e440ff69f782b0df235a623cc87d43fde1b1185da7de4c70783d53655a8a5e6497c1ce334d42974a0522cbb20ed9ae67c9258bec3a05cecca37af9d6eb548d619a5f5cf79230691eaa705dc87063cca1c9b27f759d62a4a5e5671adb38c26b5e80e44024f8e26d01107ab38ea01642500041b10c0e4858244f496e016b5ca6b2f8aa0d1f7a9c612866a6824c4a0d43d8e2ff27043e459e255a8c894759d3f4c1df005c5e2c9f90a23db9d041082453c8172e0a5eed7a9335a1400faf0291b193ed00f33676a8bf2427bd58c32ac006350594c80ac6a569d7268c1581655ab41b902da3375ca1e810da06519774c57400236d19eab72448408ddc6f013c40cd2ff70470e913e42d7a604c9f88173815b18e35e1cd0ce2688eda394c66c2c7f285ddb5fba13bda56ae06432c07b49d3b684cee104a417119f0cd8d65517b642b5d57edb4d55e6a23924d2ba35964338b74c037a1e9d6d6587db774b58b4858e1e8ceab71b40584f45704c5a25496217c2587c55257068bc224d443c030a1ad69518b228381f727212f1c46dc17adcd024386779e4c22c4c66713c1abc80ddb5b4b434fe97b6c656890493ece0a63cd567dd885327062ec5202999f7b148d8b5d1f27a0df485495d234a886c5406e6adac905ebc6750c31bcd512651715baef964dcab4739046cf837cf2192abf3df9f21dc3ea8de08693cc025ce6d7db8a8f2c8e5bd8a8f1ccbf6642faae2e53d270d9d9cb485ce4fb32146c987b0595c67b30831389184ea7c238750aee78fe2b350330eb83225a79baa4b24b2e2cb5ba064709f74249861228ed4a98a25687b4f2ac7ef205415c2e25c23acae032d43659a2a6c130ae9cf5039248a57999a630cdf0b43df6f8476f9b0c8f1b171a9439eccc6eef2c2e9a77f5f4bdf00a7a2fd4d0787c266550f431e9e59879a5201795995f28f362333d66a9cb3bcfd7a94bd7212aeefba15ce66f86b58b0874e7e382782882c886ec25c6fb1009adc8cc0cae8dcfd852e6a1f3e74e1c30732738b912a384bbb047f8bfe55c8b6016394cc6b6f2ff1f0a333e29de9e36510fb9f8ccec94b40b33eb4c8355709eb759aa0dcea8d2d0a48f7c235be26a590babedbb1182f3a0869cc3da7e6a752311a1abd002db0ce4fb6e769d8047f8f359454ad896083c8fb232debcddb63084f95dff3eac5fd8d5b544e9ffdda06d317fb341dc2c80701cf18ddd34f9b7c5c79e8818785d0ace65061b45136e33cfb6a08868a5743fbae15b6f0c384c9a8d6e9031c2e77614f90dabc44b2c8d0a8bba4bfd26b4cf45784ca8011d8ab534166d8e4e537486cb70eee74e6ca31ae02f7d1b00c1242b6d9fa4011c98d6964c3ac9aeacc072c6acf24b5cdb0addeb1d37e4c59902a2fb3806ac4ecb555531ca008fe6643baf99dbbf1abfdb1426040df0a9dbc1d3455f8a6aacb425233588480bf0614be3aae8c6588e80519323c2bef74bbf4d6b06ec202644e3e890ca4a3401df9f0f9ad36cedac81cf0c07d6a074847099343cd99413b9cb2fc5e1b68db3e65201dd9a66c0742f2e8882b2fd34e13f59d19010a4ad45e26005145305438af0a8baccfb2c8ec3a7d1372eb5d1f08db7bb569edecc14f069d1690db969e04123f0948a778e8860a0a8a470d34e3bf0b86a1571e1915009dac6312fd1047c7f27e839595a7c85b21f9c818f5812698a1ca6eca316c3878e5c856b115cd05dd82c97fca4e244e7324ae82f6862f01f545412d2694f2581788d963fc4d08b0331686f1ee27bbd2af208744d85b1bd07e8ff846a7257f21b78b67dfca29f4bf81698f841f5a558963fad1464e4cfa630a467256bd9d97d3885165a826707bc834c932093ac9ba6a12aee7dc82fdbb1a49d6094f6440e4afd19ca476fbe2279a73bde77fedcf5966686dfa765c9d3c5a96cc3f64bc488a98e5c518ced45900cfd535a11942308115dae529eb01b2162097c08d6277bdb34fc89c690262ca9e72b195a7af490004f6ef6895d7b790604c015e1da584388fe720d07e6f97f1a69bf0964eafc0198d909a45d3c47bf6b845f54ff3c8a94e1fe18c8e84278a0f79d43b6dd37c578856ad72974933862119fe8601c4a6fb45b96c605b962cf3abfc6aebf68c6186fef335388fa5b032a5c90d0edd6f8b75566234bb426a553e1e4f68adeccd7070af4bd2dd6863c858a24cd691e7ae416ae932038bb70f33f60ff10bf927e11db112c391624654a44a3ba6f3c9c9d4ea0459c44bcdd335cb8a1ce04b6728408876d0eeeb131cc8956b4ccf4d0507dc44cb40afd8a41d4f53e87fc1243386df36cee4e8398581e743b048b9ebda0d27b4e13993dac37d5f95ff364dc4d4da8db034236d73e79b3130ca19ddeb3ff962d719e189b224693c44120d0129998c3e5a18df1d716e81dc0e11e1238999c7ddd90d5c6678dc7b7adbf698d27b4b1e77362e676ae6b9f351db43119ac9f578f1538d12bad620da1b24edf94a948efb976b4c6b1d03b94ce948e4294edd8aa9a8bed6617a86fe44ff9af354e5e85ae8068aafd5e8b625bb331f0f5e18724eb571ce9ac8dcd10ef97ba88964b188830bd005bc25faaa5b725bf6f8c68fadf333f3db643493a692c22e60c1fe11db7a12549dc4f496002582a697c6097500261f192cbf4b30aff5a3fd8ab641005839108934a145c06b56c399e0ef5f7033b38ddb2a06bc25c585307faca39aeb9523653302a7dfc9e5144504c9b11df4c12231d4494606c68d01fa7166a18fb8d7c0d3ff5a540098ae75beb8c23e7174abf27fbb395bc6fd9b7a761a039860de3b81f2a76c387eb0156f76a12f66742154d0a8f02b23730e9a0c1c3a83f7c796a38e8d4b4e02b0e2ccd6ac10f858f69385270139c29c15da00a8a272a35c796fb3b7b1a099900ef5dc1d85711e452675beaf2a52f83008da97481e1bd8bde9d8482a3332f79819e18357015d8c3cf1e097ed57a1e552ee5952a21c3670e2a6919599ed554d4eede8b8f5159b3f1de8a5b31e92483e9a6dcc82a7dc491ee94ead0782ca6c1d7b78f55978429ecb85487015203024563fb87246f83f92c9d905ad0e1cc3aa21521d3da92d57602017f47a869426449b46e6b3469e7c2d0b8f0608a8051a922778faf053b18d696a0539360eec039c8bca3f46e0845420fe515ab29294ff288e04096f682059715a895b4bb95cceffe56d673dd617c6214bbca0778fb6a41a8610de019c22364394df1f4095fc10bed89abb41cfe8be40720306c6b2fcd9f9ba6f9411bf70e9e3db1a559a1a5faeaa915a27734eb0f325b9d4b77829c944df0d4e5de535544d9bf67d5bd6c07e6dd022463265213c12c5d099ac94093a86e779916f97b6011e1a2c25e823cd798a5df5f52d6dd5e7985c55450816a35b52c739a7f6a0fe171c91aed866ca93c9a3061bdac9b9f935704c4ec3ec749c584b59af4fa74dbb999aabfe63784fbd69da844c9b2d23d052d8f0e9ca4e5b3b049a0cbc58efa0f7915512ae7c6d4796158ea284a928d80bc86a5ddc51bdbcb6df320e9672a8ed67fb96002ecc8b5e35d766a4bf1c595f14e324cc8dfcc56d995527a82fb45c2a6e9c1f1b433733ae57f77696bba5e82042b819152a207ee3b4d2938b0e27dddf6cce6424c4209dd46d2e81eb8ac526be64921804c8faa4b830776bd16733e8b503d550589fe927bee2823a914d61b5cd3f7ed57f2c4f497b3963da6a7efe4ea35dc91ab127622a918692bf102d134ea2f92420ebe2aae0e74dc46d092aec60996d0c14d70651a34686528d41cdb5918bc93ec9803a4c68089fa157445223f49ca974fa0f6e184bc97f2ce4f0f096284ec379919df51bd65cc41f7c9616e14e7dba4531afac7363304acba8c7833baa0cd1dc0f195ca277ce62edf44b0232391970a44dd9151a1e778b68e389406812a4ccb6d06d8e090cc8c591b5262603e21c268bd4dbe451c1a19f567e910ee9b5e900b305a88cb4a4734ea2418454aed810d4b2cac181f90575ea60133aac13502fa3c46f93efc2eead81fbfd3cc07f1c117f71666aebceffcf2485901fd251f60ad3747e1c6bf9e8ff1d85bdb4abd56c37379e535482f6bd1f27397c4f404a86355cc642caeafa518dde1e34d50d9d5682bc264c29ca8e4b18eda129a064f983374437cff0fe323d2071cb6796e22fce388c64b8f20a089456e07b6b7298a25effb43d8058f30e4adfe0062baac483511b551f0c2d0f2c2583547b958e4e72ff2f571c5db95136f027d518adb63de600bf84fb9133f0ca73bbf71045143f1dac12672fd590a4d7c24e7772d2b6dca2c489bf899d8d9166eae47d6d94b8cd63554bd9b152dddcd58ae2730210dea686092adc4910d583e73394c1ac40300e30147f288b55de394e6ce8b496be8041c890a4d90bcae46ef704a5c07d2e1a76c9a67f32f322bee8df1ef69f4708336e172c2d1ea3e7e236db4b7b80dd5c0dadfd4d2d88324962edb35792675c384efed08900feb7aadcb4a31b80d746bcfa43c446c8ef2247a11d64d7a86a2033066f65cba4c5745871b891144dcb3479d6a37b4980936b5e8d33d25e5106e121dac0dce265f631b06d0bb8e6dac142c102da138f909303346d7dbec20d0a59d9d4201ff7bd4c909dce32131e2a9a033c928749902310f1dbfa08d6047568134bfce0c346779733d170c380d9a845724fe59e3c56b0b32d36ee74632d2303fb593e376bec658e952c09907d8a5803b5a78b4455656d6a775cd28b628eb5a90a4a6988929686b08fd9ca881025cacb0c8d9e68872403548e8c2ca1ece97db456cc75d03ad9eae7a285127631b7e8a03a42da75bbee057dbe2d523bc5974a85b7f7e7e8e300d94d54f22581ff28588b14dfaf64eed36666401dac14cd30fe2817b03eac45ed05b11b5299d02dfcdfefc7765fa7d8a011609cf0e19a5f8f45aa2147dafd6dbf90a7d1090b5c40a5031fbb6e8e7f2b0749585189c0dba2faee2de6ab9c4b657501ce6a40d6a4b7ad09c0ad5e48ea23e6fea3b0b19512c9e7f504d47698af7e592fa3089aae3865862e42522f8a03d5592f0b3d4ce128acbfd264959b8c09603daeeee74a5877fa590cd78a5072d4e605dee43033af4be1f3845584abd4bd962fceaea103dba75972db9dafcd879995d966f00dee388fb2d89fac64939cbc772326dc5a4ed6e7060a0a9585a5378a99afdd0196b4e23dbf6cce16be74eebde880df270a3496020c503154137e7a792493653a2e9b114f4a740f1a0a4f43ade515e6bcf13bc69affad26de9ab37d1b31611cb6fb5ab60a007265bf5dcd07e0f03f82bcfda00e195efd76ed71649e156b993ebcfcd3b06bd17b445856255e8dcfd9292e36341b477dd675b3a56761d39fe8acec33d682dfac3289fb03b0464ebf07a1b4d4b7a7e998c7ce8edadaa04e21c574b86e26758e8fc9761acdec80ac30f3a66ec501c4a8e9c10d2c1fc13ae9db6dcc97695b18b35728953a14dac8067750a8b8c4e1b68a98f9fd4b9bbcc1634d6833c5b0b9b7cc110cf48fff5584e5fbd6b17fe4bb78144a744d332102ec40459f896bf8bd0f2588a909aa07dad2c3812ef126c5a5d6c4874fbf941adbf9b507d008fd9d0beaee23ce2df19394643b5fbe0c8846fabf3f9468f578110390f63d1490e278efb76912ba073f382fef1b5fc966f32d4c8d64ed7256324dbdd7edec47f9bb9d7d083560ee158f5701df432e6a21b726e97b0661ea998873fb61c9d05111712162133f721d32d0384f42811008af5fc9d37d5b2a86f3a0a09828febccce239ef191fabc2a29c6d62f94b8146df5e64ccbbcd1c7e3850eedfccd078d5848b62741e4b67c39b40f935359264571686905849f181e75bb259692c9e9f0ecefe256e92e2414a91fea8f15d49b30393d5dd5bdc2e9f8842fecf92621c320f44b93935e50262cec943e8a969a535ff8096e564ca3c4b4c310b077c9bf033151de81abf486e16fabd07eb2f7d1de5c508c52ef23e6feb1527f8a03644c852c1463d2461fb310794e49bdd448c6acbf75cea59971543ad3a4b76c423a256bfa3b0f2c809bf0aaaf396216481616c90006efc66ce663984d3e269c8548ed21e98a6bc2abde9ae38f7b161e1a2c9cd76c29fafab33128686ca65bd2256e807a87303eca2bdcbbe9c3fee03d4f50d77939a5e6e5d6d8cb3c0466188e15ed1128259eeac72ae041e4844ee03002d30e3fb1a20e8bc60a3cacf6a8625bff54d26dc4c8e0174fbe228a03272c92fd48c00d82c0ae38d2bd40557288fb1e743822dad16e96d7f80625e590ca0494d82ed79bc42976671c664056a4d3913337788aac5f4dd3e1b11359ebc67836e2ff3f22ab6130630324cf6bba701f20e903711106f1bd8fc84c63a8fce709533f568041d2e1bc20ca5f5b4874fdc1ffad43c0bae10a1f9ee2ee1d248001189982438bec302444d9d6a86ff858ced0dfc082ce9fc1eafd6dd5589ac10adc7186510f1fb4950c661aa3672dc36c9d9b44fafe73c6eaeba471cc9c03a0fb112612d07c1b48b3f63bb3fc7586a68f24b424d1b84efe5c9ba175a74c60d286d38522627f604f8ce15473efdc184e2e1d90393387e84b3c7f322cd813046af8fe91527e450a16193c7a6f6f372f9d9e1421a37758091adcf96d63bc31989a6164fb1c56e21251fcdf22c36989e376d238958d839e42acc45d1b09cdf77e225c55057fdb9a1f76eee40933252e44737c1a563e96bd01b66253710d2bef1dfa781ad6151b7fdadeec2116ff8146b42a7744a562dfdee4c586d5bec43a9f4b5ec0699fbd410bc864e2e4d60083324d7008e3909aaf698c06685235298c0196c29416047292d82a65be2e1d444320f41fa59e9094f7ea28cd13014b9e0f0d6b62d13cf4958be2aaf35a4875fe7cd2051d1c554fbf7c6a89b2a35b639beef2e4999569c09322dd8b93832c5196aaada1d617bd82413b7a7288ae0be5115102ac2bfe48319b9962e4508b6e143d4040b567062c8cafca1753a7ab4fa974c40b7dd494249cb52ef6f0b418cd1c63714eaa3118aeed91454cf6457c58b445ac916edc2b79360eaaad9dea4541e08a65085101af95c64ff862f2c87d81537e0fefd492267cf0cb64a1d4506231371d0025a3ec26f49fae564e90bb0deb05cff0cbf938ad5960a579d4c722ceee12b94e398a810dcdd42dab642851df4e768bfa60257fdb42fc8397055d213b5c1d4fb8ae7b4c41d041735180ebf9b8e03b4a7d4f0bb8dd2cf855f8360fb0f6785c59abc7e5382f48d2ab7e5bb2de5658f21c4cd6f2e46f4ad81bf39d8ac3035c6a5aea5cd8b087553511098734ac6b140fd29900e642eed42eb008ba199880e73363896a51a9a9b21dd769e636bc59628232f512d545a77aee4f338b30b8558dca44d7f6d21f6cfdbe1413a475c27f7be47ef44f28f38812166a0df805953bb0d53325038f2a5f93c1ffa9a1e6f966d13fc0e7f42f1117d71f2b15fecdec3a4eba3e63e4ef9208d6e189ab860463c61201262a1a3b20ac60386e874feb2c00f28f966835b369ba616b6fc361c89087935cfd2bb8e4cd7bef1788f8e48367b0043d589e5f1f6b37737e0e68b24aac0017d292329073b57573c7761310acef2673883a4257ed2190c9fbab357c1cbe8f513aee7ec689503f5025f61406121a0629070b9e511165db23c12ba529d79d91fe11e3a6e3fa695584146310d744560dd4256b231c389688c75aac8ffac40aecda382afdb7e2a80dc0c12c5e4f825a1d6bf2d9976bca68a85cd3c4fc4156129c530b2b22be887b7dd98f1e874e0eb975ee091b2f96a08dc7b64b098e4f7c2a602c93653c7fb351929c351e394b438503389f50341a80f45ae81a65579a3839ba28175e99de063e43511bf632e472a74ff5abe7c1d6c571e826d582f9efd3e0131a9b3cb8636f1b88bded6d8b2280200ad515bd993cff008fa1bee5aff9dbf9672498f35718a583069fdb221b23e483ab9ca6b95a7aef103218667bdd887b4567f28bff2c80b71290c2c5cad47eb1b3b4d81c8c0c4ede8fd1898e25d085a7891de7b0673a194e36cde3efc8a97e6f35d5b5b89789ed9264e992228182d7e1ea2bed529006a4f2a4813b124c81927ce2e2194b1e44510ed5e967fabfd0932d3456c258fcdead7d44701afdc293a20c1d6f2f31ff5e73f9e346155835ff5998ef86cbe6af1c4447068603730c5cd1e7b8cb2135e8580fd30c2622c43b0f10ecd4bdd54ecc38c7ba2ec74d5f2d6cda9bc5bd0a85b757014dcdce761a5ef9f6f206a55752f24b57e57bff1b0d328841d52a0f1c92cc6841b92ef1064e19ccff5d48c27074e04f7a186ea930e46ed367012baf39b5a19aaf2131311620ebf0a0da2464c5b839f9900a63abfb88110113fafe8182d5b13ef1c8b4f7ca6252188d6c3556471499ce375b0c9cb43a85654918d31621c1b56a1cb8b782d527ab81409e3d8dbe230be0392a283766738d42d059f2c9125c4cc07c359957f4ef7e7e12468e1bb880a009b65d2c92056c0370cb0c692ebb9d03cff7a4e8f37811debae3164871b6249619a087e2fece9d2bf9240239a806611be27a2036554accbf1e200fc5f6dbfc3a175b042f0829d8fe3095f905148779113044f1e613456dc64e143fa333a468fce4ac867f7000027f5a402a847561195fc6488395bbe444977b48b0d7f55dcff66424269be59b70e8fb8f0c07795166091932cbe5145833a5510b5da769baa9563fef88c484277b885141f022b100fa57989998d6f53968cf6c516aaebd821709922249a7b96268d962251bad8518e1552b4c6e27a5c3be85995ee6597410b891379dcf32d91f1dfe788e15e43dd75d3bfaa8cb82e77d76fe6fa5d22cff9796dfa7b81b3d2b06c5b56bcde3ffae6195cce7e7f2acb02172ccf2d707f8b6c1c1c9f51b1a419cfe56e88f9104acf05d9d20700dc70c5f49ae31e3eea3ef1feeecbf6154cc29f34a2cf865a947887523087fda533c50cadc7ad363f2955705b8ebad7095b878128cbfc50ee7b77c573ad8c06509705d8ff3f5ba952173aeb7611e81846959c1835912355eb9bd88c2953a1fd62c038b691184009059da454881d67fb3a8a2761419ffa95078a000001132ba1985f022b5d088f9ab3206ca8fc5f3b50495f857582e479b74ea1e82b8400b8f6703ba46e95e7cd570be9672ce4e9f72fc92642f4c8418f67d02cd73f0ceeaf3c5ea622e596f14d918ebd57a611d7f6df0787d6da909270c3a41c327cc9c160154c46b441cc6b48432722cd304d8780f90fb016061f4812d781748236775dbc0439d977fd0a652f310f0ed045652d3070006fd9a61eb22a85544fe604648d8aa570b5d32bf043cc05e583d8469e52e1ee4798343b2c6d2051a59e62af499a2ef648a81ba8f93d84fe761de66f700ebda4e5cef57d672f85fcb131d4c684f0f7aeea50e655693d31a7dd5d99b61a870b45b8b363190537531565bacaab88de8c8aea80632c92e268bcfdebfdaedb924492efee02edb37ba33c45e67acc39a680992a059fe70c9e9f12949a69f9f1b20cb388c59fd6623a5ed22a6aff2c53b7585a98507d7bec657caa535ae1d9f868669d515a9169d9873f90455777e5c0403c457de5c01ccd5771084e0f0e7892a59a2b62a097aaa1ae12125e2f54625f8938578f4e2f88c67b5578dc66f1c628d02dce4d8271f84d493e766764a97cb1b97de76ce8d2f1aeb821aa25d169779f60059ada4795ced5403fa1bfa8ec39a8997fd8a9a38643f9916e8f9a79eae3193f2816606c7ab53161412b817dbe29e31577f554812a0e5dda955380c75b60a30175e3d3760645cecfd134dc9157703dac0c26e7411d360697b2116f530aa3aa182fe64f3b1e4dca08762555e3bd24b951f8df3fe17ad227bad487873f0b945abf505ac964574afa7165cadcc65bac46acb209e72952adb369e1fcf3e17b737552f623a827fd7a7b6777036e5944df0cf2aa26cbad25d7048bc4eff5f7daa3026614b19466d4dbc8147df40db15cf67d88e8ccf68e22504161fdd8ab30bfa78f4836775ad798932aa3e8ece402559c7461b6b03c9cf4ce2480dff4c0d3638ecc863081f459e25f8f22c6863baa96eebb7f43722d8458129cbbc9cff903cd9874fb7e6c80de0ea3767729cc1d162fc03581863204d0de470b68a50d09071ddf85408ad88c3ca486e007b34308875355db8cb79af14cee4dcb69f88a750f5eadd2d16b9aba3979a65519820a6c9473b3d9b0035930795158de001235319142879d526dfba3abea5637c8ec58e546be3675dfaaf9f513664272ba93c69b5f5720d9c9db224e3fcc381d83d8658336fa782f5bf2a686695dbd73a751043234b7e4e240feb5a6baae21370adde07cb6e31ccd6ea63a4ea14464684268a5afd713266f56c06a06558856ed25c5033449cf6692156287ed9193c951c6cc61e6e6a1cafec6b56a802424b318a38d6d1f93274988ee9a4347dcac6410437336ad05a44cc9b91c38a3908750d83991e869be8627838b5df9a086a37acb5a4d2e9deccb2c631fbbc0f699db361567c4602bf11ff2cd7a235258538420dd6a42ca8fb9b54ddee0e28c37b7d0880be406e1f60c8cf3de964570bfb1fecee6b44602803757da4b245b5b059b58578ff010bd1730ca8dbdbe36cb7543d4081e2dc4f5442d15a895b4a9da02dc0cfb1e1a94f2a982320cb80c0fbfa675d123d71d972987c2c9215be8b9f93fe6723cd2630518a16511a0ae06e50f6853fea81e29c5ee1e7636aebb72bbc51e17445d547ba97cf1e4ac76e470600816e70aba38d049120ecc35665bfd0bcd0bbfe0e2ae2e0e3723cf6f20f7c301adcc2bbddac4f1adceb70d033f9ed58aa26c21829c986acc6067900e507426157ad881ea9974cfd67bea55b7b1a6f63d7c2ce1388f15269e919eac2a7b535c93697551ed92a24d4f39e8ae402bd265f3f6fe43ec7ee65d5386a63fae8d836fcddc59fd72be63c95f0e082e3b11ae81050f3f925dc68cdbbe2cc70dd4379e89d04a8916fe306657e7f78009af173c5678aa06a3ed143213224c40e19be0b51cb8346af9be18570b4205146565362f377e87afb4a4f9bee0fe6cddcb42ca7554f9dc7a5b0693af366e5652ea8cea09ba6c77d9a343a22d7badd94ee4c1f2b59d239da6a4e11897a7e2328ab7252378058045c4c80e5d2a8a2325e150d9682f678f67205e4cfb7ffd910dbfc9ef2d1bbb7001d6e7d268da013071a02cfe89b7d25611bf3a28f1d1849371240b5f85a3a8e9100d9ccf8bbee80226d90bf9a42bcc7303725bac635d448234d34d7a4210d9ad73c34268070fb3eac19b16ced2fe1a3ee88e8ab8136f5a1065d7c91cfa93c6786e77efa7e49c461ccb07e6d54d71a364a4d40110f2fe7ff77671ca1b54a83953317a26b74df2def890e07ebd687925e8e63b8eef58b4439c93122754ee36f0ba7ace488209c5f737b85232f7944724a735ef3f6a2f02e8928dec8b7ed99b39dff2761f8070a61d4bcfd02811208566d81ac4fd92b640cf0a45c5fcfd400c8bc38593cbdcf1ff0fc2f2891b260fb8c525ec70b4a50760bfac22827dcf81b93cf4581890e092a6dfc3a4fed3eaf885c1ae0c7a0ec9b4860804082e9c66709166026c147bf0ad1ef05d0a70c5332a27cd29dcfbfa769923d5b4392091b66ee8832006440a8d9f40ae77fb1aead67eff00496347ac1f373411f52908bcf2906cce3574cdf59808fbf4a804a975583c40b377dc6fbb6e019346b119087381303e8921f98e5e16310c43221b2fdab0b5658105052a9e75071c2df5f4641402422dcc3239cef0bbb90208d46f8f7ecbee0ebed8a8869d1a806e912b85e4c615b47956818b15a4f10352890933a8fff27384595fd47da97ff3eee8e8320c271859e3e302a8b3aa7b19728afc87d8d44a6a68813994d10253795007fc5a6cdc43cbeb66042f9f3ec699432543f8e0407a1e1baf485ee5fd1dd0ea3cb4f06f13a4ac44c143d1bac2ff64af3b656d581fddf1ef5ee7eaf289f0f7d8a363c99873795d8288fd8ba1e373406f97f1b00e2f6527fb73721f7e781155d1203c77f2899d65876690334209fe4ff2f98e7d2b63123e5275c938cfbe79eee9699d96d051a56c7446989b2a6cfcf6cd0ea79db9e69b15bdc9ab2c736f788f723be3253e3195a0d2f5e313cc6f9162fd94f3d18889880220dc56c7499b067d71f80525b5ac5c52703a7843f964ab3f704bba5717b721d860f39d7718bdc47d35f75f73dbed4907603589f48bf9de0a9c62d7f83dc5c93b3e38693c3260ec9879a3aa9295e2a3390f10661e1727092c9b9d1a131e3fead8e91dd1b52d77cb39c15c81e6d57c9023daf160fe2aef5ebb25e28272b6542253c14e03543abc7ac0e13a1208cf59382e20e6231c5ce02bc21cca140f0652298e22a9416d5c3adc2fc05ba3ee13ba1081e63f04cdb4fa2655cad9cb679a800a025bd39f30d298aac8eccb04d025257dd2ad73e8135d5e6583075c56696aff3efe2b16cd530466079ab718076ef7ace616074afc0c29bc84e6c988491bf6124169c95aaaa571ab2900cf1a9b044c00aa1622b816f256159a846e2290cbe62c5387cf2c09ae94e0557b1298fcdc4333b64cdaf6c21934040261cfbae0b76f46569853cdb35b073bc68d17bdb7b2a302f9f9caef63da4d6a7741f1d0a2f9a901fb7527d384339aedf306e8eb3c2effad6e4b732d0336266cfa5ae93d3ea428725d295a8bdc3fbd74372a000a03b6005bf2d6b3585fffe1b4a274d2b015906b816014f83a394b8a79a760044e96bbfdfbb81a441b567d59baa4bb817f378be323298479da6bcbabccec8653e95abed7731ed9be82839ed1eee7008c4f9afd0a94cd0edea03572c33c06df5716e1e37ca299996e981b1546e36f5061e103fa7feb58c639b1f61eb1f0444d64792d56c4f66012147e54f13e036ac18bb4103887fbc58b31405046b10bd8ea8a4793bd9d68aa11b2724c76115fa64006e5e17e3a36c44b74c564376230c8fd281ee322e0d9d8dcc29a03f981cea26fbe5b2b9b7624167c8cd1e4a57f7ceea828e7d642bd1fadef7ec86ee008120f7c08a7f29d5764afff133b80e0d6a8167938feac0bc83b55ad3242a4d94bcbb228145f47004f2adadc8087cf023117bbae808b826761f561ac50d4fee5cea116a7b844935802f1d38f42e433d392d707b750849f58fe906cfcc34fdc2d0eb2cfc095b9fe898b1969cba6f72a7fa1194289eb1a70d10df44093398b38dc46451542e06038f340b47d3a04c0de0819e131f8c0810881409d07b94fffc211665901ecafc27f2106a23bb6e1a6e0c6a6779b6921a31f6f6baf5f72ebf1fbd681505374fe0f795ffe3344b62bd85569a0c7c3bfa961d5dd1a890fe269f56651610c23920cee7a7bd64e23c905d7301c94e2d5f00a8445f4744df193e98d8aa1de2e820579a522835ea55a45aac10b63c02a8b388b9240d4b0501d937961c473d8b1fe6946d4dfbd3b754b35fdaf30789a6127263b58776062827d21b10e78820fb959f8eb796b219134c3f331474b66764add3310b099d9629a7631ab1cf077b595f9450dc7e67a36ed5291102c679c31fd34c69f404c0a420e0b49ad5dba73720f8426f6d378ce9825f7365717b290251235f39e333ce6c4ebfa33a681ad0ce7d62cb48105c84de060723252ca3ef943f9f7c08d9c481c4126dd66b464ed770173a39413257d7ac3b2edb1da8f37ebb1ed086df293f76bca98bbb1dbfb632d0dfa052c0b23f65c9c0b9f5e4e0893c8b2ce8d873196f41ee244b060cbdbea0a80dd76f1a41d356a63c2c148e921ca207579d9fa5845608787118ea18984281a43660e6b62ecb7f922c6a3e3f944d3a1873774294ce3eeab933d3e7111f402983bbe2e80074a7a3f3829a02bc3b0e04cb77a9f7314d9e19d4e47bdccdfe54f6b6d5192ad5d60360730ab6438dc10cff3c9efd0bba654e5268f06cae6f3724c17a67bf85f801c7365a40e4578e493e3ab0fd29261aab0f17ad1637d6a0545570ff388ec57fdfad6fa1c52670345c561a0eb6b5971f5b69704473216a22e5e9e028ed8323aac64b16b20f1234030af75f3a8f3ce6eb4462871ee8bc87794076ac42f8b6091656bfac7ce926680e3ad05bbfc6b31bc7811cbb9de0edd93c5830c6d9f96fcfb19de7c1ab1c34311830dc4b20ca06ebb9b6b26db3f2f904c508aea04b3d68c351c59325ee897b35a2f5bb6636a38cfadce10c9ca68e1d8ee0864f03a133730b03218d7254be0cd3cc43b338b6c9a8d037acf8a4c638e70a7c02044a57b7a457309c15a7034d5aa769b02687f9604938105f510ea829a23b1d045a7eadde2b79cecf24fa4d14fcc9426471723ecdb319e09612aaacc1307e2d8faf70b4336e6785bdd6c65e4a7820ee23121112d2f6eeb6e5de52a69452780a220a130afc21eb2060a67ea0ab41ba2a7fec1439b3cd2b0aac9f2bf57adac95c130c6388028b1ada40c136f9420d77670c466ab8502a039a30050bc8b0c3640d5090893d63a0c16656600417e8c00c4fe438b182999b1e482107ac2468a1470b99f83c3cb548f082e5c41623c0620d44608175576280220627c858031a7557629841a558e4e0a46e59f8b91b8633d40880baab3044f171c26b589d988ee925af4b8698505977a54517a1295af86c5a6c81468da41adad0c20bb5869a1654a8f14d5d8c4ed502095a4cc1842fc414a13db49ffa6c34750b34ec7d78795337e3a4db52764928e1506848b32434dc960f910d083c3b476451c404b288acbad33cf0d0d0703a0d283fe1d9f12533a091070b5ba3e38e4f1c9e5a19195fe230cd5fe83e3bcb137596ec4e0f22582d88a91107c704c316b3439390d698539bdf3fcadf4aa8f79d135d06340627e6a6411b8789436808823462ce1323788a6052fb27d0b32b7e947b3ca440c3e5298287a7eef8ca8fa33d05a255f468b08f4f4d2f97e5aff76369f3d05df3d0cf37c8c840430ad250da3cf4d71f105983c4eab4c1ee1aecdfa2eb08c408820149eda7b1c50de9346154bc0d30d480347c10d13cf132f363c0e6a1ff2a4281e4b85e3a15552a95ea46a6bfeb3a3a4187f111f6cb3bc4d0a4c1ad49e26b0e811999b81ca5c795bdad924b62a2d130fdd716ceb0abbddca1176a8ad20b1b60e16323b678d8f0c14994f2e5e4f9d26ba7799a877e0de4048f8086bd5319ec1d06dd8086b1d53993c6968dcef15494426d2cd4fefd72ba90f353c3ded1d91abe04b4061ac6c4ec206ddca712cfc969141653a05a18c91426da991aa7ea344e2f611cd4d6ee25fbf53392063ba779c04103bde16f9c063be75b094bb1357270703ae7ba82869ee3399e629b3e62435b891168386be3d4feb95d55d83852fb7de021c442b95aeefccbccccdfbe7ce7d1c30aca9f0f0df66b387c28010d57e788caa351ddb5631a14a247a38a00174aa59165818620183f1a5d04aaa2a696e4826ab150d5fe2568e4bbbbbbbb63d835e76b139bd8c4268639e61ef66d97120df22c42c3b935a0e1b48969d068e4fb4ff3d01fbda86a1efab316d0b055a019d08fac16232dadf29a58d3a8b055ada2f11fff22126922b55ff329d0d08d348d6a6b74843185f2b6aaaab6ff2cc065e217594e051ad29fda4f4d9b6c2d81d6a5a6d3d679241f1aae0e8f464571594143ea84204a303333336fdcdd0d4d75f70b129700c980261144892564124198e665dd9df9e28b2f7e9943b544129e12413023d09059ac4685b367e3d6dc81c6327b021da1611ba9c1a9fd4c64b60c178e1c81f6b7aa866cc473e1dfef29e0b061e30b36b5df876d127f4fe089371b389a877e1014687f680349c3ac92390565d23579270db3a9ba82270db33b57682b5242978a9e5525405d2aac50d139bfa0aafd5bf742c32485ff132f233f49e4a1dfb5405dfc5766eb2804e6a1fd5e839e9eb0fba2f6bfec15506a7fec1a9a6a73a186271a56f95bc192718e5f49a37c94d4205d3d102b36da1186260d6abda451bb842744ead61d3e4c1ab54ba042ab6e0ded18034ea35cfcb9fa57fb0812bbd3bb0308d749ed9797979797162f9cc999c981f12112bb33933393439f3ef6dacb103b80961e36d07f990e236df884d801b43cfd381450f92ebf9e7a79797979317543483f44e5e5779e5279928b177e6df942247607861722b13b333971d2b8adfc8dd424475e2000f5cffa00352f831d00fbf568a4c8fc9993cf8df2c12f5bb6d450d0f394da5c0e8d391ea02f9c3fb20fe7970463428af852b40b1035d6ebba2e9990ab100d8433b503580de235fe4c75336852fb11ced430091352cc97427b5e0e8b957560ef31c618a3bf0804fa1fdc27044665f9b01ff4200ec47d7c104bbfe987a8fc1092ff720fe2b1fe30bcb0476525542a551375368a43cfe2853d2acbb717b6b4b4b47cd8b57cf8b55bbe7e8da7d4ee35d017c60a72cef31abbc6ae3d3f8efb402ffaaed15f087a96ef01f27ad4e574f0940ae29a8b35e217f66b7fa3b5ff817da8440d7d426054eebbfa037b0ff8007de87d80be7f9746d8df0fecc3f8347e3fb0f6c2d91f41df2e4c087a0df4fde0be2450b8bc0b75f9a0e86486949e7e504c99212b3f849aba21bb30309ef44360bc4a8b172211536178a1df3afae576b0aaeed6e8be30f4f56bcfe269bfe2a978266f48e95b0244831a00e1052c38421537324c2c0f8e94214f7aaab006296496863daaf6dfd2d2049d3f5f9bbf9e5a8266a71c810b65e8420d52d6304586be132c5383c90c9991a1df3a8c17828098dfafb102e6c7770e08cc6be417a5b6f9216fa30dcadfbcce61e4833e5383720681c6770902f4fda0fe59638470cd3e172066c51e09ac0af13ab16faf50e2059d2a7febae8b99e049a1a0f147cf89f832fec6678ef1d7f3d8edfdf14a73e9ade19f258176eabaaeeb923246795dd7755dd7755d4d2eecafebfaebbaaeebbaaeebbaae0bbb2e24687b8a713618632aeff6e3e8e57dde4c7ba3e7a4b4ebfe41302626b4d1fc654a886fda1bf2e3afcaa61cb030f18786891f7df081071e6ad49831a3461b6c681d5de547048e06e36754508e72a753345238fb5cbfadbdf65561d76c5e38e2e183a713f6fddeda1b5c2f107bdf6253bbb148a376ba17b489304251afa78179e0cc89bf398c87b5bd4e0d234dbd3ef4ce622eaa5a1e76feb9601f1fccbe2c4ac35c7f7d51afdfaae8f31610aef1093a3ffb6c535d73f54dbd3ef4eaf5856de4fa2effc954d5230c6fb967ee6d0d5ed7778da7bce530d7771da535157c3a372cceede27238733966e4aecfde63e25f97b7dc0c7bc3ff7a3fc3a2b6bfdebbd814f797a886ded23e64b9a1ce0f6fa89c872dccfccdf305e7679ee6cd0779f3dd9b1e392166aa67de9c07630acd7e4a4e88999a7d733abe661cb1effa7cc89dba32a2e45aa079814837b706e767cf0346e7034c8dcc9fcb9c1b6d18a3e5e4d66017a38c0e852cbe60c19e76a8ee230913313252bcf49c903f5fdb2f4422a6fa10e2354908e46fe5256664e4f7909ffcf5fa632f1481aa72ffe368276654ab1b9b1a9a1134a192193223d35ea451fd553075076080bae15251f759c52c1f0374610058a80bb36c230436c2542c8cab549e32c28df0813f7ff21382f3cdd6f05fd18e1fbf9552a05b7715849d789dc0c9aa0b50eaae583b359c35b28e10b39882fe70e2f8213eedee8aa911ac31be78214e32f79b171b9cb36a32cbb2ec4f358c3315c35cae8fd6d054f9143f1fbbe349d0fa35910e6e1d3be272fbf66d0deef37b3a624c4c64d62433e830fca0c3b8c7cf7768d51760c361f86d38ccbe262f9376711ab631b700e676b0d46afc1c1ed8a155f9edd06afca60ede1afcdc8366081d326196314e07d78cd3d135be065aee503853630471f135510e06acc3703b963b143f7e73590385e4ebb8befd405ce38ce4b9844d81ab8e5490c80aba487801910afaaf201008040245ad7b1a6c8e0ffab0d37adaa7593da0dfad46c5977ba3c6e75c6ec75710e8a7068119990ce45e92ec419ea66dbe2d4adbdd3efee472441008b48540221008040281e67561cde330eda4615a51b41f506b6f6c5b681b08a48148aec29c2381d3605b9b516983eb5fd823a632332311b34f58820e6a7cce3997f66fea53608c0d1cabc82de6fd108436374f0bca078b88de2f28574e0088f8efb2096d366a977654a6a2bbff6eb73716da8a02fbf1b7b8ed6f441a1573ec118efc7c01ca2bc3cf4bfc198795342a6737e41aaefd31eea756c699c8760d6ec7c08b29aaff574d14f9b7b28c807af4c6e974687f18db933975399739d5025dd4d42e54b5a5aeca8053576613a06d770fab9f34cf93961ce585397733333333333bf3b5f1cc401a3348dbb866de381671333773b337b3733733333333b333b3f347f989d234ad8af213a5d544a7c9939d263a4d565c44b20d377333377bb311dccccddceccd31dda6289ddb3d46292f795d18366736b30c04d2b4adb5adb7e6b8ad4322ae45a2ad698f465b93d8a4b2b2b2318b7373333773b337b333f3a7039129ec46a93b4e94e0c49a48d3c35a31f1236ee427ca939d1c254da46b7c7a9aacf8868f7cf113ee93ba430c3fef179b4466d10e315b43b411a1b13b662342f9738daff5c664c805888bbf90891a5b0d8e7cb4dd2f8c37f1c8173f3c4fa6c8711b2792858f4e13247dd3aa2fa0f02c99828bb04d0b8975c2c3d140b7c193a381465ef610ff3ae6ee4e5b731b01eda6e9658701632a4179e33287791ce692601096e0d4ece38d4ecd62ab51b38e2a67d5c53f5659f906fb7802ca3ab0ea483c670acf6954f69bc9461aa9cde32989442aa9fdd9276f1a7c82f2e74b1ef37c8a06db6f3a77cf69b05ffef5a1e7e0ec0dae3fb855776b84520735fb88a1e2ded820b28fef655edc1ac4bed9a087a93029687e065e9d218b0c0402edc0d0a9d987a3cc86c3643207dbf08e5fb361636f74cd7e024734ca3f7b03644fa9c87e77a590695986a3dba3d160f619d86016ce10acd9dbc8b29fd9f68593bfb0ab3eb567cec1ce093153417cca3e1fcb84291d122c74501e467453b7358e40970955e8d47033c1a01d13aa60c268d0f61466add0f3ef6b2ade4addbda1527a7ebee1e7167ff3bb8db3e446a1f65f33a8fd1893a1f6cf6652fbb3286a3fc8b3507550fbfbd92413360a2d98c1f505260336453371220aade4999ee4955ed3461e69e4919ef346af4915cff4228ff3e8cb0b9b1948dbb02de4c5e78d42f5e719547f2643f56f26d5dfa3a8fe9e85ea1f75501d475f1fc69feadf5bc8b301fb5032a1fabfcc0faf16547fce1b7dc873c93ebcbe50fdaf2de4b5803ec4a6a8fed916f260681f4e27aaffb6953cd387bc6efb704ea1fa735bc863a9dc87590daaab78bb5b83bfe4f5d81afc246fe4716b6bf053af576eb335f8439eb3b6066b5eb852a85c83ca9f79216f519b8a2e6425d6aa803d33335fcfda87224dd3be2a2b00aa718d54e57307aa3f835a48f567502f289f4350fd197405f6b1f685cd04865d815d5d47a95be02f30c4cb6712b4bd81cd0fa591ca4375022cd8d7fb1be0fd03ad405580403a0d66de85828569904ea3ae1a50158bbaa2585082962cf114080ca0325c35a01df70f455f3827a5decc4c930082d64c252207c4ac6946c1a6e6e582da3f2f2fc82b14bfc8338917287f8706b86a34e9c5a8578d771da597056a04e2454dd46ed04e4ded6f912a04edf0d6b8fe7a8f32243fd1ced5d90ea4489122458a142952a4489122458a142952a4489122458a142952a4489122458a142952a448912245050a55edd7b83dca0b9b1948dbb890888e482593ca0a4b07a3c5e5c5864dc5ba51c51c9716181dcb8a8a0d316eb0e146eeb056544c2592dca9fda31bfee5c66d465414eae7de73b1b95abde236edf2be161b6cd50265d8270306ff7ecb6753fb7bdac8387575b26a3859b51fe374acd44cfb22cb61a930a6d09db1c59cbdb1a252a1d1e485cd6cee0edfb47ae536ac98232f6c62f3a6d52bb761c90bbbbac3dde19b56afdc465ef2ba69f54a72f7eef04d2bfa4d6b226d0d947bf24cc2ccec7299bfdddd28a158983931df8fd1b1e0953162b12b3f59a375dd9ceeec585076a9da1aeeee3a3225d363cc7a28c7ba314af9bd94921d8b18a5dcadb1d1a526a5ec1823c71863dc114143aeebba24cb2fe6741319a3157c45b4c2fd0af9f1d690cc0cc28981155090aadabb9d13df7de01f8c2934c61ca9ba42b475dcd5256f8d239dd371430d7581c66d57de48249d022901e1840084e35b3b77cc0903eb485d2cd4a5820b49a89aa85d88e8f7f76f091dae8004954cff8ccc01eafed7036c7c051a4e1017e87231644201d47d22a60f63d4bd023d2d0ab43d495e665e66e9ebd2a54b77f7df5d291ffb9d31468f490451a2976055a740bf202f48c3f0970490cc0b9244095a978c8e1581355b6611e3738bfcfefd7cf493de5dfe612eb43d611feeee2e7f478fbb7bdaa1fb1913babf55ffc57eb1d8ef31b62fc188effe7e6f90bbbbde4f6451a0fcb17977dddd67d3eefaf3ea76f32e8adb8bbd06da9ec2c9aafe9a741fee417e7209ebe0efef62d81bfcc9216c0d9faa9d15b51153b706e41a876923b8e5ceea3aea8e061adf7d3aea84ee533d469dea8fe144effa71187face5294cf59d63eedf0224d6106be1f814a85cc23d605bac0e43d81bd819b686bfb7b646196888b5aafff5e3a9754abbf7b97eaabf91eacf66a86ad8d4b6302dee8197cfb0dcc5a2e47babbac9099f02fdc57eb78644420d4f75b2b22168d8ef296c9b2194bef6578d753d22f6091ace1a87506d65652e87567a9216482affd45d8da10a5593da100ae3b527288e0968aa866d98d755727b8d76884109d860fc3f5597cf3c1c7ee3a1e1b77c7c7ec23b9b027fe5775339acac4c2f076fbf3056976f8e88ec71c87eb27cdcb78161e673b37621ad1d4bf72caf895e3ea4b5fbe50ec4e1ffd75339fc0d311ef472f070f07661bed734de6478dba0f632fee48533f5f41897e3f41a6fbf6dbf63ebf66d1fb2112ffcb979a108567567fc723e53a71744d619dfc53ac8f8f5c2d6a9321e879f3c1c5c0e19df0f9f37df7b21f6e3f768d4fcf8edd3a017052288f041438e428daf52e128ec8dcfdbca01eafd7e423418fff3b67aef793de42f8ccb6fd5c5545dfe86efa5ba7c8c4f64c317d2976f1b74791c8d81bc88892bbf205010fbf335cfc76a5bd5b4dfb155fb40efdb17ee0be1aabd69f49a37227921d354970779e1f654978fa4a394792bd3e26de7855d59bcb0573c150f483479a46d8798c2b1c414f70b5b406aa0e1eef00ee88786bc848d74f3a9129e2ac261e24f4a3f9ef81813ba8fc3415e88c4b6eafcfd8199746a65e2f4bcc1bd78c753cf1332abc69db6d198e0197a512b5cd8156d6045bc4800ca6722aa3fc7ebbaae223019a712d5bf1877b0fdb6356aaf1853a762380a1a9788221a8c7198f6982e0606f4178c8e7d8ca7baee318f727af4c84401457840821e9c0006355c08c30e76e80e5851a4129004305471e353051b23c8603e3b18c3b5832daec0031dd9822738b15488428d31eaaec6e0836aaabb1a830cacc0fc53a50fcec840f7e3461720a24a256baccb01c12a55f3d628fe667741fb4336d2c59c349ee21f99fea94ee3d985659763f25d82881b6f805d99cf89a1c1af6f0bd2af2bfcfcd018b718e90ffda15d74d10500b038c2532e23fb88fd24bcd7d7460b7ae022ac436fceede8eaaf499f027509c29f9f3f1f71415ca840b80261d0c73a5d989336ab08f7c04d5887fe6ed232dadc8e30e2a95edde8e02ca9cd4f583beca4419d26dc0337a18a2574ec5012ec977b00cb609de3305cd0ef99549ce52cef71fee62fc4fe7d609fa94119df3678faef0bc2d59f7c2b59d0288d92f132870f7725a30efb7c7ffac2293f07cf99348c7c1c3cd799e1b91352e4745cbf92050d69953eba06e507e16afa3024522bc9974da4c7d3c77afa421154ef7d498312a7aa7cb8ab26d2355ed334287fbe2f9c35e37460d5fbbc4983128bc112bd97ff162fe4da827d7285dd74377c31be70d6f804e57fd1b4df5eae644fa37c1af5f2f265168d62182185f1bb3070b40d9eb3e73e0dcabf385e79eaa95cbd7cce6a503ef7e187ce6a02ff8adf79259b481dc905913d7b42e6bbb8785b5bbcad303c20dc7940228b179a563c2051be0aff54f9262fdc4a5ed8c9277940b0f754b730fca32f9c222a431c356dc4565fef7882912228b0398521d62ac626bde384e7090b4af5cca7b67fab4cc7537352dad1e8ae9bd35ffaa7a3e57f3d030d922feab50131a3469e069d0b1a663f2d1a0cba2efb81b2aa9cfd1c59817a6a94ec68518c30d95834384eb856a8c8de68e7e24393d21459c99aaae330fd3a3a3a3aa1253e27a55d6849680994182566117b767c959127f250975fa4feb9c8bf7eabfc7474dd06afadab1a769333277b238a7f994e8be6021a66d9cf4fa3c2d0929ee8d3a830d3a98d0a37284e2a4ba7f3c1081a1c27dc2ab2a4a1a890e8781636718abdd13526461e87e99f93d2aecbbe68b077baec470d0500a906b03d645eac0e5ff663021a230f149a466dab73181e4f653f0ed3ff38dd0d35d2cd4969d7fd673f61e4a9fdda4ba77661ba1dba8f9e767c754f4d1af9f9a6692b67a02e4262c5fe87a862d98f02e2639f10aefef9f097dffc82708ddfcf1720248ddad86a547b0cb15672b8166aee789c3b7367b23cb532dd9d7fe183476a2c72b1a4f1d517953f46e9942f892d79a4fa124f714d1aa63f7e51c3b9e34b685c87480de78eefd4229d45051259b3fac7ade6ce0968c8ace9fc84d5d32966b964cd1d96a7e693278dc26a3f81527bfa3777be65de7c2bb630fecbfecbe5e0f83572ad79850ca5943e64c82cafbf3e1f52621293f2bb65acf192dddd933d8cdb0b672ad79e4b109f8febfbb743c698c3ebb1bf385e97fc5c80f0295336623e586d63d2973c5289542a9548a352694422954aa32f954aa5d1974a4f1a75a5d288542a954a255289442a914a17b743be7fa9542a953ed652a9542a954aa3128934c46844229146251269442a914aa49f4f323da9341a955ee5fd8728a9a88c462aa51fbda6321aa98c4ca55189542a955edb4aa552a9f4cce528955ee4855d45a4af9f8abceea3477fb9d2e36813cb6885a4d269246fab4af7cca974a4d748a352a9342a9548a592cac7aa627a92c79565a53422b189f4269591ca275b0dfa8f489f8f12e947aff156227de186c3eb6772962a3c0835f1da6baf2b5ebed245ecef671fc2a3e7d8153d215c57ce4099a7a50cede3000ba1974baa7c4ea77ebdf64673bf41845e72957b0df384ccfac3d7a0e2b22f2ca266df1e90feeb312f08574cbb3cece7c7f0af9f0af0eaf5a251950ffa75f9da9cf3eb9cbff37ae7f5d056ae22cfeb2a126015bdc6ed6dd5b40f6d1f86425050ed43bf3e425a880c1ffab06b0494e3f66a6619bd9b00510a5a919253dd95195635e707a78809e868282b5a50ff90f213a1381ae5d2df95bffacbc7e6b7d7559b9777cdc71e838272ed764e47e8e747ee71747605ba1f3ef7c035821af485b10a619f3245c3766e5ebfe67d5d2e478b7c803e8817861efbcbf311faf95a261f9373be94f37174e6ed0faea08f201ffa5db0aaff29468b2faabf0907cb84bbc2b1f54bcfc7f541b8ca3f352acc7ebe8fece53b8d1a7be3c7fccce386c05ecac7b05f0e08ec658db3c1d928ce21b5f59ebcf263959fc98916a56ee5999d0a9eb787b6611dfafd97a144f6691bee81e9d6681b268032abe5246c2a364c7f153bbaca6f95d090572b6ec23a5cafcfb48382e156afd8d1b97dd8550b97e8817b58077fed0b4150e5874af0caa93fb66d4fe580c03e3eafdc9f7b40efd3bba75d007e95cd9b6a9b85f11bb34ad92bc87ec6a0baf6b1cfd65042dbe6bada4663b90d621f20b186ecc37fc96d10093008518d1ff334c81bbf1679dd432113feb1fa178a805fc419b6d209168e9046a3202821bdc80c37756e53fe6caaab268a3c19a541cf116b28a9a7419d0e061dedcc16aca3974e3805883ef944eaac70417d04e1ea511a5ce182fa187d10aea25f89e23f1ea5418f423dd377a3f711a6a2a358021a7eabd93d8a5e04553269d027a703abeca441d11722c1ad2a7adaa0ffe47288beee0554fa7c92d5a03ff7603e7b6334f2b86261f01f6244228d4618ab51a4f70f914623d24808d7d127041454f4a164d556dd1ba39ff205b4f4a30f656bbe8cd228d1fbcb9fbd417a7f8de471955e889ebeff1023ea7d55b6380e08d1935ea31e4883f47d837e914a9e84d2a0bfe813a241dffa491eb9856c6d8d1aba32834ddde67288e266f2c2aef4259446f94ed19b3c954ff248268d2abdbf5c499d6c55deff7a011d7d88b5f64657bfe186aea36d643116d6d3a811e9b591b7d5e4459667cec4f22101d2aa91b6291291442c1a895aae3973bbcb900b10b19a3e967ee4715df1dcc7615c53f1da88caa6c2e5d0462ff2464eaa686724622855247a86d220338b3f92f76347fe30aa6854455915899e37b532a2c7b81ca296a7da8891d9716b245b3bb05afabca7417f1627e8e84befad4699400fe27eeb691456fddb88a730d3275b18cbdf3bf4248c55fde31374f4f910fde84b5f287ad2631c10a21fbd26f27470ddad41128d9ecb41f2443ff2443aa2e87dabc8cb11f280444eb642530ddd0312411e909879a17402842b265b40a410b4c35a25675903838dba64fb10358853832f53761dd85dfcd48e31562e155ad5f0a5625be5def1f5fa8ed7e772fdfcb6063fb0c129e487a8f26b9ab76d8df18107114620a1048dcd119c29564e781ad5d3a8f668a4d030fe6ad5e610a7064d1f0885865f5d3e901d5be7efe01abb6edeae0e21887f8cd41ea28310014a7b075ddfa9417fd0170ae11a72bd7e6ab4b8c5214458631e3880ade11ce67b83eb7e7b3bcb5014418df18bb102fd53a3fa5deb2cc9a5aa7c84a7785bfc9fc4a95ea441b7d91ade3f0ee35fab1af458d36a554df56f55d3744da358c5053326af787de1aea28c91db71758c4d04f3fea5bfaec1e56f3bd1d0868d2f9c1b9bb8043b5888bb2343ee096365225572099c4bc03d45eaf4bec18e270bd800c1c8f199c8a668c3ec47df6928e8c8dfd37e9665d7fe4ea7c3ab39bff0e2204c11846824f37ec011d3833fd0619c47832e0123bc87e45595cf4be4cb9f5b65fc811b691fee6e1fcc33d19e00ede129cacb937d6e33173f1886c595b25bd3a7a3f3bbbe2bc64bea609e291641011ab20d8b6dd826094f51ef37226c9f1abd7315fde9d6feb60f5d98cb39e77ae24dbc486b7e0cf790fd0cbd48aca16ce203f6853b4802700f99fb4700ece5fbc73813c08becf46cecd060bffb50011aeeceaac7cececefedc617f09c03d708d45aa7bb4954384a6c6d45d45f1a4b698d400d45d9521a73aabb2c45e4a6c1f4ae58f7f6dd817fec50501aaf17a2c363a04ab7b3207adbde689983fedb75e9a139aa6c52ae5471ed05529a562faeefb7908d12897efefb1375a9e65db14fded7f61b86f8e63a9dcf317b2d4f0358f629b73b2f4cde548a2327a1af3f9bf51a51f7de1a97202b64e1afc637e387a215e93d0df4afa70a91702d1cb17cefaf2a38f5c8e1e4fb0cccaaf88de892598982223fa213332a26f08cbaf7cdf8ff9fc301e87bf78bbefe225117d8b97a4e6657a54181f7b49ba67f1987062a7c8d4c090e93e28e87f2bfb4386d0951f42bffb212c1f75987dd10fe9bece61f6591e07cbb70e435af9e83c358d06279743fe72a5ef9b34fac21e55f4dbd1d7449e463d27b45f6246467342f44bccc8885e5bae87e8133d7ba02f9c1748fb131059b77ecd1be279e661bf3ba2dfb23bda1ae42f363863831349a35cf8e747569d53a5aa3bc680a4c1ad271a5ecc0e9189381399e819678267328c2b41bd2e55dd7f5357b3cf3eccb26c6adfccb22c7b9017cee49892cb9129806bf6da6b99877dfcba3dd6ddf669980cd65c0eac2e07c47c66ec72e9d1a3461c7a84110d5efd04da1f2e4bbe60341ba4d16ec307ff2382f8fff08456e7a9eefed82563505b45f5976199f598ba44346a5bb0ff17a63f6661fa712c8f9f0b76dc3089ba5529abfc7e2c51f93749a40ee30dfa779c820684575e10fbd877f5aaf1fe2c1fe9d44422bf084f75524a9e6e799607e329344c22e9ce534628997476f43bf063c07079626cd4b0ab52ca4c30c6862cc261e412ce6950caef27f04a5a2103308002490ac400e5075690b9cad0240a12d4a841065d1a7e2aa8eeca8b2635a4df53b42e8a2e80589d0960d05c5ec0a6d09840f7171b52130a8542a150284402111d91e4d6e0f6248f36b86d5c88db482cc09ef941fdb27fb920b0ef993ded04c40bc2d5fdb59f1fbb63e69c1f76a2fee4fed0e1757e736bc8974f50ecb98631a830e6880dec8d1b66f6318da22f5fbe8d46c9d7a837f268835c6c900bfdf4adafea1710aea29955eedb366febb54363147f63eda39ffbb147f5271ff3e2465e0f5d8fa399977f474041ec2d377713ecd171ec0dafd9c4e2cc26e663ca6d269bd99c9f3de6f998df1f67f6c39cd87287c2997a7dbb2d124d8c31c618638c0ed2b0719a1b28022b66a06de7041afea9533bc44c192c9723c66b596bb670ba6fcd97d60cb96da7647c38ab0c4fc66b1b112ac39362c6ef378367cca859b6458d5b09e843d9eacb48e3ccd938ad647ef41a67fecc9eb397335ebedc6253495c4d7c3ec36b250d768efc4fca2f8caa48a3fa6bd2bdb135c50bf3bddcef43f9c5f7a1a9bf0f2f22f2ea5e6e717ad937a70fa517559ee4e9c30b8a8aa4cacbc875e4e272749f7933bcef35f65cb24fc77cc9f1d8fa7da7063fc6bedf66783fb225a314a1a0bfa46c7ddf36f7b287be6b7ff93d766854455554451528140acd0cd4fa6c78e672c4f81e0c68c0a23e1938ccef9a38c8f84093d2aed5389e922cd65f39e683bc10f4df6b5aa4d9624d5445d55573d95c46e4171791abc8a5ba6878342afc9e0777c30c9a2faaa26ac61755cde3f3b82a11d1d149c6894e1e3f7c71a92e9a566bc68cff3c1f27ec79fc405bc96ba44de2e0f816f4a541f9377ccbbbbcfff7291c5e7e34126f3ae2106914f7f148cb87114995d1a6ca6fa491a22aaaf646acb961ce9873bef7a16ccd393f1e3f9cbe953d48301c4fc930cd8f3f8d624fb591eb71f0fc4774c3ae1a87c70fada4713cd54696fbe7a5c11b667cac33be5004f57b1c7d92e179160dd362b2a4a76850360ecef7799406a5fca86ad494acecca6c78cfc3f1f2323fd992acbf24cb861b5eb6ae22979146d9f0f2af233d6ef8ba185ea4893593b66cf8a2aae543d972f930aabce0a2fc8442ad4671517e42db835ebcad2e5e4babd5caa2aa715a2d39a2df948f237aebc96c5e734ade891161509053afd7b6511b472ffaf8ccd2b66bd350fa317df69a73ff74f8d3a79f3f7bd8631813eec2a6a8c35cdf8581b7602feaf58b450c7b91b7232f5ed775619a171b4cc2532a7f6da6d754fc4d98c74c3cae9a5cd2a871ba278d7630e2bbba591d9c9c7a3d6814d320f6a02ddc8f68f0d2d8e6f2a7af611848c61e1d27f14984d223d269b20a8598521b9b23f82f6d4e4ddb481e3369f0fa18bffebb8e4615502cfbf961d7b4125a7dae2ffdf55da48f340a49a3b0fffa4cfaeb7acfaeff6283d7973c8c590e73bdf619017ddc35ba5e5b9084b13cb58b3d8d0f9116763de58498a94fc22826f2913be98d207d213ffd683488bd0ffe8e68f0fa30d0e0f51cf6a12dd66bfe7585b077d6dea06914c6fd2ef7bb9c7bfe4d40d4dcbc4291974061d20a77a749917afd9504cb51a2bf1e879b80f0f5d727e1dbb5dae5464ca051fed7675eecc17f7d9dc3750c26a79055abf146e95c50f63cf64657ed915002884d750ea3bda6fdaea6699a36b52855c3d1cb5c10a99b45dd55949baa3137e587b149e5d5fc6262f3b5d7353fd6f9854c54ec8b2d6d9b235383d7ef2a0a911adaa8da4b1b8d0aa90f8dd2b4e7b17d3875b46dd2f96944b46fe6d1a036f9f713a251d7179f07f69af67bd2ae2260cbe5b0e135970b8b9efaee418e570befa25c3e6cefc3ab2ea86ea8ee4a8be6302f597edd653c8cc70b33e250b2aa945c8e15172fc6cb532779244bf668adb1344143fec130fee12f1a943f2efce9e0999595e71ffea2512ccf71441bfc72c8c1cbe13516cfe5b5f562ec4ebb0d1f76b54d56679dec8eefa065d91631110481e4c63e38157b97d7b64d4a9dd8834d2771273e893c114a64c59ea9d364c57111c35cb077f98430b6b10ffb34d8a837525ddefb0b73ccc5dbf5629c3a1931076f97875016895e12f073f06a5e067c6d234271306311e5f2f2a51216e57d8c0f65134e1fca27741fca2a54b9c2825a6179f92e9eaaf2c738c9f06c8a601927c44c5df99567f17eeaca17a5f2c756a8913855971f5c63bc90585dfe87a8ba78ff0dca7779ef13c235c617c6c73e1a0d820dcaefd4a0dcb69516e534508ddb38c61cf1a372aff8b1c69317723d456cb7fb5d965de93e7ab4c1f8d106e5dc3952f95d75b14452fec16ed8a747a34e38fa646af0fbce03c2d5c77fe4b37cf2577e647c2f9d556f5c3c5e107bcf8b0bf2c7f0e682fc276f5b90bff3e882fc323cd382fc3978dac57b7968eba7f1f688361825490e2ca6b4ebfe41f0f39859bb22d6791f9f764aae1c467e8cf1f1b797672e870dffdc83f7f2bfbdc1315ece4aa23748bf4bfa189eb7ab437ccc03bd1cbc18c3833165bd0806ba2e17a6e32bff8f7f8cf82e5ee8f2def3e9bf7e5167fc572d2457fcc33f72159f7f62b4a951c577f72bf8c7530e0667f994b6f12ebac6cbd02af74253b12967398c7c9f26b4a450e563a146a952fac82cb0ef1a157adf35487df4782ccfe3aa8454f21dcff33ccff37ce7e453f04ea7f764c82835975e3ad969547ce279cee4e4f9eada4e7f7acff321e38170fdbe6bd49b565c1c29993c700483e20f65dd7796a7603eeca6ebc0cf59397c373d60475807f961575f10076c6170c0e1e5121c70c0013bb2e3eb0c57c382f2b11b4ff194c58e700ffcc33ac8972e40411ab686fc109b41955de5479a68e35d5855186ab6a8d2a6ca58a4e230e3c3480487a41ca864acb6854dcf32442323000008d314002030140c070443b158309195c10f14000d8dac466c4e9b49b3240821858c31c610420810002200042303690300a424b5129e27f026d35b80ccb2359ab35245c67cbdfbef362d2a4e0b363573b93eb04fc5fdad2ae8ef57205706adeb73a1927fe04b2dc1a21d2c8a0346bb2c79755bca8ed952177b92908c7e6bab2f0424dc3a66778803f8440cc6ab4a8c3ab028b88d7f8a863323435af88d56dbcb87b717e18596d1e1d25a50591898508d7571aa22e816da829561425beb4d5899b4e24f8aaf00218740a583d20cff0250fecec50d4ac9bc109071ba492f8c074775661941dbeaec62fed12ebceb2ada734349c7827f08f91d50fce3979c95a3b443a3667622322203ab6d5a4ff88fa45788b2829fd5aa48ce3aff0fa8d45e8857a98b78d4f9245d86dc2fd6aab0175a4496b18b520ee8cd64c999493f44d7a9b380c10fbe10ce207733cf1863dd91c9ff8dc86fa097fc7a5318b24063c62e2f9b44148b71d0968e9238a5bb901f31939f91d8b254fd4c01060d7eb12ef7be5b2a146aa271cc45ff278cdf7fb83862b338c850d62fd7e02dbc1d240dfb98f51ce2b460b30886000cc4505c1bed79533a936d4b2a75176b94a3a38fbe53fe8c9fa23920db29c34e5dce19121e98b3ac6f43240ed17cdd45b8d72cf897660f6dac55511e1fed43c87d4ed0f032f0a65de432d561992c5da02294e842675146a344db845e6ba2ddba310d85424c939be6fa45911570e9645249387b8bdc2f317d03a536d2db57c435a43ea2b191829b916e4dece00311abd262fd55827a23f2cec656d85433b0d11ac9d4829213a22315c4d131e23184979672acb63ec9a109f2310c2006af9ec14fee5a270f038754ce0a3574a4117df802c7ae27e382eb903a17c02264d45f962273563e81ac4df69ef9ad9b4c2b0e7e8c692b2514981f06b958409ce47bd919edb9dc6a6bdece468569c986e73b536fb71559fefd5d34a1ca0e75fdc7488aa95b71af12fffcc9443f386df67d4aab3dbefaaddc52d286c03dc0c85bcd64cd53d205c399ac7c8c516c3a9335b597255a144d964bd3b3c6b8078f7c45dc799b424b08acc9724d6ab22eaf92442a61283f2ecabaeba5eb8eabf8e10259f1d6e8a3bbbb42e5fdbe93787d34eb07e7997fcf952628e90a58c16a044e8432306423a42c3702307e111c786b345f0371a04c4b565c6118a76e9b0022f92945226502550436700b2aae424f7f9db13d374e9951f0a5c8a96c14a44481be5465c464e5dcddd2900d50dd8c782cfd90ccde8451010e9a853dcf9144d5b309e69ca4c8126bd389c20421908ab61f3b3a15a9a5d9ceb28a8eb3dd39e3b7ed41805d37d0ca513101b4ed8bf611490a85f7014450a99492562ee07a4331202c2b51dc29f53113df5c53a10dcb554c950a84961019766c913224365273a5b5ac97ed3933566da56446caa134c36d59aacab915f4231e3a72a9ba79a174abeffe3f9b529d8c4d027ac4d5ec050cca1c807a1d395d1874191d3252dd9584bc213b44f98ef332c8f4804866faa4ba75dc822b5963d51ad7ab9b481f13da8c98df8454ef232c3f60e9a784c809cdf2db214c56bbdb91044aa9745215c6b4fa59f335565fbfb2ad14ee122bf18f9a4d885f42a411235608dc3f915b9f7b4144fb1ce22d2a2875a9cfed081e9d4068e40993c069c554918a845fbd510ae0d40ee1c82e2a660a7b7080f83943e62238e4dc2f8b8b35a999384a23100551710921f7459215c9f4bdf403ef38faef6310460282006d71dda8b7f91da59833624a9b538a25cdc4e6bb6840f623a438d5ef7745258c507791ee9c989b7d03d58373d1e244747857027f4014632af079fc41b9083537e48d3ce6ce41168f375993422f921f87ceb28c38bfe6489ca64576e9641369e31264d74e639cca2e024032fdf04ac6411cd1f1b4fafb819696e4d19ccc44df20d7532b3c6054b2e2869cc5ced781f142a7fbf7acf1275631c6cbbce3f09d45be86d5e857d435e42de27389c8012be76f7d792167ca1e3259f18b5f716746d051059ff90a4b94f42529a96f2c80af123050f41c9b2f60de826e83aa6338102a0f9facef43d388f7e54acfe90463dd6bcd5a2063108c389402d26f751f6bd6b0b5a538eaae4004aed13eea57d2c3d407ee46ab39a6e930f69d968072d1ba53d846c41d06746dcc43d38e6330eaf3ba16521ecd0c1a78d54c782b3e58adcd40cda869619337c90db82e132760f0de0cb4eb40a687da551df3a05d4fd588dab136d263f736b66606eb572914ee9bb53ff7df62ff635505f2c624289a64a317a78d5b6a71e8e02be6ca715e17c7228b484fbcb4d5c5cb77a7fb9697a7f4c2e11b32539e75b10f9770cceabb989640392b54cd69f1eea74caeb28bc5aaaf4465b4b9dda9b6b61e50b539cd51978e00c16f8c72ec4753ce640468f31821b23da7e47f4063798397ee5c512856dfd901defea43a6fa03865aa96881a6646605917e7600245a42fd163a2180f1b0d0e4fb5b7ef418610158db728285941bc9e4bd7ce7048702daca6cdff55ec05536dfdc25206a66b18e6f6c1d9c987fc09173baec42dd81d549b8af0c15af89067a00a3cab54168f4f0628c5a26e8e43268e815ce2c61131a196d7b5c2cf43f5e9b5c5d5ad9f2c0f39320a89783b042b3d785fe56e3b40e351ab89d1f6749ac75b2dbcfb5a6ab896692422a92584ce6f94b1df21153dd681106cb9b6e3d25085c2968888fb2e51f05b710fde4f786c43a376fbda4c76e312c4ff1ea151a8b30113cfa39582bbd9f2ac34dfea0d6022b62955d8c3f0006fc4e1c93edbf4b28d89757c3a68540efef7045bd48a22667e7cf9424682a9f5f581c900868c6954fb516bcd7b776f402f92e7666bf44b52ce7bc68a08616b7686bd41ad482a8d9eea248adc1266ae6282cf621c5aa35dabe346431b3dae45aa919cc8e9bf0bedb281969bb51c8b66be8657757c619420da4c520ae373d9f27d21f8e71ed9edea409d60ec85bbc5b3cabec10a7fae32460e4c61923a828a2dfdb30ca5c08758b67948bf16c4368224bf2577fef61b049e88c2734de36c5ba33c339066e75015cf4f577751a77c0acdf3b1d25d41078373bf01d7cfc81c4adbbf59b6f9ede4f7c16a583d4049a44f7671d135f1e49f3c39baa1d0dd2ca4cec688cbd074592fa5272e91207fc7384e473613d540a321c4d837be631234e452d9bf43939fcd21a07c64f048b75503742df27d37f82cf0d09c17363a8fd900d868df6a106793963942a0069a8e43ab7682dc38e45c4013194500182541f10a3466b8bd82c67331d25136626382e07b794656362100256a68c520c4bc2cb6c18c11089e32cf8ec6b230ee80b98ad8fe0f540e04e2d8fcf3c95e608551e0e149e1150854afaf0904dd30f11dfeeff1e2e827698eff3c47ddd27c4e0be0b4e45e1fe848cbf34a6e7554c55abdfc2c9ad5364dce563e336388eab57d8c9e44c9c6ee627d7fa23ffd3c4627d682a226012a2674198e36deaa2bc7ab2d0551f1700103cc4bdf38530513c2ed9776386804aa6510112405aedd515671ab7ec81f5f064b1fddc7479117b2107db75455c497017dae22983f186d4064fbf183a1d3aff43345554e3734955cf86069114c562df432d02d858702ac464c252511e2fee4bce751431fa1307bf15332dffee013b6d7e98d3f8ac22b68d3520305fb20daf73d80bb90c4a1d605fcbf90b0ff3f28cad1e6a99d3e46780febff866a854623d00bca328e910482b611deaf1663fd3d8e607129df0372cc5f4a7981863b4ed633c20e8a2ae9855341598d047bda6984a91de3c402aeb158bcf067161dec965283de967f2e784d0eb6fc0c6c157b7aaad0a0954b8030c4f2060b758d86cfd3ec0286048704eecdf7b038221b8cf4566663a7b468784bfc298115e0ab78047a7d9f45c1f64e33dcf7d78c7ee90f2c0dcf76694210a6216bbd4a28ea8840b08594a209b9910523271ac6a206ceda9472d35658f246cf2a7192d722d2455a94a69ed7050127b44d8edfe3ac4b2b693d85ad2e15a9809a9865369874212ddd846f5f7002756e8e32c0beb7d387444f812e592921e1504e8bcc9cbe5cf3ad2208573b03cdd35b063426332960cde9d86ffaa19090d92c5b9348727a2db30011b85297a7b4b0eeeee990af335117cbae0797506698d4027678659799e27d7172baea02ccd25d5998220ce6114c0321910be3202640c8f0d82456c2f4f1e0b22e48b72a8a59e6d404e7204af2008a62f94d7ff34f2e5e70491e39e9f0184bdd8347f5188d368ad382335981289c42ec7f9ef13516a5ef7cbd20a02d142f326d714ef36336ee6f0f0ee4ec52c66cc42e882d5c3277f1efaf9b97240073199c178c0c25fdb6195b66b83a20efdbcbc9e38aae76fd5fdc48cd4583b386888ae22807eead60f44bcf4352afa69d6ce6ae33d8d9d149deab2a22edd011afa81bdd2e11c262118d074daf1fc0c17d09b59dcecbe6b6ed4afc669ea20317d35b1c871d5ec8f532b25d0f773336a5043f2dfa84bf026130d5f2609deb6a4050a4ad4662b202328384694e26f7b04c57d0836673b745b4fa69c4c9738069a9454b56e4ac20c3fc67d46fd0c0bbb882e3529feff5580a13c043ca2064f3af84fde6df634331aede6cba960d665a0764bb9fb4dc2d5b186320db1a3dbb33dade3aecb1e92c3c59a4884be4af0e75b02ab4c90dc5004d6d982be33aa4653f05e4c4f69045ae316e9cd6a607f3eea4bdfc909b538bfd338cb8029939731fd377ca10868737743403c59cbcc7de6e80d9616d6ca45a78f707cf5ec0afcc88da93055957adf9402aca4460c016ac9ff9c7c20a097fabf0c9fc99b3ea203a4bdcc1d58a59107858f759456068d438e860bc5ec07b2180755fd71223a34c1fb0e295915594385a10ab246b97a331c70fe283a64c7a74053eae1aa453edc7aa9d679f810ca88ecfc4380d6698b88ba5817d52ea34ebf8c02124986572169a2f85e3e0ee8bc33c1ed187b2662583eb84ae9a5b7503675f7751ec7ecdb7c4b143b8602a8e9ddefb174e9475d91120eb87a33f466c33bab8449ae197187e69de91bb4184a51445974a11c0818d4b318f4fe3b230d4fd453cfc81f4b76f1493c70d11f07362fe8598803111a4420aa1999b175a568383d449d55f6bcc814a5c5072668b2f14072b970f31c1515ba409f8f4962214404ee66b3b94e40340d7cee37f91492d47e169f383fec8699736b7c1b575133278a713aaa17934a65c03c7b4e7f7f88206b488f5280828307696ea92edec7021064f72e328dc2fc7fe95f7b5dd4f0e0d48a542db8f4998c00e5df6d63af79cb9c66d96e610347d95fbb8dfcd1b85b285431d59435523e316e41c6319512287ed96c709a2e46f2ef689fb4cda4b51974c8ffc15c857008879536b58b4296143ca22d2c27bb1ddb0c41d4735a268dbb84daa412804c24f9785f7e8c80c2a707d41aec62bff39ea0441dba5e584dd0c3181781b862439e0badbac3574d25f2e00da6223e2eab7ee3e02284de2bbcb731b0335e88f8fccabeb2351a8da7b5e7e212cdaf47609f28d3882d974274efb69172da71924532c46d638b3f0cf1e4a1a9cf73bcb5322c7d2793c7885c90647fd41973d7680a0510f6a346dabed2bcc6ec91783136e31aba0a19c09ef5c59abc128a6a54ff59855a3daaafeae0299bd54dfab4cbba6b28baac8978041f98431a15ea28a420d30d69c45a6721537d4c32cc21119405dfa1f2d21fb775f15f3954721533c126d1d70a446fb52429097229047a57064f6c55e4f4d7ff69fc0600f145e6b876e19323ced2f69ead2088a68278622b874e586800907568b76424e6b2489f656fdd2e414842550ef1d5f1f7f9d61ce4fb452b4e7c98d2306d9ec2f6e2b30b5184bb1ca1f3674a44e7b860f0081a802cd785449cf3d44d0f07d3239ab81e66ace03433fb3c1f73997faf4d7507f5e7364a3cbc58a14106ab2464a631c8999fb9a957e6b8fd6f0b84f303a56bc651057aa500661fa3fa11fc72236de696d0503be02e656aa75a3c571423dadc3f295a58b760b7ba584108e895c4adddee30d7f8ade29391b5616a2d436aab01cce43b15229550090305b79d44d123dbf54a523919451f1555ca4a9284f70b54f13695c4b2dc678566605372ca9d212bcdb930e9e6215ed20070ecceece0c1480a9faa2cf4d2e45dfb18f90d48630b28e0cd05388088a6dee79a8670e2bfef9a79b8e9b2070e6b47d9faa7bab79438646ae689838fcf4ee7b50ca8c348a04d0ecfdc8352ae32e9b624404b685aa348450144a042797c74e21cb8dcec82a12f3c170e70a232b5ca29d00b4b20d14a12a93054ef7e1a09515f214e9aa6c9cd6f24fcd134551d3bb279d8f3317e9b63a24e3a803844eaf6eed8981326d8a32c23f3534742a24ae10b959130f25597677eb475c2d1c6aa667d849d64914a6fa2a860715b339c0e4480294af27ffd9d1c4ea5c8328ad813ad0cf3aa9074e52a0b8a4384a94fa6cbc9e4ec630a572bf20c8efaa146e59b62c461f159d7337b1c82722d6d732e82f4b672fa3fe92beeca3bbb0e4243b8faa76127d791097bfbc31753fef709a79c952940ea277fb04c193934e0029b2331adef95e2203c5a6ec58098631084d6e4c9388b4a54b2cf0905064ce84398087889d6e2ad9b9d298e7148617f84642393d735c7bf206f08cbd1a8c36f124c38b05546b050357294627b294b4a746fc0b0eeaaf2be6709ad319180fc3cae3fdcddd2ab054fbe31b2bce514ea46e4167d80421fb030d2f2077d0c1b2b9859f6c417dc55adb0aac9403b321d7ea12cc6fcf06984fec75c30de323daad62aa6e3c4fbe63da8e2b664347a5801d2d822c2cd71a22aba24d311071d05fc76dfdd0a15917fa145b6b4901e9812cb2bf03174a20f71f8f4f1d64b86f07e5f4fe0bd4b2049af547fb97074ba5497aea7557b5433a6fdbc0bf10e301394b0ab712cc7a3f4b3bea74cad40eafa5577cf2c2aeb50f3fd2a0a6eb2e31c853b53765205c551362e935fd9c4f2dc832329cd9fda99c445270a0c037f65e4ec2ba18c2bb6c832ab15474e7d717b512b81e06ba258ff6255505316ad6100a5ac36d6a693171d403e63727b5c4ddd3c5b0a848e13aaf144ab8ec750d717adb8b6d090f4d30c04db3279b8e1277478e3941915f73ca6e363b4ab34398c4148ec2b74041895debf885d8c075337f45fe99c9b32b6937e6f7882d00d3dac5ef81eb5cce3d66e3ad9008c57a86c48a45918425b08ef040c148cf3a42bcc357319e25af507d76c598144f20ba224d0db51994e22b09105d4470660c196f0b8487d7d3ab19d81839e6574cfabec50271b80ed6eae0e2214717e0f31b81730dc2dcfb56ed2252bfd79c9ccbe8529ca6eb690a9f0f0ccd43370c8c7052b02848ca5557e33f7c1619561344957bd37b60f9779ccdeb066d4d268ba855819bd04fd1d7af7673042f31269dc0e8d352983ed8d95c02839e1c17c8b69be125ca46fb5eaa1575bdb0164d8365c575e5f303485da6766cee91ca24823f044ae10d6dc2ff1a1ef417893286eb70246c03c9d584a17c25dcab7d88f9722776203fc062d697040ad77e78109b402090969ec179320b98dcaa0df64bf19d250f0a625c59817f3914815593c844f3e4bf7439702761365ad7c0cd3625b8245602ccaf3bc7b0ea1c40976865dadd750c40a9dd54297600cfd3d5d7eafcc4fdde386f5bd6885e4da31bbcd675ff64bc3d47ffcf6c015a13d26562f1e103483fc4959015dc949a3592829e2e00cd2684c2ae2a07ccb617dae3d6db7ff1641670d32d862dcb49f028254d29e44138f9ac89ea95ae858652b96f8ed0d55118f7feeae646fec99b65a950070793832144957214cfd793473b9c440268613eff1f4433b5500a819810833d76be6c2ce7b1c48cad136c476f19782329080ad979807a4c5e13c64364c0933b8e8622549363db8674fa8b13df4e6f35f168260c32a32c83773c1b098f824a5c6a9db0d7426ea8fa4446dd588da5715acb8d3ca3993a98747d06735e719d58ee9126a2b9096bc8f4d37b02f95e0049f8f57a71a26bb749c9d69862e6211e2fd5b7b01960c6235a4cdceb068a69e1955344f2de7f01227cbbbdebba217069abbc039c636515466cd1ba2e44a0c5e63af7e4899ba6fbbf7f1f6df8bd74ecd71a9a4ff7f432696038ad86eac4781a06243f8d34e170add7d5e8ef05b6c0bfd36b845b18eeb64691acb5f3953f55df15199e5d7ccd49af9a0962bce97c84a74fe231807ffd58d67eaec1c3da33f741f4fcc8ca3449182dfe81fab41f0c55f2c9e3ea29b35bd3ce6c7ba3b9643f0e8557c9173d2de6f70a820804b1ac20b1a968edc84f64fe94f31a057adffabc4c0a2edbcb3e93d51f5ff32511a441ef7c4de0f3491341234d23ca7cf524efee0212efbcad8696fc5ac24986182cd93efa2a1a1e3480362e1b7da2f20e3290b8bb38a40813762390c2b2c8b73cf8f050d7755a27d58994e041233e45e027a985b38fcd4c484426dbecdf428c5dea16367333b1fc23fa5ab3c27c3eae465c43cdb6ac292e55e1454421fd7818aa36bbd4084844b8f4665f816679ddf424d1187cc2235f62f52a5009903d731146459c457114aa76f33e1ed04821551ee82dc6a9a230bbfaa841ed14f152e15f7a9827aed975a22de41773f278ce8f31ace8bb9d05843e52ef1496f806824a97db5aee901107da08af0c428299f6988db19fdf7580e49883a137b9040e76824b447cbbb735d008787aa1e864ad7327540e7517e27bd536f746508df50f8f40ca70a0eaffd093a8cc6216e82e4146f29ccb7397c1fc0e0263575cad57202693a045036baee446b691be036d8f42cc936a308815171f094a3fa03febc29d317e0a573daff44c152820ac064a58753b0df4cf093a19a3c8725703469631fc6b7cd1b96bb1cc668a6c51504da9b6a0d6336616a2cd02983264408d686e62de6d2cea5652a7bbb0c5a4c71bfed3aaafb9d2c09112e9f23467b48b145861ccf4a32395fe50abea2e9b8d6b375c571ff581aa9d8fc96f761d632e104282e1c226096456094f57df9c504da1274b34d3cac7455991c510f887581ea51832963f30b9be39fe138d7706999fba1ecc830235e720e326567ff9cf62973d5071421d243ef42ffb92d4ae723d871faa8750bdde16cb6838f6c893148dd3e5640b606c34253db671ff0124236b8a43d030a09e5bde2abfd7c13e091cdaf6b27d4799c5bc25184ac4b6447e135a619d12907acd51a72c3f77fd3d82f9afce0f5e8cfa4847ac4228b358b9be0146c88f8e06a3517c3cede472e925641602417a2b2127a9aa63fa06fa61e425a855766938a7847ec4ea5f49785d566c205fc7c7848352a2f238e6e97ddb365476632afabb360a591f1bc2ac85d899d604d91f556620d70f53cb114d3872ed56a65fe15838c404d7f90532e58780a1e87580e6057b92cdfd5e8239bf29a6d95c2cc10208f46e2292d5cfc94e6703b4416c93f05b68bef6b7d3618528fe8011683ef8925acf1ec029919d939febbcd6c6735172061e011d73d18ec9f5c4b559270a0e88c78f2e1a7cfc576743b541f21fd30053951ecf2fd9fbb90b9bbc710121b8441cbdb3d69d4bd2d856b44cb7aa3d0b43193f1a07dd2e070faf2476aea8f55f26e74138c49a828fd14a7c39b4e126268eb55378043443f3d4c98391ad401ab62c8ba8555d962e6c530de5003c8939c1080b1bc2623aca48c9e1079da33683e4a8b4d2b16aa3d2533ae88c687b794f04d8b008d042e863916389541b092f0b4a9486c2983e0c267208a3f06c286f36b2ed445dc20870aa12e2632179b61cd1ab78efc07ac8b172c801549293ec23ccc9a89279cfeae66dc835ecb78a70b5b5d2fdae68ace37f457544e122c7c2a4c560efec20595f2d367eece94b84064c747e3c442340d3f52c816ef3efb5206f3024d736bdb31237b6bd96d679affb315f8adb30552c87cce26f31fa5df552d20282d7966725e3af0e9d04a3ecb2e260e2f65f10b9718c6725c06f4bf5446573c32034cf1eb2ddc65f7c8d3e1bc1b3d2a10505a45806bb48d905e687b633075389bd232ad833d78f340e009467696850cbbd034f9a6b20d152cf38bcc80dcd70a7ed4cd3990a8c6435c6450f902fc6a9445086d875922b83779486d151b3ccf0107dd98616db824b8034957cd972c6b9545495fe6c2c040ec343b472d44f2d79da9a6725b1041b71e59f071f009046bc8eab891e93387d14437c37d63438d55b48134c96a19b518f761f8dbd55007e2ba20e86a9390bdbe15ea1e30d104a0f4ea76e2e76979bd107fa0d84a158c479432b4b7b5c5ecf7588140a66756615fc10859d183f425c34492647e48310be26ea262a62aedaefa1f126900ab2c6f2005148bc2993684265ccab22aaa4920d5cf62aa7853469d628526f2d0d9d88e3b9a230c0c99aa8a6273a5169a56476889ff2cacf2075549d8a1b1daea59108759d5446a951b911ea9987a5ea32ef4231b3eb4f5053c8933791447a1bcdacd3e498a72c9738feee32f1f61ab3f976d9d1d99cc7dc4393758fd90d1b727604c0b81c38b5604f9be4347e3554df3a42f96475dd5a4b31e156c1376453bc3c46545ae80c153439788fe1a4ae8e14ac7070b91d57aa89039aabd09a7c6a4c7e3c9402aad81f5494085e268526a39c4b843b45d5b6eb54aa611dddaee37466406da0a1731cd20e50586cf8399169ccce58aca7ab56757b2dc499e9657d75b9c9fcdf5af654e3a501f233e13173911a00a304ff7ccd5a55e0ee8dd22c5cedb9a613f7ccd3efeebd000a30f0885f996995080ae989879fb7acb79eaf527385ead8ec6e9052163ab2d5f7ba11009a7315d312f962656f83ec769012d5fb59c7b7308f37c062141ccb938ec73941dfcaa24614307a95712c4426ac26a84049d0f933c524c2a4ac89fec3d09e25e5f58fbf046c2325f754a36174d4fd549d945b8d8b5478b84325c0bc44796b23e1514197536265ca24f451d919d17cde8cd6d4d861fcac630ad03aba4662e13945e50530ac1985435399c668a7af67f4e3a9851f9deb67e6867344a43eaf88f7c66ab9a2b81ce9c40da880ca9c58d6a3adcc3bec1b2dae40bdd3c3b471d21e8c8fe99e039942d1183acaa388b1bb0916e97076cf03cfb5ee1769019862fe1dcbaa23ffe4e91711663911e271aa76fcdbaa16e5b0b6bb8a21216cb4fd8a12741c660cf64fac3fc1c09e4d6ea6aec1b2996e4a0981b0d4fd830795acf1cdc18d60749eb9ea3906dc48558a0dcecb94818c52faee674c607631b39ebce6612ea5297a9fd083943168735f7680c6439723abf8d7715fd549b16d73c35ac684779bcc3dbcb20ae6a61d4fe54d24597ed5e161c3764a22cb4b8f1c551d837a2eebe5354961b4e46d3b876dff07a5ae89bab3272c1eb91cc435013810c408a83588ff103cd2a64fc4b47a1d2db3111e0214deca084c4a7190c42e22721692a8842f4d52829626c64293935fa68345b698d4e8792c6b88cc30940002686d2fdc4cf7514e039d116cfc12b03eb1029da3aad4b5f74bf40108ddcdc455b9d0f692647446a0f3e8f1ea98252526429c2e918c516fc0381d964bb0162bf3c71c5bd90e7462280f1f88994083c9ea3ca94264bb1213e4842cd9c01eb960c80626c04dbc805791011509da8cee34e72d11ebd4f56303b88212f24b0ff369e67b9d221edf80012287d3337acb5d99d35397403937438f3a6333e12e796cfa899202060cba9e5a2c2b464491a722732c535750dee07cee5eb641e15d13602e9f116e54fa11f9aff736dc368200a59e77ff1411abbce6e05ab7d73ebe680bf9c548177d2f7b691406897317363ba09729b608416dc0db4e4b41eebd90ebb98c915d1c1fb12886b2585591d76c3a78e054521408aea5638678edcd7817915732dc829824115e284a3b7d0a82b0d83925d95262196a9550d03af25361c6af26ca1c63b462858e98e621b7a14a67e441016e85b177fc6f172bc9eecac07eacf9018f0efe2c86760da9593e8971150c7f883108658346934002471f154d63df32ec733ea900b09146c590145297d17bca0b73215961876d2ea9716beede08727e10be5595c99436524df874138aae49de7adaebb4f4a3fa486e1e782d758be3c3e3b3460a101b5b4ce6073bc5e281d63a8df89acd941ca14a38c52dcb0caf181f97f72e8857e0d336d1c51c632d800db9376458d71b26fac4ca1ae36b27455b8c117eec50b5271147deac8cec90ed235ce18fae505856d9c2b3fd085882f8f7ccd584c86581a8e8759c14fec06c552611333153c0ff07ed09c7ee8f56986aaa3418633266952cb8c05863be8add548ef9ce8718c5f1650506fbbc91913d83cb90388b72099016a0848b4300672cfd9bf4a900f2c05020415bbcde880f875087d2126f679e709f2166499437285d0cacab5ee1e7b680968169dba9e25873e5c5ecb941ad8a16ce88b3b6380eb3258ac7d5e11409282249697c7d459f65766aa3a230adbd968a82934822983bb45fa4325c35ea936cde5764d335ad6017c0511689be6d1eb8cbe974ac32e49fd7365839e954b3c151fb21579b46b0c98e45cf86ab9f9302a88c4c0938e0911ecac9ff7d888037fe46cba506fc4fde1bad3f101f583ec0e2b552831efa77fc8b03680b2f838e878065920c833accdbc76d1bd8a52892666281c895f9025d85ff49099c6d64237291cc7ed659a616d2e159519f7fe1f41a821847031c1a72f9ef712a7dfb39e2a491a934b8548024260b06e0d3536e49bf2ec8751a589492a2bfc55bbd53b56afdf9396589d3c282297dce350788a6c09e33839591642c44fb942d18cdce0e2b1218a0e49636861375178862c5bc8cdf50bdfa3193e772080430ef15c0f87997fd8e9330d2fa2d3b33eafa0bfdba21ba22a61401dc70a1ad93f56179a8c4143a9e568b388ac3fa84553915e2710db1b55d3f2c1dfbae010bd73319c0941168840496b96e4914012837a8f027001531a61adc923ec7a28a3c78e2fe1c3dac0f09e6f4d94dab0b6ebe983b8706954f91c5a30601e88a920d3cce1145b8f91daae115e1254936acdbe3b703943f9060ef76f40f4e52418935173da29922f555949783dcf93d5073ac66d113b68510a59d0edb2945633c27fa60d4a34d42ff28c2082d44fd07ce8c678ab2645a678092567869d241aff70ecff8c42174c668ac9bc091e41f5e532d035602caae6942617cf2404d9d868b7225ccc985d8e1a673647095e3f89ab3a8399961f50e4314db832cd80673efd35a6a284fa628b3652401b87690ec0c540b561427632cfaf649242f327ee50682cf75f24729dfd6989d2d6b9ce406691a2a5461a302f9b017ffce29e1e9bf3f58a4bb388d47d65ec35df58a63f39f102fd6f5a963990343d100906cd6a62cbc623d0ce7fefa0179b00318ab723a7787abf4bfff4c475ff9dabb62e7cace4c1eb6c3a681cbfe5cd55809aa793129c1abee9a525078e9d97ea6c9c3c53fd45381eca9a84b997a8e2f29ac24fcd613561cc23eb97df03d5d15b32e901d1685ee27fff29498838645c8d9adc90975901dddaaab25b3c308e20d927ccc6db3b9fc8860723e9cc07b4135cf21f270c3256ccbf908e23d79cfedac289b1aeee9541b58cf5af601c3536a255a006d8613e93029ee081bf577d6f71016d5b57b75389332f4bcc69872dd33475a3e7edd07718dbe5d923bbe22da4ccea1e1b020c9b90dd211410fa6602d0abb30b0ce9d0262b548473bdddb8a2f88ff9e0a4dd69575ff055fedd0a234716f93c6e9fd1820710e0898253681378879c18221c1174d9a4c7649a6fb1dfbcb88f4dfb19b00a833b6e70f6e1287b9a494d0ecc8e3ea60d7bf7d2c9eb8ef6256e5a56ad9b363e413e33d76cdc6e036028701c103c7de7d2700bf5c5cc309ce8b6d63923281f21223cb90f245db6526a87aaf8d8a3986f937d253db32cbac113abf1ab857a2aa5cdc4aeb72e046a599b79aba6c588af62fc8e8c28689add02bc17d5eeb79404706307d98d0f17a6afe22e0e19a27d8724782947881f3f231ec87e917cd7b9243f0b39aad9d837ec60b3cb2f683cea40660c19802ba0105e6f7b9847c95e99b5898679c884b52a96a4c78b96af04e62c87768d0aa392e389588562a14914670c53f14d4daa2ed968299d41aa2e532a1de852d12bfc6fd29e203e550420f7a652c71ed9def45a8fa29db08e4ddcd06d633950db9a0a3801f32d6d0696e52c2acca794b37fd1fa36b43e4d24554422454ecb618a074e4096e3049b690c97ff665bad420b91dd1a11e00a4a86bf72188af80ca3d429c289ef992c4e3444d725e9f4382bc721a9ff373802e35c16e22f82f425035fab08ac9f7410604288a567f5f55c3263035756da5ad722e2210366d7bd7d3e263a9fb61decc591a6d1f696599bf4888455d10b7fa635b3f81a813253d601df9837e46ef34cce6364ad68e4118352001f47ee20e407ca90979de3e52da61dfd3c3d06690068cd183b08140435ceb137b57094b3d8d66ab6f2eb89c0e66c1b41447053bdba25300c9cefecc91bb25fd7495f62c92ada365179335016e1b0e0b50c983bdd91a61accd0bae9687ec0659a795839ddd4191ffe3bc676cec322f86008cfb1788168ee31d12f4317b3db4154ab239d6674a40172ee6ca86ead5b150fed9d68b6cc3efdd0ece2d885c63dfdde7f68eabde4dbd68f320292d99e1905dadc9750ab308cc3b546b69afa14492a004d56e5d796a0816bb6b07476ad90742c0263ea492f38ac273d283f2b660ed8a666c5272c76d2c56c0403a49ef79036d3160063d040c83160813ca007d06dff6186421b0e9b27ee6ed353a377fa1bd5efab51e8624e08130936b6557f714b85883b72184cf7402cfb99dfcda65a9a15342cae2f591e43c361bd8e5c8103cdba6b98028c162359437dcc9d049a8c8cb55da055c8cbc84ddc29f406e632f50be86a7e742f3506342f267c1fb2be749bb13a4a32c13ea85492859ae24b5660fecf74b7834dc951881a14be23acd3dd9a4e21819ff81dbe13c62b76e4162f627e1339ca70425386e23223260549d004b0d029659f7039e8d7c0beb8d77fe3a9b4f12414c48b7536ea8bdef7f286a96f6cd285003ee4045db374de5c9bb8f39d094b1a65d7deb812e8e886be4b90d63a66e0ace1b6fc04951aabbc4d8563ca9e354e8d84c14ec2344d18d2aa700845592e3501395ffd2388b6a236d728bc480e40911877a0833e0d30549af968e233195c10a3eb00a0054e8a51293178a84e443c5672f66a447f67127cb88be7ac890869dbef04b93a2958150e80522d4b36d587335b4b3acd85a4ad51445bba920c45be9056ac6b422f5296d5b2875d6d01233581318da02ce46f268c123945b6bd53d86a8e14996ac3694103374ed7fd84e1ca221cb782bbed0a41b29514d6f5d3f59b2592fcb239d3c65d3309f2077531205ab2697d02ba79e0e84008718661b09af42f0cb444404b1aea82f2387ec725efcd25a700e75dfb8eccf9dc4514bbcfb8ae2af08cd7eede6363fb2da5f5c10251af27619a865941a79c40dcee8e83f9d143951a4c6ebca6596c4b55f2caafea644d4f589b5ccdfe5452704e41d45895e903ef3aca3ef8917b72d0149912b79a015112f350a10f8f50fb663db625067a2bc715872cbdb6250d1bdeb2f1d4f53cbda202315644fc9861911c79e6317c2b2b15fafb82e5681e631edb5679ab2381ef7a922526fe6869efde3f4266ae001ef0bea3608f8a4a7bf4bc7e03f19a4f14870c31e3e5a24874976d48fa90d08dd45be82d623e99acd4c80aae67f20780ce52b2265af27dcc61bb7aeb97e4c54ee184d9d78b99aea820b6dcd3b126355fa81da38d430cd8e3e8c6debc75417edcd717bf0f737f4d35821bdd4bad28461ca1b5254091de0a69bf8f3c280f649ba70f60a4a9d0964f0ef4797aa4d5ccf7f66964c33e8d0ed0ae9d340159de9af5038ff35860c9c053f4dc5b8e19b06c052e8c754d9d70b605abe0c0c20c563ffafd049b9baa2aa055cc73fa8ef70036ff36c6598f924fee562d6a3254e6e1b53cd33aed76864261d3a7c851e6de36ba80c1c651a05abc23edaf307324848cc54881af296c2f286e08f49c3c929d05e3d930aff36dab643da5484c0a861700fa52126982a502786a6f1c3008ffd91b330370540f266b9f6e1e061496c5468a9a931df07f56302dc64cc7a395ccc93c02ba184f2f7dbe2725627a244aa910b468864e864abeca5148dad1b271afe05a6eac704b447991ac6cef31c570242e91ce378694c440976c3b4114b43b6fecc495988103ae61dd01f9bdcb14042559b83d20825d3dde90af7d20b1d43abf1b8556bb6195859582ea14873c4e880a0e300ef3f112b741d30f43f30fd4762001d0700fd07087a0fb8fe9ad843e70042cf01a0bf1073e8eb03078819e8abeabe18c5bf552305a68702d4cb3cc0f115403a431a325785e9a0f10c60997d1467bcc3857b48bea74fcb04713790c3781fcbea7251fcecf4640430cd023be2dfead06943046d857f30028db41f364d04495c89c32b3ee674995588b3aeea21dad95ea6b6455de104c613c6f11e13c2ed89c3cd89e1ded0f37b4c10374c043726f6fb0fbab83771dc3c91c30dfe432bd67b9918bcff1efaf5f2f4aaa06ead0204ceeb262bbf5c0038d9f191791eb6fddc57f78eeb6e8e510ce5bb3c714e735b227f63c329ce13e65cb0cfc65a2818a3b9887607304f266803e7c65949d05009ff6241347288a0ced956bc617ec39697a0c3446aeddf6d6ec2e683ed30a9aa021ef4b4eafb721b8c51ef78eab8594cfafc6f81eecc669b9a291dc754baa63a0962d9c62d9e76a1885a133896cdfc3b6b45168f9c3346a38149b3accc6156facaf767ba4348e601a5e862464df92b3c97cf4a5464a4419a8e127c4de1eefb0071585414d51d96ed3f3430c26265ded77c672ba38b1d63bd7af60498d39c39ebbc6f39e707ee905c79765df8134415447550b95936204a14adf32275a86b090c0034437ae5e63bc91b4b0e1e3d880cf6073208116e3e8fa4fdfc3f3f4d1d68de025c62a2238180977e00adf4f6908440909b21e8c647b57dfd9bf60daaf887466f4d60d13b40143e7be4467c55fb2110433951e892c92f919bc776b92c975f39df28da5e7314fce871603fe522655af3491a9c43c84d6126415821f639abf28af350de0a56c2c44af7f0fbf3de24870bc50bfd2f11d902f61edbc8baad63c40bc95490a1cdf17f28cf40b2aba350c437dfd11be6c68fc2fad17619d721c6d10aa713042303b74200d5fe204ce02bd4f463cac922c2182c75ffcb8afe0994cd5d2053b72f5208baf8355c6b1cde57c7347aaabdeb6b4b11d59ccfe9e00f112fec3bd440e818284fbe4de32edc2875eb161d3ddf283cad25b57105c844a2ac6418e5a439fcadabbad699237fab6d90dd91b6cbfa3b07227ec2914c2fb82b339c03b4f127234a1649f37769ed71bcfdbb46fc0b4d4f64df0cef6f3001c27f4ac97bd83c4a18f4496b2ff7c0259c04f0b6d244ff9db11518ff55d7c66de4992f7b6b182b1aff92d669d48138607d6bda9ff40e836683645ac573d73e194e41c5ec5c87d7139015f2270484436c3102c910a5c9430534e32ba6fdcbb50476bfc848dda0aec032ebee68e7fb435df2fb943e46a2396053dd3734d26e6dad060e2739f08e0846d7150501a145a3b1f3e2f189e3546771e1e4184dd7f3396f00521d21c2ee267bea30e526e94cea65d99cffbb0572630968279c5fb5274fe139482c180b4b8fd1525c72df68d11be142224a288f4661f7a451ecd7a9dcb47a4af2e0f11e513303a4faa307dbc66fa97a46643862fb18df7e04fcd1a8d3ca900aa7e7a1315cfe9e1e2c8b0535de5a3d930b4c0d0329fbdfefc5fd11e2b2cfd1d12fff54a22c4592450bb56ebc5148437fa3f2d2bc4c2dfe7fdeafe8ad7483f3c8b6c75b23c89c7482b68ab4870af9c12e875020dcb90c47ead66b9609b4afbdc349634df2966a8160bbd1d64f2c0b424846425eb66efcb7ae4d8dc0a80889a22337881ef6e914f98df903e350da4e9a11976e8d2265a0c61accb4e7da5c2a25ac8743429038bb19f0be7d8c21914598a4748443620d96694c80cef729519677f8e4c590390eec4a62f9a8c6ff5b11dd395061253f42e942f982d692b51f81a93f63cebe4676d17722404928c9053f2cb9df963bef601d5422f010b322014d50f62489f2458a7784038162387910479f3f966f108d864ff73aa5c174923ac71ee1ae0fa5f310ea6b7e694b52c1e4cffd33095c85a0db25b858eb1e448d4e01a252dbb0d45c906aa454c626d67092caa1c675329ebfac57e2924cbbb86bc859b8853bc591f548f0cd0488795a6e35141a0c328a2fd3d481de9e9610ff30a9f6e158c24d2f2a8273e3ecd938f65a215bf2c684ae692b9641e71badf4f7c4e7c26c01edac42f65b1f1ecfd4c54ade832edbb8d36237ae8080c825d664e707374e80d14f432521d520d857f4e84e94d891755461cfadb991bc37f84dd8a415bbaa4d319299f35ce30d833fbe2ccdafc9a41496cdb1f1d5bdc07f2ce9ef2b59edf6a28c577135879087b9e526f4920ce8e775d4701fd8c2c8f5d0e23fde21810ef4728fba6e2c264ddc279570f2b358730dbf90f8924741d7864070db879f840e295d858409ca7ed29c0a79b766fa2c36dcad13a145a5868192f4e1ffb4ec06b03cfc6715aec99cb5732dd35ae9e9f94cb71a57338d55b8fde879a6b32ad732dd4ac63d655aab704da68cee33cd6ab91937cff5cc8658b5326c85a3b40f07a0b2e2d34bfe31510ce8bddc91e75a060ca8bdcab4388b1fad0218c37cd09740fedc12c418cc827f1dc14462ce8e8a85d4c861050cd37e9d2865ea9b04d176b19f717ba65bcdb98fbfac555424f5c7acd417a443672ef17df662d00d67d0d7d7ecb00219ff1fcf5da62deb9cb9b9e0e7d362f998e9ba0094d7dca69b72445771355e31b11271072b33d1579290e3fb02966f4ef5e28c4e33da181002b9ebf988b825a932ef41175fdc2b53de10bd89b4ef30ada9acea7c33b1f529faf21f2d7a06f7b5af174d18dec5b0fcf5adcecab5e16928593d9f486b7915107f2598d8521006daa3743cb29f9dab8365c0b82c1d8f4304d8b72f5ccc9f0fd74c96286261b8faf91951ca69ba8d5670498aef27956cd6ba1c5cfacc66556bbd05faedecd68aec3b93cdf8765bcc002e647ac00eac5accc389bc23c3e4e0b7440d94720be5b3e673213a92d87a8c19a7c145511cbd039293f3f6070b10a3d920716170666616d5fd49f8278080d0dd0423b3e23ae10232cb8fa02cf9485fd229f2164dce45a0465aa6e848c2d36f55ff8d0667d72e87825a653034f81e49050a41199b90718345ce5954b2cc3f236daebba6a2ae3ec39992f6e9d4efe7c6404f89770243d0809b2409bb144c137387b94e8dc5c8166b43ef1891ec9e9383c03567956407c889d5d7adaf58049d98bd9b4bd1c8ae85ba1e77c65c6bdc3e389045da1259de1c34e186c8e21724f68da6c01ea5bca737cfca373f99dfe013c99ac5a8ec16c19526e20a91ba815f88c905dda53858f4c0ab9e4a883e979f8a91674c28841beee88ed0676708724193f5c2245a46c4ad27b30e58f84c87839843f581f87e625e007da91ba8f55e0e43a07a01811ecb2cebac0a7fd1b26b1ff24812628cd2afcce7f64e1ca8fd696e8840ed8c40ca088213006cb3fdad26301d43abee6bf16433ef4d4cea83c61de2a988f5b48f908970a262fe1cfe15dcf668524d0533629d6a3980c0a2684159454144c226686ce958b80748f1e00b31dda9f43692c2db69c18fddb099877e29aef0363a73489a50c883f4889753a38509bd023e282be4f2f62f85af28b511f43aa617496f6c59799a939b8e124d006aa347f42bc1042583552b4247509a2a89ded797b142075d096263a73b8da828c4910d50b46163a7f78b256a2eeed80602f403bf86344fb2a92b3adc3352fd2ee94bf9bbb64385d020c5a175986a6671d1559a37aa927f7866c484d71a3d00f5ec1efd122e1d123528a91b2122781c79600f9dde6dcfaba84774d01238a54641c70d114885715b3ce13e3a472e7006fc4471d3bd37949e52d402a80d3d060ffc8256e1a0ec2a8dfb8e7f7c8e8f0e796811c4bd409cf808ddac825fe28b3009296cbaca0b0b3d28a030723e2a7a2e9e9acb44f4a83078ee0d2af906a92f552e4b0eaa7cd0438781a2b03202c3ea1d72799680e12959cfd1bbd8f419528cb96225ca15c646b2ad69906f5371d30ef721cbd588d45913b62e22fc314168f82fba0018cdd1d384a2a0c6efea87dc123091b14e9e379784b7decdfdfaa51dec3586500f1d180330657395ab7a3581fe244c043d4b3e319df79cd98a2a0be7885101f74fb59ea3f8bcbb19f92b96de201fc2dd1ca984ddc15eac5823fed8c08f09ad97e62c754b5cd291ce39882bc9f425c69538fdc02aa7d5bd51aecbd436852e602b0af8a411f99dd8347501d44ea31e5beffcd0d61c2ab5b1ab39ea9f419e64e6fa739bd130f1e7e3de7e816a45f0fe439fc8bf82b4f2dbe8c76d2ea8318d56d51849b943b161cf6861608d6a389ef9f5ffe357eccfbb52f9ac926f12148dec9e470cf5aad2deafcadf237e7d0800211ce01d66fc3c098024d3ee4bdd221e5e0f9d3d3e189323c0409e344b19de226d6ae996da8b6035249b7549764d8fe4ec198b26fca428d53461ca80510179390a2450c49e4a382bfe27b88c6d2552b1f752d9bf92825650aaaaea70bbe89530a9954ec0cdbc52a87c89bb174472b5974c9096d9c678d52c6ab09fb73ce3afa5a20b80b8fe80458f2c0658067335c0cdc28e3dc4c4513f7ee3b4266be448cbc33b8132c9d01e1ef46c79503f11abb4889fd59005ea97028673af67792124a341ae1055943c9341fa7edddf4ebb925cb249fc4bf8082d10b8582ba5332de5b996bbfeb04b2cca25877330d84de5fbc40776cf91666bf640a73a5e2fa0ffa473211da5c45d755364d3e0df84d1b4911d3f179c59e54d9fd5bac2028ece6323418708d766f0641cb0bee073fa05cf529ad1e1e15e473d71024061ae4527734349b6df1ec9219c1da43040ae06fe5112d48a6cb47d0221b0839297df8ad7c4ff9054d19e888cdac5df01535de82e9731c677de11314c2f835e403f5a20a9e3027520bc5c12815048c6a01223fb724682a4225af0562d7ac46482047c9ce8cfb94f98694cad76de3866e9140c917387234eb827723219030ece416d3d41b53ce196265510ca91e3cb1ef755c77e13795b433714c28b5f2b9165c90bd3d760d2d54526c9598821a6fe5a6be38d67ed42d9d12d80e2bb2f801bdf4bb3137ef5d72708b93a9e231c348dec68f1ffcd442c4ec9016ecb8408f46874890441d311bac0915e339c1ede80569ea9243734d555476231dc0a78c6078369940001f8a51ad57ef088a62767a8f7b59a556fa95d81259c100b494161368cfaf3696050f475190ba49ca9e7ef01192178664a09ac02030147c0dc0ff0683ecd4a2a5006668aecdf1be80a7fc4470f9023d129f6e4b4f40b196882ce38838b118f0c00a5d74da54f60db970a82d57ef0bb9124188e3f8388a47377beb4691e66bd33023318023856538faec5f0a35409739b37780572a805e5184c323ad7ebc9615d5e1bcc56aec9ea3a794df227515e27f0101bd7c696e3bd8ed7a5eb1ed456e5b1faddebc6f226bd195d049cf94c4c8cb64dfb48e7a279d3b0a550781240463f9853afa2b2351223d05e1d00cd4693f1c4f832247ca6b1479ec76959a5eb06211096b2cd86602cc42e77d03d2a5c85d394a65c08051dd5e2220396e055af99fb6d5819ca7e93079061185cb26258618619e425458793637e5c84a9ca43d4274d3c4c575a9c772fe7b896fba78dc900a329dcccd3587232e51d33440a5615b968ad9a3b27b32256f9cf7f2ea44beb68a0ecfc97e21d800178ebad991a840436fd0862cf972b873a05ee08a1a3ff73d6ff96bc9629732926617508d4b0d7d989d6846e8c51a4d14d7ab32a4a4a93eac202f923c9539f261be0cfa1a03d56c74b12069935853b5eac5c474f7af668d47942444c396932761ad98ee0cc2f66e7e7f9c5bf8d53ce229f4e2e6a258f5c9adc633c8651c0153487e1096b31c478a0cd77ba8d235a27135e422fad05dd21f02425e58d85208ed6781116d39cdfd4347abb71ee00f7d704398583e73d88b7de800bf54cb350b233d587b5e11e5f67fbec3cccad569e1438724b92c732f31b755c9599ee5e9ebf61cbd3ea464c16d3d0520e938c0635dd66b08aadecf63ee2b585ae881e710602ea97350cfbf3eb132096ae3d382ffcb3f91d8b39180cf17639330d76effaa784269c54f3de6d86a02e51f4858c5ff7f46c060e13d9a48a76b00e098ea24e6b806d4bb3de9d604c842557bc6f74b999ced6a0140eb83d9d858177fa4f490fb21e3f9ceb90db95e91580a74596d1143fa6cb65af109074d3ecd5372386fd2412c6562245627ce3df48615daa903b362c3d1c926de9c95ab676fe7068944f352909154689cc143b66e92a47c80dc8ce997f417ebf9f021bdd9187e173f00f7eb8839339f172b5a5ffbed1e00a5a9e1931ee23274ddcb3276c3f24594e02b5170b674a9cbd928608d8a3e005073bdf2902f22118220d6b687a60b6ce16f1387f9186c220abf9a6f7612f1b22a12ffa6d3a8c11f49f7b5ebfaa02cc5db8f179787a6ceb1be5ffdbb1f1e9639a2908d1cfa79617820d4af3efa0fbc9cda0b2affe7d31b5775214c9c75810dfd0402ef14db44c41e8b01331e51c09ca8fd8082468aa2c759c481601928d7a37745c7e60064212250b997665beaf491a062809c98bac6484be44b21dd7f9ab4d4d24a0c6f8cdb18c8fd12f2600f0718dd14541d782317393b05251155772965d4e3957ce74af3d80ecf752fbd5b13e87a56295009eb698fcf551671ffad552e5edb8d81730fab8c7bc08c9171d02f7ea5348295e003d2c7bd9e1f0fa6bf47c0f10acf5d67c0de22cb0d019d6ff60235d6d30482e5f74caac8d0c3ae7323873168cd3fd28f7209f57eaa56fa20a6c254b69312ea7d8a1d56ea92c92a584e030843d052a040335bb5e9928b4b5e8c852d66ec38219c2034be3f103a1aa66c4c1fa932c1908b62831d26628485f2b379f80992cd574cb443b861ab3760efbb994a9031e21502373036b42408839799913979037ddfac7f940177cce7161415fa6c6c2318a5d4534888aaa2e8475b11527f7ae60e98d19d80d19f9aafd336200ced2748ab637e8a880266d36ff7eb1a94ca9c99e43209fbeb9a11382d090992646886390d248f7a25db2ea586e8826c13c187760ffdefe16ad7a5c17acae3720c826f63e57304605b912c9de81179bb23a7be412b1c87293911e664998ee368a6c44676d49e426bd42c8e8410ab9092020a9b93be556a7b493dfd63f3dea8cb6d1c4656029ce258d1feacbd8111289dd02ca47e239599937f6f6d926b63d30444d7aa41a9db584ab04cac840c3211f4ce1075e19d48c62862c4f31b1038de297f4127b2639ac952cc76d07680d8c5b5e95a0c23a443831512ea6d3b1abf51baa1bbdd056e0fb8eb8869d368d570ea292dbf0fd3b08008d862bde0723a52f122d69f16b74f3671d900901804ee2b28d0d604a75d0f07c6d334be387c16b746acd6269af2cf282675e06ca2e0be5d38769512c73daacd31f456c8538fa294dde331a61fbbd33c40c1123e70b1592091aeb018075b311f5cd5e010e1fb156f080d98ee6dd03a00cd3bb93c7898814be4447915ad844dd1211c26b6882361a8038fb2ac5af7a20382680ad3e63d1c4141fb62025909b822f13ec26bcc9bcfe8a26bfdc16301152f66c2e4a72617a6e92908e94aa6cb315f9626c35912b69ff27b228893cde4e9c246906e1276f45878042d568abdf0612b96b1245f6ca9103a9458a1639b9ea26d8e647d443227b1a291e2d35a1482201732337d91f334b031af8662fea68782227acee1c70340099ea03fba8c38318fd244d2425bd8f818a01f57595953924b1e010334daf666682e86cbc19b5998297db68652b9c051ef3ea7c9a4a488492e773375c1fb2d41f9ad03f47053da69170db9108d57adee3900a0d89ae184819f7b6a15a66a48600de58cbef68bee1cb99a155de799281d17ee1d16c6ded950f6a220730346377fef58053e8a7de60aaa2b0c44a79b31b82649cffa24c6b6f69c893cb0ddf1cc891eaeffc8b0563547aa8f4ea9feaa470c10e6f8de2e1cf99026bf44423c4eadd2eee199b02d229d0224b971351631de0eb6079ef64035066ef094cf083a8c608e80cf89f5beca7617a57ab4387c36b524ed63c22472a38dba3a91e301a2b662b894d0d6291b32ca094d3f0e3ee3b2d2b72420d965bb4de7b2ab5a5f92aae17691d8cdf5240d96cc5312ae84b08c3710561432ef7c0d9d989a52e7228f2f9b018a70df5226d8042e8fc082e612ef1250446aba032684fad65430546ef3ae4b52c07e29019819b3aaba5ce0de9222b6ffb0db235fcda1bf8190c35f6e697400e088800e95f97ef32359c42188b81598cb0d0564725d6a18889fc0073750deb44c25da2524ef7056a96d691164d7f8511f46a48732e4206044015c29340fb231aa5076d4057a16e12ccd476026e8aa9365094f1871d4bc1dbf9e1dec5bd7c1bd6934a269b497d77c0995043c4c16ffd0106d2e3c62f576bbcbe89592bf00d27d512b1a833684d069825f89afe8262a7f25d9014d6fc902af6c1951eb38f2738e5b076a220305d1822399924441f3aea97f1d23a834d876ff9a47a2f34a033b020403db2d828dd5138798b4334a3a918918518c538c08d4de152b8822462f7d088a3e9bf34bf5ec3e257e4cc6f608c6a69c98ae664e4d95109831eea31edccf91b9eb67b0ac060940dd330b410ed79000bcee9e43d1a2a01d66ebd99397aeed9166458ec779f8796688d6d2f9e7905b3127e1488cb555324684fb54e0c9fc36b705d6f7f0b840093652f69fabb9be16554f4c3f185eb407f01e8a7badf200a46d1cfa30e99c57228277a378712346e21a260460b0820e99408ef8026eac678368e691ecef199723218a4338eda31b4869b710a60daf40b73c76c0767b108f8887a4175e1637a370d86cee1a49de8ae18ebc546e9e1018b97a7dfb18e2e2581161312aa2728ab1ec1da38fe4feac9bd00ab788e4beaccb0c6de3b7df510b3ba271ce20d826ea37edeb54408d5d6bdfe8e0804664d58c987c23072199bc5142c57ce032cea44080aea6a2402ce0df9a9d965b0e20bafb3b0488c62a06afc4c88ad9c4dc2b36c82f2b9dfb4231bcfaab7cbdb691a4f4d36e5b9a4407b9b3a7c953bbe74f4586f336ad33674f9abc0b1a5cce448c783d6c4b90537b0e41bcb39eeb51ee85935bc1d041e1d9bf862922c3fc38b4c75abfa6db527b160a6332efc559f1b23235d839b3dbaa562630831951cef9f3da0b05549dfa13f03b26989ea477c9aa47e1b145029c47744df1c909ed38b31aec423d55f18fb69686e8fbdde5e474f4c319d381c6611591537e7164fa7e777482442609873a3a5eb5c54cec97667b6b8ba02b998ed12176d8fa5907a1ff11a950d50e786f6d2794c53e8e9395cea1230e3432d0037ddad2dc9d511dda871e1154b05ca20c9b9c776d4075079020dfa36363f46f2db625c3c08b4a3a723debbb93d7c4c0bd5bc587470dcf2b7bbd1457dbb5518534c472b173345a232dcc262a91b27e57c29abf3f8565565e41a599a7061bbe7503681c54814319298b93a8b356de5877df3f5899e8218ad80858a6fb2797ae7b1cb9ac27a054db6997ab33ab23071310f00b2502ceab5d12d8686286ad874c584d786bb51f3e98c6631f6638be0c53894b333486e5471d01307a9059fe8c1427a052d040f085fe78f32643217e2de06ef9572f07d1eced3b472abfd1fc1d099e9a83dcc4b1fb5a96983ca420c340e7bca328fca823e19e04db8327399f7a595cbe0331242a0bf967944c1a605b1ff97d93576e6357438e20a72ba80e170080bb9d4ac1c704efa9cbaeec871122a285656df18aa2a0c02bb48b05f8365faf610035099717462fbf80ddf0723f985152a80d055201c9365f993f17a4428d73180cc5d17070d8553e4404d06d1abc25cf8176afe3b250b6ad83b75d20351c6a59a2c4fac5dd8e70ed05803e507ef0835494c01f8c01e3ef39133eaac4b600e717f6aae357cff00d66104235f88642396d41d52cd582e777007fcb7383958f279cb5f14c7f1907bc3f139cb68d41a5da0f9f1b88dbda1dfbd3f15f3800f560ec93f37d4baf4c122eea412169c4bf4b9671ac08ea17091cf0873fa289c4ddfdb7520144847375970179771938dbc7dea7fab2667c558b26d60d18e8303cf845235a6d411a3bdf81acee37c993f1b786051bf2605ae67f497a41f80a6d7232c9fe68ce46bb1be42384a8d36dff893093466e275368057adfdbc4f102556ccd070a55c657b03e6dbc40cad63f77ba1bb12b42827de64b45faec4f1d86bfdf0db8b5569a945a07d8d28009f173c4ec8dd88c5ef760a8c2ed1f4848881e4834eeab89e80cfd84f5897c662b8b93b7038e2420037c2c65d0c4b7ad9942310871296a68cd9b400dbad48311091fc4d3bd5507db26b02ddca73cddc4df51b7ed139b6349e76d9119690267bc39bb0624baf3b38d7dea087fee9a6b3223d88b4c81e65e22531d6ed43cd75d208ea66be516deae3490260e1076adc0ed688ffe1d5b3123aed5645ae6c4fcaee4fb458787d1207bf29235ffdc76db755232c551880f9cc5c40e398940b94cee5a8c22ad80611d665d9c7559517b42fd3d2f35da9066533cb869c006b28930d7aa2ea430c010491a7a0ca260f1418c970ca958ccee72948b62e61bcebc2124f010090320845e49ff9e55c583ce72f20b12d3fe0b2f8a77ab841f3616025f862725fce22fdffa0017cc9047ffa69909cafc5803af333247c15d8c8a3da6f5654bbd7b7276d5b4375f269b9d22dd6a7d1e1e10bd35f5ae18e5540c8eba9a9ead0a67e78fc2a05eed579c39bad001269f8fa8d6d99d7cc2b5c66c7f8e6702e04572151e2578ee2978f9e46041fa574124ab30500795b568171c32e152e92fe8995bde148270113d6e5200024a67afd47c09d3e40a236cc01a5f404f18fc6110d6a9bc187484b33b04909c03afeac753e8b4558a2128c4369d1ccafba7b35c07eedde6f6917296b11a1ef7cd3d1b3dc5613c615a81b5a5da58d4f9d786db184f67aee35105abb618649c500006f4b3431612a405922682a72c5b2d75b3db2fe638ee4c3aae6712d9ef04430fdf74580c9c551764ff43895a45fbda967d07464453052d48d41ea3e8f1254704b15f9deb792beb70689fd6e75fa81d9e97446ec9eb63f6a9fa2506a532038c60a3eb159397f8635d9240bf92eb95c4c65b389009bee28b7a9b41d9f7d5b61a490260c8a2c101da5946c344fad8654acb19260e720314248040065762218916ccf715b1cc391caa61e89cfd974cf559e7644922521b766edf4d22f6a0602e95d8e5f3432b0df8fc96e95efe178680095e6730a6a42d0ab5c79d6f4c9fa34b54625671192232c7813598ce112d3eddb1c91771869e0924a1bb7d02ca361c1cb7fabbb1c932639ef266c2c03a44e562dedc5e1f24118f8a4ff2e096a905a9f5a23d00832928e61da53fa93ce240aa0ee8cccefd94080b57afbbc836a9d452758660c7b96af7f78ab4b22c0c6d59b0f987a969ae41c116893c03a6826fb4a3fdd5e9d4cec308ea8487aff7f8c3d9928b715d2146ef3d4f1fb8b9e2c111386f487b095dfd7bfd20c66a313384e077b161b06f44a8980b31224dfd209d34cd06f11667329a71751fe10a49cd29da2fd01365e60d786c0470bfabff4257c8bcf6e5eb5ef1a12c1d7f68f1fa4b40480f4de1c58934637bf722f574c5d63b40855a1f456851cc41f547eb0f8bf133ad12a0b6a8c7c7f87e8845fc458099282688e3b4563576f2c147e2992fb39591541bc51696c873d5ce28a2fbc38be7abbf2dfd939f02db162db213d7e3f32b795ef549cd6d6f1403faa6067778da67dc79f8868406f1f194aacab5363198b6b42f781e3cd216b582021ecdbbb1afe1e0681c592a879ec4ea7982c5792c2150cab016618270054a34118092950dc5fc762f08d9d8656233f2fc9e155af4c7e01f1f925c658e22bf4f708352925f23a4229abab2343fdb858d0471494097f9201baf156fc1fa115bcd7499de66d6f394711d7dc6373403c891b6e4b61438cadafa8cbf24389d1fec9965ebd380abd72c719fc11c21dc51327846da506542b520cc6a868399516f1a3fc4e688d8dbf66d2a33f75af0322024b13831783caf952db8eab2cab3330c88a274bff297f835d89bf9414af1723fd96148f4e0f94ce69eb2f8e628f208cca05c096210507d00bb0ade72b0439225aec3cbc5ad0f42612b63863f5a58e0b276227b39886b24903138d6bef27652eeb459dd80a8959aa732b1d118e987669227f7d59c5e169c1c37e204c6c65ef97613198277c694e00060533e4a1ec5bd276f36952f1bee2a2a743cd68a9dd9419b1de68f095ceab6b4dc706aa64f060f76f90c8985c3704883d3cc7b270873a40d04829166cde98eea5f9a1d5396d02614c88d31b2b61d863bb967f70c0d9a857def98f2bf60f22b1f39e2e651b8b353991bbd4b58898a942ce7c7ea1771dc3775f303e9a3ad9b3efca922541e0928519cf7aa03d708ead630718044469a67517f52b8406e18f1ad0832af35ab05d912774ea78e816802081292b0703439bd50614903934aa12b7de45dad26f3493dd910ad1a9ea42dd1d8abda99534aca171ba67a7a7f92082e964d39e01e49a82cb3b39c84ff7438aa21281d31b7d0e5554249700ca0d4b8db02876538745558d7259aada8b9999b72b558baf8464be257142b9aed2af3690affc267039ce89df77c5b427805992f4834cfa5bc89cb8088c4569069d7379289fe7cbc869278258409b2cdb85a87474dbfe6d1adf424a6674e7a5512c461f29635f36775b1b65ed613c3df3905263e93098ec9357023e095043f3ae6c6842b1703ae815faa07e5e7fc34e5194efb98f77198982c066dda21002179206b702d70ba9bbbce920ef27947033e18e915ea2a5761a97b67e5522ef22ade98165aacbdebc171f9d4db008ce160e518f3b510ec7c778e600f846f0010ea0accf4a9d67458818f2f39e09f00c6d87467e358d23ae63efa22174104499e79b6d2c9cef570ea04b7a90a93c23d2de8e106cce594d4d67f1abb2cddfb237fda18a077427b5c83d11d4050ea5e92cddf745516cdfd6837b090ac4148d5651be992bf37c7ee1fa902930e9404fda44282a007a8e408e39b1f09c84524bc0c20614f77c8a215eba5ce3664a59f5bea02cbba01007a4f66fd94f1a9a9ea8bd54443545e12ed42633598acd35efc5bd6c4a9d71353c9c2016e15a77a4eab86a2e0f39389152101317c020c95e1637a9da23ea1f80175fcfaf50a6d0f3696cec745296a4fe257326cd63b64a24cf20aad80800f18a800e57f10374e96ca830ed7589a8a858d30d6f1c8a2d40ac5d8ebc28a2d976e6a36f329b473f5550019d7db0d986ca8003b5744fcc8fca88f88da5ee33c410738357d8043ecb571fcd1668c228b11859214c85cc9665fc51fc928423da2a23c80d03b8c23d6919c27ebd4f09acfa66406403fc91eeec8c1da3463a2d02cbcb91323fe7749855d91f740405abcbafaaff55afc4bb38fa51d129dc53db4b2bb68a38571540647483026804281ee245bd3258f7d0859a8100747d0194e5605cb1b6d9a3cb596fad243ad6a3e0f98a8d1d6cac5aba392457f3e5081f9e415789c09ca4f2695c396777d716987d8865586e45bc24aa5709e76160d3a6485b5a1704311b4c27dd8a0bef970644691958cb1208d97dd842d1111103f5c0452ee5faf0dd801780cba12c1d467e2865dbb59af6b994251728c1721ca90bde927abfa9029836738b42c5b83b3e186d5615dfac49f0eb8c012690ba1d4e60391c4c1998878e5aee5f24adca24b54b9f2005f0bd680a15bdea2dfd8e6da1c5c7cabaf864a070688275a09d6012d80993f0583d49780df3637149a457105851682da097cfc04a63490b16e55a718aa75d294fd05acbfb5d198b1d3f3af320067e72f6c0b33b1a47782faa9bcbf5366b5c8dbb8be0b74b89f56b01a7aae23d524c85ec91b0c96b068daddfac4120dd7404e6e2e92b868e401c804ed9922ebace3b7c064a308e17f2acc5a00f33c6d42991bd93bea82f706ea5e8035419ec0663cb5dbe722940fdec54004112f8d8aaa8a8bbc63fd41f87ede3b7d5bef940f60b3094537b84e8990d3800581b94c90f86641e6f3c02c7bc8cf481893feb6a0ffe989173c920cacc3344d45e40199fe2de8119252006c74eba4786ead2a0fbef2d6bd44001c3bab16dd22b818cc7c40800d4fa703a8d5b361bc70839640a19375b026f577f1186285c189241c101e6fc86f2dd716dc059c412ae401de2750d1d9eaaf4e2c9811dc7e0fc5122932c52ac590e037ed1c7a913841973984cba9e2e05e95183e7f4220d25cca4ae4bcd3c252647e9b4a9df858e85d082913308b4ef328884c40d56ff0abc768c18c60af0bef835e09d331b7f03d13d0c6c38e32d108919d89cb0e3a127c05d4b4306e7eacd9877aab0ca4cf302a5b695de67178f36d6ece4a9afc7a8136b6680e285ac8ed6a9bbb339199cab10ee04dc93d5b2d7df1bf82330f6aa4d13685cd034ec28a552425356c4c1638e744d138e3fa1219ced84061c62f267f8389e2716c5c4d0b73cd501886ba2f569d12c020723b85be2ec39636d4cb82ceb26101747746b4cbe66f9c602eb13852dd58f4b497916a18944953994c6982a6364a90e70cb97b733c6c6b9bc8c09631ee084b8363280a1e28149a55fd46afbebe3a0127d54b9830712ac8279990b1b6f8271dc014b6409055c31ecf50e4c7bb089df186316fc28d33a8564cde037ecb27943a6869a680d25a6bb182f9f37fad730cbe208be24039dd6fdafba4220395e13552759978106c981a369230a7731eb5b3e06798463cd8cb61037bca50dc6017edca659e066c96be0d7e553a6a0b45b26c380215bd3ccc18c317fb1d131c5af73c6ef41835a938c75ef7c1ebcf42546843acd87697e3c349f2f9e526900518bc6d73fee0ca10248b408007a7a24df9ab664f0b40f99717bba90a93541fc13cd3c9297475e68eeaf1ca067158ca99f384cd6012b4840fe0c7f7741c7629b99816e286806a9cdd40cf6db68f145012d43c49bfab595e7d40798b1cee2f7320884de6dc33c6aec9e023a03b2655a30d819a703d9ce3cceeedb0d726afde298d614231a296b16b2fd14330eae4e6a19b9662028c30c8995c9191573b91e4320fce38a83e051475d5c8405071f914f0cebc3d47de1f96deee69501678d78d43612b2bbc92da54c29a5140629065306b5db1193a1455737e6830ba25123ee201a25c4cace1904c46dcc7e0daf34d2f1d79a2468504201f3937d577f5b7242f7798a4fd987b20883e8be9c42b781fadbe76e7708a34728a326a7088a44de2c8a9a9c9a686e9a686e226e1b89b86dc49156a29460a9e4cd25d2caaad47193d213badff9d07de1a2fb309ad0a5d24cd05d51691595a15894294051625160dc845b8c847718091fa1593495a1cd228b95ed4b47141d514769449a1ea5a6cd29126d1ce4386fe6a668db386e34227123126965a554ea5ec0172fbcf9c54aa9eb5ebc800123060b6461f16696173062c460619121a38545464bcb8c1926130d76a1e102f35203c6468d1b0fffbdf94d2e2e3c98be8cf854473c60467caa41e2c1d406b461c39b6dcc1adb1f89c76c19b5ccf864145dfd62fa5b3e99483ac89eaeb2f0890649073e9960fae5855fe24a476266e65ca626a38b0b7471f176619795b142d281349ddd17aed42194a451f66aa5fd82ff692dbd4a413cb088c245b75f4b4174917002ada518ddd28bee4b75030be4102dc351c6a8454dfe0780b8a34729659527a4912863d42247af4e4df3aafc288144d632814a2f4e000bc8050eae2875fff7f77d5f7fa33ac539cc5aecfe172349b052e0ae57bf2ffefca9bdcb448f774522afc69f5e8ce6c197a7daf5b723e956e8a7fce3d334ed2384f063a08c70bb09bb1ff33d5f6046e36fb02e9e8044960c1e7493a7dc9ec914f7ddfbfd98fe3e2191aeeffe8eae3374ff4be20a0cfbe63e4f06e4b8bbbbbbbbbb7b9c394d84dd97c021319d94495c2182599895f19f2796d1cab8efcf6edfb4d48719b40aa19009a20d781274a41122a08c082047788400cda809d1a8fa4310e14ee8ea02296069b0e8eaca8a80096954dd21100144019aa669483ca1b5734929e49a436c2fdb460410e611c23a4530ff266d46d35344c88830d2fd2c9f17071cd2df46683f44edf17ef27ff40081585fd97e476f9afc219a1eef173140e1736bbd38e0100adbe377cfa9c9f67e38a051d0736f07a9477f5c4d363aa86c415ddadcc19c42b80c4f13d4a2a67d2c84cf095465098ed00a9c44166ea069e1cfeeeeeeeeeeeeeeeeeecf46dcf93f67f67e677eef7776f698c0f79f69547b30b3b2c038c511d59034d21126354d7e4e9b72cefbc14a84e1fa4a478f897b43512d995c80008a1248689e73cec830944710d62114c2f6ffae7b985174262c122d805c54c1ab247cf0f5d772470331e98c8efe2a30abc0314bcbf0afd76e3bf8a4e3571755d0dad56f8e02b614412777a454c265134a74b44cdba800093bfda928a5c6900ee109a1f97e9a05e63be57cc49cc348e3fbe288cf03744dfba87946682f64525f9af830c691a649af9726be6700aee42136edd128f9dcbc1a5ce9d8237a4c1c7a2f94223d2cafe082f0f3680adf9b6a32f2d2f843ee85a26ac2080aa28cdb9ff946cccccccccccc39d6dba5f198eff85f157de7e34b8c5a7777777777f7474de6b0b1b191a41f3eee01aa541f7f7ffced5ae4cfdf73d4c71f8edf859d2860aa1cad07275fa48cbbbbbbbbbbbb2f230c623767653a87fe96bd9addfddd25b560790708bbdfc3b5a8695293f1e39ac423c8100aa1970393fe1d353abe1925aa50271c011c05d98820650abeefd3722842c81145c47656c41141312633353dd337138f2822b641d1cde98244175f7bd1699ebeefa33e68f4aa8f1aca3b5c3bf94b1f1448cd9b4caa2805ca5f3ae19cdad4e0a9658cfe04b7315c6a92e4e7d01fc769ff32a71043acdc013f788832f3cfa8f9685410fccba4b1686f889661214ad049a640cb6fd20ff07971c087fe1c4f14681ac4ab50a1524583b811f2854cca4bd31a88241fa4172b06a8938080543ee7cdee2bda07712a230f8d821e937efe1e8d724f878da7dafbbeb94f9977a4135c675a6982aed6c4baf000ad3b63b2f9f0d4e6f5464d46e8e4d3d85403b66e503599f666a3fd636545ebca8aba102d84ae6ed00fb8aeeea15723bd285dba740652d345a1f341bb82a9f47e2c4d936e1d467aaa2779aa3ea8d43c79025243a3d745d1d5495b3563dc12ba67ba508c70b1008edd997778da28d8fb82ffa9ffe674c70ed9a7e036a6b7ebeef3aa0fbabf7486c2ff20f2eb11d4a3000d08f09c73cee905d15c061dadbf241884a9b7eb05915fd93ffa3a7b41348f897fdbeb044e5c319082fbe6beb9efe74af2b918742c5bb6ec8f65c78f5274f5a372765e35d1edc4bd356bdc146d92062a9f39668e398e024c396fa68707bb65a8931109ee9b938edf7fceaedbd42e0dda6285ced5d5efe7ec34f7f922a6bf29d2be666fb6d7b8af3450ed23e907ffedb9d883fff6358a7bedb49d6af421866ede6e333dfaf3d8a95b863aa9c527dc2e8cecf10ab828b5299aa2c97af54ec76614338a198556b341cc7acd7a0746a9494db2f835835146195fb1bb6b8298e51bd43551ccc0ddddddddbda7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7073e95d6e9677efd48a1fd3016613ffdf5cb3df64bb8a3c123d0487f0d5952064d212d68bf6826a3fd1c8947076d9b6be4e487f6935e2b241eec43fb4b4efaebd28b7e18acfeda0848632c8b8e0c28438637cb58cf552e0f62bd74ba0a672d331ccad1ca0d97dee956bbdaa75f0d9b0da956ac3ef282c58082dca5ea984aa552a91a28483673b95c2e974ba552a9542aae83c1c2321b6ad5aa59fc62580c28e8f57abd5e2f168bc562b13cc8d53b2e97cbd572b5cf4b46e89ebb5cee7297bbdcf582a5c514e3c5308e315090ecf57abd5e2c168bc56279d06ca855dd1f5fafd7ebf562b1582c16cb833cc8833ca865c688c6cb09066b1ed969b1ebc7947533d3ebf57abd5eac178bc562b118c631a0209fcd66b3d9ecf57abd5e2f168bc562b15c5e4635467f7af183db2156c1199cb15eafd7ebc53bdc6297f7ebf57abd6a7435d3a67b9960b31fdff76f32c13965ddac59c5afc19984b308e1cc67d4e9be44776abed8d3e605f3548c69a41d4834c6a088b19539f72351284822a9fae10adcc654ef58a364505595ce3bc644a8a8b2c44a1674362229e0c1c1cf098020c5932c76d6451531a6f791efdc3f871ebda858c1f594737a55737f4290fd2a82bf7e52c27f08911c7f78ba804faf4e0e8e7a73f72f4224c7bd22f137cf0bb2dfd30713ffe8de12fe4520747fc85fc48bf817f18aecca78260d3011a80127c21016e146ddcd94b93db845e7ee5e757494ef2a55d0174b78b1121c77f3467fad105d5217393002039f22e79b60b6d1361a71236ee39553b72c6bda0f3e7782d90ffe76fa1ded073f088174a37e2141045a4d7387835bf03261d5db5d80f314cb4c0e5aa363c8d08a99f0f961233c2c6617e5a9495ee7e79f9a279f936f62e2a1cce2e958a523a4881f413320365488215ae842512981dac4a2152e67cfc9935bd5cd392195f3db882965d4a694519b734a29238cd10a50d3b4a9cda951fe388fd0a2bc80164515d0e684110310c626b8bbbbbbbbbbbb7b77c7ecd73134c890ba23e85ba8627e4697eeeeeeeeeeee326a3a5ac68b3172945fb43846334623e20b9954b434fc5fbfcfe889f6e3ff7cc61845222f88d3e8cda561510568c3fdfc0839b33004dcc6744d8e1f4074702b8a4d0c4888fa6b32b6b8c98c56de619e98468d5cfe247efb6e8440d862dffc668470408e0d46fd6b7c6a12b645711350239846efd39ee8b6d7f6e3708bbd387f886335a86952934c17babb13bbfdcff164753dbfdad0ee5f19f9f3c4721aad8cfcc8a41e3a5a850ca1517bd73c26f0e57311c6f818c2e1928f1bbd89b3c238f8eb466794ad304ef74368dd8f5df7e36ec1bdf8e09c9e6b8252e406b49a20eb60e3cb4f40bf7b19c840ff82609e3687fb29e7d4a626b97b557942a118a182d6d9c51557ccccf876010565afc2d01d7193977d7f2239f1fb8388ddefde9096ef3f247a2ffb9b13bf9f48ce694286fd017fa2fbaa83b68e4675071187c08f1e919cce2112ba7e78727e82cbceff049fab422804f6eb886a3c34ad8e643c011951086bd031213a75733b2d6a9ad464fcb657b4528f982c05ada66ec5df9c78da1c79da1c9126124dd1d472744eb7390e65070f9e9a4f24676a4588e44022392f06d07e7e10da4f6d06013f7a43a0f7e2deec5ce608b583620407b47e97604e1c3aeea1146151eef7279c736e10ef95ac11d581423edce6ef6956584714c2efa751a59bf2c43d3c897e3be93cfc254dc84d2f7210d8edb908e72f69a7154f58f1021e96ae0764e4c35af184154f502943d0bd1254a806999b231fe04f0f02fbedc3fcc827ef531542818c280b15794be7fcf6d67bf1614260fba37b5aeabdccdf1cd832fc321f5219749a37aa11b9a807dcc6709f023e04297bd3824fa7200cf682c16031278df2a4581c2884faea3170ed4d8d2fbd1be91494c91c898ca7092dbdb31a05f3a5f7261627eadcf0d541404bef4ceccd8d59a7e26a35b472d5ca575934ca06168b1379727ced27c8f68646944ec5564b4a6b4a4b08d628d3148b137f6cf8da54fceccd4bab53110673c17c4a2e5ffa56b23851a886afcd025afaded91b195ffad6e954943513b236d24868e95bd528ad8bc5913a347ce52a54f6664650a7e46a255bcd5643531ad54284c5913c355fb908b4f40cb437f24bb2d5e29f5694927fe959068b237f1847c4f19567404bcf4ef686e54bcf473a256130de81b5602e5a7a36d2a8183fc3571e012d3d876071a490904e492a4c509aaf2b055afa082c8ea6c3acbde12f951ec8de4c5917547a1f9dd2562e1367be6e0f68e97b581c8d8771c4df9f968e9a83967ea6535aab47a3b62f3d0f7b03bff45fa3baffbec2404bdf9962be3a2dbd0b8ba3fdc4ec4d7fa9f4a5e7c5d1841847fcd297fe05920ea517852b4fb10b2b18563c8cc335b1e169798a5bb55aad1fc6e15aa91f98a736990c2683c9841887d34141211a1fca3cb5c160500683321894e930ce267483ceca535babb56aad5a2b1ec6d97e72f0d0b86ab55a3f8cb3f1d8f003f394482683c9603218e36c3a3544214f8960b0288345192cca741847244483ceca53a2566bd55ab5563c8c23faa9e169794ab45ab55aad1fc611f1e0f881796aca6430194c06a3f1a510e3887466904274c260520693329894e930ce14a2d159796ab65aabd6aab5e2619cf933c343e76ad55ab5b41fc6993cfcfdd0f81acc539a4c068369301aff8513e7448871a60e3b8cf184e85793cb5c2683573bbec75092619f269f0783f7820b5e8c57f2b82634be0ba7955fb59e6bd1f8312d903c19a77392bd4cc6e9d0f82dd418c1c0846ac0606030211adfbbc1d9d85a3f35b616ccd6da5a3f34fe89c666da561b0f8d6d65da56a56db5f1d0f8ff2272916d3a3464265949b6e9d0f837644c0d26240356826930211adfc60cad45d4fa91216a9544add60f8d5f434a178978442bd14ab4e2a1f16158628c2913e97031996cca443a34fe0b920ed2ea401326a4c16030211a9fe35e26939cd0e8a4bfd68fd6b57e687c6e738191746a3a573c73355773a5793c94721dc7a2d1d1ab24249a4c87713618e388ef5593a993d1a9434948609c38273ebec11887936271c48f5cccc4b962b0347183d1f826d20e4d5f7831051df41489e532f165b4811a157d8a9f3a56a3b6272121ed9078b86fe4abfab9fb6ab543538e739d38271cb7f21417a5e37e68fc158d9c6b69e2378d29e8469b6c936d596cb346cddf5dee49ac0e80cb8cbcc5b938178b73b168f456abd5a22f4ebeda1ac1b471f4732e4f712bd39c0d688c31493c9ece53e9446225d1cdef8f4f6235aae4c56fc1425799887efbedd93302928c346a494768946114258905db492c19a037cb105756bc971e205d794857bc9a811e6a2a2dc3793eecc34f9a7f18d65f8ca41d4fed92589e621f9fd91ecb3d9fa104e7a453de7219ce45e37b4bd3699446e3b38fa74846484868941e89c4a2716536a301d6786584fd9951284bd1b17163159dcb8e1ce4ec3c4e93f06b342939fa6babc209460fbfd670dfbf3b9c3beda0022d5a66a99fea02eda0022afdb50475753a644f08531de00bf4209dfd2d31fa22dc88d37eb484e88b4cd19c3f5a62fb22a24d24fad112a42fb29146bf71230e8af8dbefff42692256b3509a8842ab0eba3f3a71a7e932f0b793e8344fda299ee4af17618c3142f8f1618410bac7d6a4e0fab9920e2e1dd898982f0857e1fe3c12e7dceb94483307018a0f54d40453f24a04babbc36d09dd7eed66330cb855515e7553ee5e47827653a1dd5a74162d3485f6b794f66faf57b53595cbf0cfaed354aaaaa928bfd62aca2e6e458a24282341f96b040249751ab5b238b2e2952ba40daaa93c359ab212625166662a2dca2acaecde28a6eaf6eba662b85ed274b8ed28c7beec2916afeb6f5342b72f026e281b6842bb79e0b625c0710bfb8b89f107705c894733747b85b9071768c9493c8022f74a8ea51931d1f1d0b1c34e91dc2b4cdac1297b364bb39f03c29565a7e203b333f12e890044883873073cd0f573ddde103add657729a2073a7e8e3726b1448b6e0ee8425909512e7aa073d7e469997e484547c75dec79073a77cd9dfe589dab51fcfddb8ea7e48fbce53251a8179b523ab5b966ac5170c6dc355d9b8bdd893f819ebbb6ba2b4b962c59b264c992254b962c59c2b3d37946056e37b622e8160a0790d085c2012394eb91694a27a5e28c586430cfc281ee0d1755f68b4aac8c8ce801ca4eeaa1c5d0acbf289485c5abb045a10e1d8201c3ab1e4461570401c59ed0aef3aa1fa15a906252900501adac78b563b445c5cbc7d5848e465eed1505bac24c162444b78d91502da8cad04c36a7cf1062b0975c29b486d66caa2e463483d9a8b38c70a6c819f6607fb01f4321ec0cb202f522b48430862f2736658380af70c901be42280cf015cac047c3f87edf62533f80f80a9128c057b8c40f5f61087a7c0c2b36c52b1fbeba1108f0d599d0c35787020fdf49b12976f1f8ea34d8e1ab43d1e1abef20877ff1fddec4a61868005f9d04395fdd09fad597ece8152c36d52a017ced2904e06b6f0180af8e848e2f4db1a96ee17ced2a6ebeb615387c6d23d87c6da19fbd197d7f2bd954c7525fdb05a8af2dc50d5f9b06399ad4c5a67ac886af5da486afdd040d5f9b0435bd7d3f136153bee332fe38be721266f8ca58d0cc34f77d6529325fb98a18bef214327c651bd07e96c1a61ce6321ed3ec02daac04867e0ec1a67c36bf452d3417d92e68af15687f0436058fb88cc37c5d598dee1e36055f4078d81bed5fbeee0f006ea061a141fb8588f6bbb029182323e9d0363e11e0d57900cf003636a217105e9d05f06af783d76383569e121df1c1ab93005eed7a58898e507f1ebc7ac3e78c8757e70e5eed7470cda87f0e5ebd41276c005e9d395eed2803f9eff0ea0d9f3b02f0ea0c80573b0078d53477a8bf0e55cb53da108e57e78d573b1c5a43d4dfc6ab37fc3be6292d96f2ea4479b5bba16331ea9fc3ab377ac8535acb06afce1abcdad1d043353d44fd7dc7539a0a8757e70c5eed687666bc7ac3619e9240d2e532fe327875c6e0d54ec6ab260944fd3f874d18bc3a3b177c16e3d51bf088a7e4ca655e2d78f586c9f3aaa93b79b5f3ff23f386576767c3ab5d0d0fc6ab373cfab848e6fe2f5ebd6172f1aaa9a3e1d5ce04e70cafceaec5ab9d0c8fc5ab37280703863775100944b234fc4202500c363414e3d41486e8c80c5c2dd18e14294bc3331108442b916ac8e55a1aee4e4db915d81541403167c299589ab983031f574b265b1a6eba726aca8d86a609266baea6eae767696252900501f54eef2c0d37e5b616152f1f17152a4bc39a4a4ba21dd158da0a08e8d47403bac24c16e4c48948bae40f244cbea40fb398b5344da55c492672471e91ac2c806841558666321e96a6a9f419420cf6aa31346669e249245b1ac76265fc7d5b45b121d1da82fa14220b76699ac253530e8eb65322998ef9b3a217f5af29ba3f628189a9c111bd18877fbd81eee7d81bcacd0abb59c81f360908e8358f56d0f5f349e42f7a6d6c53c186a8ef6b46dd06ca5f4539a869e9a28ba31c7bd30f809da20abaf586962e3afe0a0dc0432821890a942851a2448912254a9428519224499224497676767676767676767676769224499224c9cf069cd08ef676febd4b93826efbf3e1eeeebd036fd6eef6e84d3150d8f0631cf0f739f6d5f68b7fc36778e963284d34a1fc3d99f9bdf207ba971d6205b240707b412a113a699da1f025fce5557f2898f99c5db76e82dfdd4eea48d2a1a53b771bc9612344a770f8fd1f8d82f12cd8ec3a93e9bf94b37434aaf37618a536451b3722ad94ba172fcac56091d132c344c3e505a6868d1b7f9aff946b61d402a9071728c948a36246de9ab3eb4c26a7d2f193e65e18e1706218270ee227cc2f1f1795ae72ab55a398e8140e1f7df6c69d73712e29a5fc5eca1929c34b29a58ce1a5c491de4b29a53cbd943732e6a59452b6f052da481b2fa594b2c64b9992ff524a296fbc9428e9f2524a2969bc9437489897524af9f252e6902d2fa59452c64b698334bd9452ca192f650d92e5a59452722f250df2d98994526a2f6ba494524a1c925912c64b29a59c414a29658f46915ebefc8f464a295f90b27b291f4af92c241da40b30c8e0dcd8a45037e4b0a1061a6a70cc40f3bdf0bd3023438618643e185e70218677fa8711d382cc939d642f6c8c6a6c36b61adbd63d7763db361b5b8d56c965a3b1e291ab5f817971a1416a7142866c9131323931e3a7a5c5b1486ec52357ab8d9de8a24cdbe08bc6174c88f3628860c080012569b6065bc2985a1735d825d9adfcacc02409899123389c7ce532d1b537cea329e9214fa75c266b2273e2b227b4f4b0f4d089c5813a327c854968e92192bd89e14b0f559d82ab155cad206b058f506994cc168b03796cbe3a17437b739ad229d86a09b5b2686901d4a8b76271e04fea6b9a76a8c4acd94982dc114900000293160000281010870322812ccaf350b70714000f6988406c5a349607a391284b71208410430400000001001000630064ec08009c97f867bd67fcb8afcf2ff66afd322deebe4f25f6bdde44dc7eaf67d465339db6b2040345e26573d436295ce1416a1b131287a426f3ed3f9b752b1eea6752d6933374976730fada0fa724ee278a7ecaffe7d6bc6911cfd53afd623a141d9432eff927cf212678b91e58399f5ec73a15de704b4744ad98cdfde47ea55954568db5fcf85a8d048ea6aa983d9a8d3893a1fccf29baf956312a5f1471e1bf2bcc608b0085e980730aa150737261ba9e95539167dd746b758a821c96fdb3e8f4ae58a9493e8e743ae5bbdd4a4d41c35b24be148d8fbff5b72e051c296a6076a3a540b1f5cfff244bdd4c76ec0e5075a51898a5e8a4a1bbf8a1bfab5240a2da81be3b526a88fd63434340e232fcb0b206943ca9a17b2c297746177eebef9a143052edc06c4b0ad4701496abc6ebfffb5f920a70ca002de88ecc3946aa7f80ffdfd019d036bbd727ecf8200aed87667cd421b1c9ad7eb29f1b665c577689cb6b72945672854662eb62a59133ea96a06a2950cc4ca7cb846551fae94541c384feab08a7d3e4d5565d95bcf590adaf089876f741bffb90a4f365362b9fca2b1ba79ac91d37e0cc8d5eb204eb344d6261e4c778bfd0867a32d24d9099f42752adb54804208401e71a5ed2044c1ab40553ce7a24194e0a24778be5a60a197130a6eb6d4b113b106d8541915f31a6d3316d561e7e3c7c149305b64ceba5bf40ea690de4c79f16de39cf6db304ffe725a64fa101de82eea21407f0f3f9b7a0085d085a991bb215c16c02a9792be5c53d884d6479719d13277daa649af298ea2f98391897e7fd273f00e2798f30e8bf450cfdfccf95f301e384e2d4dc606284736380de836ed7786cd5c7a5ea05c8cd30b3a7cbb052dc32e6f21b2c26635c6f80cf14637da411150346cf30017d0a8c09d4e6a473c6b8bab9e8f7d48c63f97763eb98e0d202bbd1af9fb9a8040df0630beb78f74a0be167727ed79cdeef7f3ac43bccd99de9ad17d004baf375a32161ad00ada99c094b97158aefe52a033a2077ffdbd873fd470d57e3ebc996845732eb35009f36e69299b25ded7dc26cb9e8654d0fa9ae4bd4b682c901da084a1ace9d8be8b8b1e6a8e2704dc0fab0dcf12d40a65720f6e6c8f21b88baefc05be4dc81a3c5831bceae53d7c06fb91bf76f803fc08e143c6ea3bb71f6907a873fd3901cb55728c405ced6f29428a40562c951286228b3a81faa54e6263c11c4792bad16ddfd4dc941add16a222719bc1a761a64837599d863edb2d83a46eec7da60de8d57a6a199c38877df5b6480f7e60ccfd5906a0782c6763b597ab376946b8506e09ef803d82d2201c8de4ee280bc63d080357840f00ba72438fdc8ef75d0e1239ebbc4d0c4569f0fcac5ed4ee490b898a2c9bf77d8f746480923a6058e19b83487efc067fd6dac1dd856d308aefd473444dfc6c2c7e0e5b6c8a927bb6c810dabfb48c1a4981b753277a8e805e7f5f69684ec2901f4e11241681cbd42759ebe3e6bf72cc2ff691f7528dac7bf91ac7f644371af8c0dcf06499aa51b4447b0dc1f4501f281f30aa379bf980f64f2b8038612451cef80c99a7243499ff3673e0dfa8bd0fc460f490b6a581acc28780b451bb19800abe6f15fcce1d8c298b8d5e7047332684a12d4ab8d51d66ed1c8c4378707b45ace6a4814136791d910d50f767adb029be359dc6292f3f7f8f1d1cef1644d9cda2c801ec675deb9c8bb285127f8b40a21bd1005fdbd8163192e200aa549ecc862420892ec0cd0df4fd9a4dc8126df90904f21eb7105b7361d99119a5d9225605781b099132fff5006890e39bd3ee3199bbf8343d25c7612fcec1853d1dda967ee927dedd5d1360434ae515798d0fc4276b4b9ac06399be057c43b551a9095b5ed3a02e92e31533d6832759e479f9d075c0f4dd683fba193d95bbe8c45f7520b46d4985e06119b89dda5a9a40efa2f78b7d4ca1281eec480ee6e14e5be5032b909543090e911ae4ad40fed928d4c4bacd365a49769114887f4b862b83692d4db9b8f0e2d8d3d3571c480fd02dd7b08d1e3deeaf334a46158d12f855c8584530b3d2d6e9beed98eb8235308c2e99c2838e446e755ac7d5fde8e85546679e638aa11c9302fc2d8bb3d88593582402632ccc643e711e118c9b4c13f43e61671533a44fdc099b6115d219ec124a9ae4840713570499ea21192a5beb2319f9dc25847dec0d57c000cfc65b9606c4469416ca45241ccc872416ce4f28958036ef5f65c546fa05b46bd3a7ef11d25ffd705ddff7b41e7fb5ed27f3f2ee8bfcf255a39748b6ff0f45aaeb70f38bf2d8f0a41c4fc38d17dc2e21379a1968f2ac6e3430089aa5a32c1a0a03f9b3a4a1778158f69240e58935893828059139f3738a61b1afbe360c3e6eb52160f3c75d82583fd1027d0376b46733116d31ab3bf2610f61701e6bc0803e100274b2633bbf238b3e607b8e93748a771f3267e13754478ecfc711c5b926ab8150716041ccd854242e1f8d971f3841e3fbb838f75942d6af7013acaa54d272d95632800f792d2105d09639d1f2b6483e9b01acce9cd040d3b3640a9313d023d43a2b0fa679bf4599ed8685efd7e2d91ca668e96794f390350c8dd02419b66d56b719fc6425eded7ecd1fdf6ec5910fd2c706817da51fd547bc3be2274f97b2662e36101714fed1213b88c606551a0a8acd90a053ccce480819a5884f5fb8a9d3888da4c88e0f73e8c7a0301e2c90196e5b3a843e7a572bb47c12ff269aa50e9a9e5ad9c8a4b63a5f29ec909038336104898a084a56914179b090e2d6d32e7a7587c62a9b778234cd40c27b5ea43024208e526e89864cd656f20297ac5a02da12bd6163ae4ece23aa9e3b91f8965d7dd1ca534168641d0ca721136d13ad1f9891390a1dae45d5f097054b3479a5bfc4a427a08ab6bfd7540129d6d9ada7daaef0b2d4d451d2772442abb81ff216864f080db0911199750c3016b82feec575aa18bbf8cd873c99293f21e43c2641a56cda561696a3a1666a561ad26b54b1a45eda1ea4a056dac89f4ac9a4bc392699aac40138774f9b3c8fa2e8db8cc6d107faeaff4c6e557278c71175dc0568df33405450fa9950f879b0377d2d86017a21b38e4fa8da50a88732e9c951cad7c5c986efd1df649a0a7d4c66df56dbd3e69337ef4d3918e32844bbfcc7b48a13cb014cfb60c7401732e9631c33aa79abd05b2874608830f309b4b3bdca92efa0e2d749d57da48ef8f7688f718d80eb4c883e96d4dd06d61d93ce7989959b7689ce3574bbc2f84b30d5be486f764a5125d6119fce71c9b4cdbe5c0f5a316ce0732a98d2dcc81796943b3cca16cfc3355cc6cd376c0f9570bfa5304dc8d59e7c0fd6ca38f1d95a922a9e1997ce775989b8802c673a6a0e050b9c7f8e70ea715f71af28cce67461acd009edce42a5059dbba58c2c9b88985b095742204b278b38d1221a37254627811cc20b0c30a07fa58e2768b95c9e2b6bf567196d5cab471b13eb6b8656dcfbca0bfa9a8ec34348304ac53f6ebd150c6e4bc6b4728ebe5404a5af3eec99db2637474dae173e7c729dfc521ba0e3a2f37c44a6897071a10a138cfa625fef30843f9a52fa68127f2255f22462924b655715be64f261c31e0e6014c2fc02f37677671a35d71fd401c65432ac7c03d33a062a3de6190476f43e44b5848b2c17a050d650d8119bfaeccf40e6d0a3ee5481c58718fd51cf4c30b28a11c42f66fc79c6c0d7ac5e8cecd0a2e83a15ef69d8fcd77344054275e93509687aa3c030f2eedc05d2648595eb7f0f90b18a1b36196c5bc0b30e1260f0b3900826eb56b323deb5e704ea63baebb0115ee1ea59fcc80b73844d094f0cf7047ab4f0332f56e8e208065afe7fe632eae2dc8d90b52c685f8cd82cc51378f1af483c4772ab3e9bdc600ff5cd05391d96827923e316b223abf67ad856feb5a2e68d7f0270d0da7947c21954c53cf36b15708ef587deb3e1961aec099b840d7d760dcd1feb6d06d2566d6ae8e5dba50e675964597055a95d05d22ddc3318fa6dd7111a7e646f5e8e52483b360ffece77fc9c0b39ff3beef538f6f00839ae3cf6d9a6cb2be6d0b934bb8a26bfec45cd7bbad6374915658cd9f98eb7ab775dc2828d2005b819fc613213b55dc37c0e457fd52859514af1d3586026e81dfa513c56ae4e1efd0c12df0bb74a2582d280e59b02b996e530fb874eac52339bc32844f82e3c652dfb297e782ddaf80b30dd243ea353a1a70a51f181f96d59664949ae819f28a01ee9d03c57d2ada5cd91fdb80eb404e8297f38196ddebfce5cd969b66b810ef864188b83d8841c108070e5e26bed94a0be52bcfa55f47ed9ec7be9ec9947b14712f7f78570786c2e8e935e7906dfe6715fa08b0d022dcbcddeb0239cfb4a766cf5279884a8c8f3c2e68a63672c57f14faa4401b01bf5f3a11aadc5f9208826e86d5f716ca828330e4e9c778b8eda852f2a9f09441727355ee084246933f7bf7efb8da572fdfc2d5201d6992212a616ec498649f08267690bfb6d4e79eee80ae5ff9a50d3a6a06b20fba31a1d5025ce28e6313ba86591830df661cf0d4a407d4ecd314cf070c206361f304f66dff2ac25b40e380f0f57d4e86934f66743c2187d60dc34a93b07ec93e1dea1070032681aaa13a28bedba481b4448911a69af418ba5baa089983b5e80c1bf822b5b6d92050a2e8f46f8d1acf96e3c46c36ba638d866e42f3517b72a027c45a4a30d21aa1ef79fbc228c20ebab413f3b89ef1793f97a5ca85ce9945832bccb8eaebf28ef0ce8f28075e5e7f93156e8d9e95f6f2e584f67feefb02a0aaa7c39d6be586581fb7114cb04f37d31e78c521504cd08bd02fcf7e7349cbbade47e34e4e92039e1d037a24c1f96c663d64cbd0d98d6526c2035895cff0386ee19cc65f740f02b4d9ae0e86458f8a6b06ea41aebddcd8643485bc8a7da64f406ebfd61816ae6b9c065f4a7d2d53d918cad7037a3645c3179565a833bebdba4c7f303b8b144c5888ce313c85c07d965b0bfc56375138074187a2bbc0291f40fe714265b3006d21f79aee0f251d790b7d54bec15233e544a6233943baca15656e15716ae0f1f38d89a4957d35b37dba06dad94684ef0f2af3ac1f4ec6123d83db48256fac6010ea8836b69d5b022357e8056c8c213ac4a759e8cf9cc7680731de0824dfb1b2846b5be1dd573bbca0b31a8403d475f7fcb4939ef1c48bf9a33f78895d92bba15bd82b954bd5b153ccac7da81d0d91c7fcd8766f98da96e50d3605b3f64eb3f75b8efedc2f5fcdd7d905e00c51132066a2a8f4fd8b5b3a73b49349504586cc8089ac17baa5b4761d923f58a51b20a3f10af2064b6a354d45a8e88a13c3851b6c0b7f3ca3879aa56a9c4e2fdffcb7188b6910b0666a3c01c85ac960bebffc66916e5a34df83e171a46063541a9db0e5521ab076c0e48b60ad16a13b90934fa9c718990d37c889b83bdd8c1a3fe39d03688bb62f5a875d1356fd6ec96f7e83181b4c0dcee9d75b8f2c0db0df167bffdec765aa382d38b87465a9b66717593aa2325c77a7957a10818348284162be0cac4a5573a7a320b129e290e82b8c84b83901920d704979651999fde1d851704f83590813cad334ac9f6e6cf1d1b33a9dc6bdfbc46013031a31cd65611602c4577ffe254d278606fde836e30904b6ea76f4de1f4b4286c5c44ef39eb0cc4c2cb0f76bc05a6ba1f1c2d4d434951b186252d3b43a687c87c413b4a5082f24ff9d4899fef667fcb90162dcff68aa3bbb713963e2fb62fd3b9c642ccddf374cb5279a5d27cb5c84658f3a8ca662d60de8a68d01c961f18829bdf89acd14c16c273e09e0cf9b43cc64933dba0c6289886e815cae26c3eda7da8b17dd8dff73040c108e2928ecfaa49471f5d36f7d92a7c2af6c12c371dce54ecb39111f1c377f0f2cc3c2d1d90dc0ccf291ec091a8ecb2232abc29888f798864542befa41a7ba7038ba28360274cd360dd2228a8f063b8a1a9e6ec625344901715e2c45edc22f89708e443e04b6be2370149a1705bc3eec20a592ca25d39ddefe1e88fb926444ece00024952240d789e8c5a448e3ec30120aac783dee1dedf5834bea898f82e3b8e58cf48cc9ed354394ec90082efbb1fc3f174f57591b2a9a1bbe8a12bf97ee13c5b7e9a63c3e0a9db2d7991b5698c5bc93293e1c9ed68488341f6daef684b38b549483891cf391feb36a5bdca467adf90a115ef1d8dd7738f573db0d93c1556930afebf197b6ed6974b5100c165de4dd9f966b4c5e9e31cbced41b6bba188757e01e0c354f42a9b23e31aa5222722fcea7ba5ecd6a63b05fbe026cbd8c201ab3bf009d18d7c9ab4e758d050b87109c8c4c2ff6f2885e243470717b7ed3e52121ddaec10c1f7a5a611cad184185838fe3e3a896a8011d7c6438946a899a83530b9babcb9c29638582aeb53f1b743d709fde074666b3b391a75f7eb6a2aa47517f3c59f0b3428faae673ff693bbe2ae9cedb98d1ffb6d954dd1a7240d510b03a0df7e4853dfb9833c77fba4c238b9855276da04977582e679ae2d8f07ccb5f7453e2cb1919bca2823d05a6967fb22e67505e5031eaffe8f81b97e2c8e19ed657aa233cc20cc6d9310b5dc47dcea5b7102d1595a516c02a52518f04f2548d82f451d6f1ca4dcf472859dea19b93473a1a8b851bd7771f9e808075ed5046218c2dd72b549ef5a0243a31f77f44eb34b4544e6e14a1ee29f5658be13be37062e3462b97e38cbcf72781de228ce52f8509d9ed60e26ec9e3d4ac1085c367ab8eb4c049c26e2a07011087c73310c3bf27479e1ee2d9fc9a4447ad8b9c4a45895eab8ae7f8f19040e2cde2751d9fc92830ce57c34a92f74540017491659ca8930f92f145b748cb6f6d94f5748d4e97ff3b44620b4b8f4aee410922dc326799f4e63d9df983671b65e3a0ce431480d35e400456814262bf61cf99007cc1b9868ca00354a0da2e9e81469f7d2ba2d55d843a670a7ba7b49e73472d875373d9b972e94cbb030e2f54aa35ad649f8f283df9fd6f23e294bc0174290baa09c02925fe46d01380d7288134890f23ae9525346bfa7fa6f99866444809c38301733841df5896959678de1ae3f44d47004d320994727ef045070f3c044424e35d4a2fd7b849f66794c1dee9ce04655a82f8c24ef4ec4ab62c08cc6925c80badf2eeac2ceb45f06ef983a18d8034ba1dc724672c7af4587e75545337023881ca735585861dec978d219d695b75c271bcc402691b9b4824936eb5d35f7a0b87b3c67bf92a8cd1dbe63e23930b30359f1b6c7bf468105cb9d83bda0f066bdf4f8df39bd8789e03a0c9d720ec28892c7effb828d11ca08516c2dac8fc85f821bce32c6d5418fec0d54a835bebbcc02e6bf589d39bf801ace6d461e46ff203a574f3a85ef475b6e15232fab39f9ba3d20d3d4b78af4abba7c180b8d3b39c4ff3283363e060ecdc16a4f130795d3c39295157ef315ed0a04a1d0b4882be861cd9d00eb8f6df74cd8078d5e41ce5994ea7604a6c0105392b9d780cc91a197a605317041caa1e0a2fd062402c9d0b478107aed0e737f3b817b64842662e2c1fe5f3d39b8d425a84d4009c9caf3f4ecb3d179e2d06d121f1cf7e71bf5e3c40e7d70fb9d8a634b35a6cad820d3355ed779f4b19d7b7376cbf68ad628b2163770de20617c7f0b1691894b4be79e8c7c379fbddb6b9a910e8f742d74d8b7d1b21117506821176f0389111a9fe595537fd826228607002b362c0c2575b1129fdca3b20d416765812839a1e1aed5844e2c5ad6240b09feb9e7eb3221f8b7b5708d39229070f7f5f0d452f5b6a12e753a3145ab4f508c1f41039c4173c18b39cd831348499a2d30567347810f05653522514bc8f757a35038ff67164f1dc962ebf141589515e4831f663d628f22536edd56e4f122ec880e3a2613728a7ed7f01de21283626bae357f8f8b4a415ba8f6345a788c3bd7842da5dc53639d3acea5501a989d89dfcc09eac952aa4c7b995d5f3fe2fb1762cd207884f4b1a14ca6cfc8d2cc40ea3fbd9d5d143d29a082886dee2c30c39c785b2e08500f31be99232009226e02b88112c28f417d7b0c2e17d0a81f3e2ebf13d1ab451b2fdf2c322bac41b6388bc822f4c83ac4572e67dd770dce56bbf7e2c3ac5c1570699a039eef63ceb31bfe0deffe5679af4741b39e4433c261545d6c940e8c21412baa0f86a7f4d86fb7593e81ebea99d4516c49b9bc6e47d7d91cd126abd5ee4276bd813c402d20c07e5e5d68674afc9ae8e79865787a97bb93668614fc4e4211b8b734fd6a99cdff659e612f29f370208dd01607e7de760c5dceadeb8d00d0d1b86420111d78884c03fcee9ce8f09797da8674ce504e1f9bdea79b65647e4fa3a6ffe4600351db14b426b40ad1a17d483e1ab35be02d23ab58eec7f1aa6f47bdc90de2bb9d0f9f316fdf676800d2a077f6c8cbe6129f02710797ec9e90eb73bac7ba2792d8142d40174f362fe6a8b7e72884e2f60ad01ae627f9340809695d250eae4fc6fb0e2518a5165899983f0f234483317fe8d5abfc361d18ba83b064d89261e5a9f85d88f16bda043a2d8ac7402330a4cc0652f95a6bd2580e12aa1d225aa421005cbadd6938f935a0f282077e2db8cc133a2ea00fe31e530d4cc0731d3497b18dc0a05b26221e53c66e984ce1687c8d820505ad309a698d82a0bbae923067a3958d2970710fa39c00dc9ab94cc1f20f4f32f089de90b4b2f6ce74f5c10b4191708facc7541dfb7e7554aa70f50bae99785dea5cccfb820e833a70afaef6f2a74a72f2abda6fa2f0bc8ea355795d541753387e761f5cbb0fa076ff08bdd08c183ae6ebdd749733b46cb600383b4f51defa5a1b7f0cd65d57d9d526c0c2fbe9ad2c6d2f8bdba3b521f7e2097f678d44515ceb0518447e71363047477b12ba1a7fd1edb9cf05878273a88829e6eec1aa2b085f8464015721179ec4ec348d0831673a33d995a9e375b772b857379e6a5ecf71d6a21527c0e6eb0752ccc1e81e08ad0da2ae1ab8d4c608746ee9b6615dd204617be852391359eddcf627625d68974abd896f7afa1bb7224025684dd0d1482917207bdddc855ce4e5eac9601145684f12824924157f309583d0ea060fdfe0edbeea111684d9ddfc6d97b54f4587b9a57d52389a9a04a579242ba6a426560a92851de30cefc6926ae2047963ed1de0e01f0673b9f9dc140364307daa2774c7f5508d2b94005d5475b0cde542c00a6ad7a4fab12c6bd1c02c149f0a739f23c66725f2ff6b2e9f486526c4439249c414174dabd807ec8cecb16c83e4307c1c72e6e1db82c15853151e1ce7defd62563a8da59f2ec333f4fa7970a0bbad05c98a6b3b401f0aaa6db83c58bf933a880ccffb554817e5aa8467889b7ae598fea232e81a9169954c1ad79ccb2e820886c6a6cf4fcf305c1658ddac6379f80b68a87e5c3432a993b2072afd54651ca44ead745af6383e6479a68b7b24f83784ae013f13d874e6b77c5549b3841d026cb1d78af20fdefade6d8f43c9edcb53bf2ca70b6f6358fea531769aed84f1b95696d708e9a59b509d25ad9792ea8b073fea762aaac6dc1c1ff72d4506f0de41aa16a185ceda5f48d19424b94896eaf6fae11adfc39e7159c688141edc6864f6cc537c76daf8e602eba54152f9b048f041ff486a1587392ab97c0b37030d8abf0d4ceb7fe6fc0dc6bdcd8b8dff3ce7564a873dc7671584311f1f775c6423524658f264acb64bb73ba12ec1c09441d3f309cb9d832663c1de50b7022a4a3483f94d52db2804a423596fdfb604dcb64853bc917c237fa62e70d0a5818c07a4ff4449c5edaf8cf8a26e1f885422109990c20be23a1fa895c38258758805f7928725d3c4d69f7bc5b3f65a2669de1da996b01ad28716f8ff7eacaa4d673e320fe3626ddbe9bb5a086476043a730301521591dbc19c96e8f91f88a3c92467058be4479bc3050d89f30e89ea4aa7911dd0bd4e720f2d38543be2e1c823c14e6d317d832db1496ea43b58efac1194901efb914a662a873614b61db8c28fc397a13a4e948fc0b2ed64a45662cc689197a3b33474eaaab6efffbf5838cc8b879c914536f6c50aa42c01805a20bcf0fb9e9ac96f7d21b8a38b64098b874fb7db93a3a05501601ab4cb6b072fb62c8d5c317cb435eca0c00cfba1c6bd4b92ad31e051d71acb8d664146362a10063fd3f3e68b79357a3cfc67e4da78e995c38d91205aecc33a8796982394311be1fee38a97380b0a21f4d583d600962ce244fa83693bca154a89c6ba61031dab65104c1c01c891c7db96e7b77488edf703d1ae241aa8c1817777326c83e6be99629bc1df8235d881a3f1088855640add9df7550005aa94e57470c36ed749e92873fed8b75d9cff309dfe2e82a3f196cb69c9d128ec95f4dc225e8c467150ac12c5fac8b5b0a13fec98f28146cddd7a3a8a3412da8a81dbe600571d2870b90059b90cdec9ee13d1ec93d382d22c839cc2ce30755a4abbdeb90da50e8b9624f6fde99e33143e4795c59de84bc41b7eeb1bb26b1ad06761cab308e06976c00da1ad157ea9c58efc3e1737f98a0ba1ebcba7b266c61bf7428b44294349d2e73544f14b4ff478a681a50263db81c476ad9f045aa016ba62b569ba8dec23e56e03e39d6e555a8a6aa1ad218aed8ececebc98ac47ec1176d6299491028d1b1618c141d41f6d049e4e667d8a9a943b409652dc237e2d6bd01bc67648ec1a5c89d3aad225b34578bc00694b05ba839f9f82ac69dc64987bfc34ee16545e9283e69cf1ecdd52a7b505bfbd8ad85bc6b5956839065c8f90094020112cfc017cc577e7506748b8a389f1f1ef77082028f1d4082b00fd8808c398c553a6f7868e3e069c4a923c8d3e3c62aa894b5bc3d2dc93d2057e0417f4f83d7329806caf74e3178afff69105d2da188b7ae7753fdd6f538e84831616501b843f8160ad909438ba2fcc1f3250719222635788065939ab4db64ede759c9c082a1f2d63b11355df7f0f515466a45d2083428959f05a8fc83327f43fe60b8c2f2d3d9c8e59cbf9c4b30ed923f6e73fc72d72d4965cf57f745f9ea613a1e894eb788660367c00a6e83ae313165d37704ddffe83c64d5329a09384ba24fb96861106af0c780a18f9c619188d2c00b6f5c61ff7f71554f6e97624dc72301e5e2830a22d08298ad280cf278d3902ddb668c71b1dd4b659e418a59aab1a1209f62193730fa7c287531a9ef6a2c91b3c75630144efc14f69d579e397128a8a6e8c934c10015feeab555e6096466f4143f1a2f0a1db1e798e054ac1797a7369772d65516f622fe8b10a1bcfc0ab92a766ffc094337fd50d08406513e7cbeb9f7a6fe3713500636a4206fd10cf1121cd26d0354007752cb6994a0dbb9b78ca5afd6ab6aeae62a8ce245c398f44badd4bc00212f1f5afa3a91d441f287a5077d2b8defdb3a72fc7d182911827e49271bef95fcccf20d0deaec1ea691fa2a4b8bdebab10c2488b9190628552f34a0966c3066493923fdb18d53742502d0dbcf4cf768dc173065651bf96a777c1de86f9a0a46d86a61c2e5ceb10375d5315474a0283e60aac633cc6948b98a6781caa0cdd94256a2a95e1a0a2af1bcedc878ce15637fd37795acc81b476016b635aa4574475b70c5068799f077b9f4092e7776cd319753462ec248656c24bca34fe75056b54d9b78655086e4b9bf0ce8ffd04aa27972ab7145398a6dd399a2ff3451a94bdaed499d3999055ad5e814355887a1aa884abbdb9f6256e3758ced1caf1df3e777c1e773cad34c0870fbd4c8a7b8cc0df65d14268cbf960c462a38a81339bff1c63934eac839762aef34022c8772f67268d457e631e6f380039b1255edde957635506cb90f305c999a00f81cbba61eafcdbbd355ea313e187e436768436ddf8000d6b583ee22be3fd5c8a9070b488939242a9b1dd8c829151904103d3a45701810558f8d7696c1beebf2f62105eda85a47ea1ea41ee38d14112e0456fccf961985d8ba7f9a5c4123ac30bdba889a034287a804a7dd0dd078671bc1cb40662a4a646798dd467380799cdfbf4b03a78aefee23d434a1dfa62315f12cba5d1dfe9e3bd5cd84f3789a3a2267345b300c3d9f0c0568a7e5f74f28f90c15ce8e23b7eaddf4a0ae22d306605dea7f01b5a05dd82d08d69d24af43d63c89480737138e83756ddfa949e795aead86fc02ebac52439d86c366d4a261bed684c0f94ff4c7edf6f7c715348e768e51e81987bdb3e6398a694f61569063c0e5d4b403385aaea1f12be45a65ea41ca12597c6e4976db33d04c49e4e5f089ca1c94258b1c4cc0ea26614099ac0b186eeefa2e8c220977e55f1a5f5813828fc245377d1eb91372720bf08e5e73569fc3254a3e60de0ca493a82cd2187b7fd6c180eba493e5bf8a42e236248679f8fcfe95162e2b303e0ed0b33e9d5fb3650741bf7817f167c6e20cb74274b820bf251a77b74254f5d95f6ea120e72915a2a72b83d5d8a442fc4d214b2d46bd429ec0993626887d41440b1e3f54abd67d5efb47ecc0f41ef14494d5fa73017e3fb568bd534c4bfebed50a3c4a8b1b2fbd8da97602150296b60b1b497acae81a5b74ccf8047c72ab236d8e1c1348e255a1cc02dda55eaea10ae7048b46d8c558170d59a995e5f87a38b71efdced21f510b6ed87d90fdf0f840632e029352d2d94f5bc4aa90e14c813ad480d9eefc4486008511c816036eecab2fd6a769f988dbd97735a07dc439df40decc1648e228c7fdb9b3daafe4bb739bacddef19934f1e568ad258665a021017446d4e3399890d60a988b60ce46ed94e62c450730598aac47f15d52be3ce77b3749c34ce021342ef81273a1955c41acd5a5443131a18492fd1ff0762739a2ea3dce9d632445a79257de2b23904c8d448098d22c2b50369822d443a938c57cbb170419f63699950a739a6c4e345d148c12da9d59ffd08dde072b9d11ddf2385ffa522ad7aac68900503b55681010b9b19c23e2b98313b38e91b382f8c421439919cec059653dc85aa4259ee9cdcc50d0b43854e8a27b2ab343fd1578f91da6b7c7bcfc87712026d18f4e14470ecf6daabc5e49364504dc9768d7762fb72100784736ccb9181cf66d29fc9605fae0ddee7277bf972ccfac1741ac4e97b5a554b9de3ca3028ff1e753aa3602046ebea01c3163dcef92a27ff8753c6f3f862964e342c349a4976c4731ec595be7d03ed20addbba14b4f5ceff96371af400dfac4d8549a50001c9466b9edce8cd11a668f83019c1b6613363f031c1c15641f35055be4067840873d555c92db878374492c0b041f0ef5cbf1024407a394c19cc341c2be32b38ea605933addde716aaa4e4ff4540b76258085bba20db151ce11aa5a9414c2e5db70c4861ec8aaaf28924564a1c195bb48d5fb4440d591463a9a6a085a951287e5279f7282dc9d5d578d9a87ec0b4efb6bda45e55f42c4cf4e4a2be6c81cceea7a272db3615a0a2b6eade96f6a763472f74138ee226ae66b526f994680f1c122bf3314b8290e19c95be58e92b370d951f944503c69e31c0cfa713c51d6092b983fc705f2761d99759e16d95f6ae92445d8915390184e703b4aa36156e2b16f40572670f7a79a8f8948408fff1f86d5d212fb72534c315cb9e1612c0afd9df9d0146f612a9ac01f9d725e46121b4df22342bdf7a2a60795c0851d7e0636a951568805d87e02847cec77a1bb4f17f7137b5415b6edd91d84bab4db260a6596403afb6cf44c24f7e7ff1f7f6f8793aa8b7ccbde7fd4b9c7dece32b9877116243d8a3fec6e7023f29efc278f8c200687c09fbb6d8ba8f92954435847e1d83d722b8c711e3d57d66b460730ac5e4ccfec33a44ee185840e03d4cc338032ad9694d5b443249049980987640ecab768e47de92cace4830d186edc19d951701977eedee56fb610b85efc6f6a578d822e863a3309a84874b2e5f6ba74a8b14ac6f642cab6ca985367b3f7ff44633ccf6a1fd9f934e5c8e933b3dbdfe069d57b614de0548ce8573f571454d08202c58b532c2016fc8ed2992d58f58138947b87004a9de4af61284bfce5ed25a69c02802ca7f42280ebc7df8861eca3567a0366e1f08dd5c4add8cddf0cb298d631023ec6fe28234b035411054038747277ede7a8fc6d3d183e20d20c42d8a0e27eab284b81a0f7c7ec49e80166434d39b42ba9f35f61ffb02cda2bca3b96c38197b722acdbc8ff9507269ccaec507f1441150fe27e7000047c51e1db7985b29806bf256fdd5c2c5d05e8f6019516e3e8370c4715b1cb9e998206c5a5efe29193d4bb3e14a5385315ad880664b47a18d5428388a1df7ac8c69a58ffe1f5bba9feec831784eab9d0af80c42d944f7af308bdc08890e9f2566c76b7d77dda4f603007002f403993a1bb1bb9f1047338bed713459881f3fef5e46542617a27b8b4d0bd0356288286386570c3ab2aacf120ff754f8dd5700b52125a70bcd2ea454b63a8601af863b4e4b16f9494a13b3db36b19f14afb4f48c3a654b94d6b8cb794c99ea6d7aaa7ecc362135352e85704dd06d6605c12f746e0b0ed4d33c72024f93f56360058dd31c691ef3970923bcbfd0d4a7febcaf5553057e342fe8009430b4cea4980b3ccb09da5e28244360cbc50db8924475ffa5907b84ec80d8f59cb6f5d886d6106d758ae3f1e1075f2f8a01a3d3c7475019d91260583b678d0b7e82c994dd1c3b10dce7e2e8c5a57aabdb1c447404233efc8823635f2230603bc2507a6215894b0fedc7ef596d4eb9bf91d8f75601ced35a05118a2e074c33e47cac11255c27f2bd1894a8bd48ff77ccf7b0f3794bd62d4dee8ac5ffe419d03a26085f2842b77d743bada68885811cbe52387f4ee5cf8bc9abd084c3d0d47aaec68b196641f4ae212b8cfe4740c6f89411fdd39f28cd9383a16e1159f75447bd54e39a9db6244e380ed4956b281861fe6f1e276ea9022efdc84ed14f82392dd1def735a33cdcf399042e2c64e6e60b3b457e12ccfc0ad103b1ec1faff715331b64984c756abf5c60fa794b323a5a7a2a60ac9df679db751a6b695ecabf4344f79a3a54d1db16e6c29eb340113cf066bf82965dd1635b47784f96b7ad4d701c92755a523b3c449f1fb7e818f2653a6d30b5a5d10a7b5cab347bcb1c7765c8069f85bc8ffac285069a47f00b0228c3878da3819df1b0f80f912eb6c83fac4eb35a5ee7dbb10eb706b4c233d39a12f9a4e5eb90fab295df58149e0cff777708f1a73a355d1d77008f25f5cf2e7aa078fd2e0bb98db028b140b588af6db58cb82facd01653becf4dc1a2345508894b63af99b20817b1624757c2d5cac86638bc62ba4f2e5ac7dc83796b2e01859f815dadca7609798d3903028f40c7dd0679db2c033e235a61aed3bc6516a08aaa795a4535c44592bbe613d06c108160257715e5cc6902ccc148a32c6c3091f71109e7ca12023ff20a3ba31027c235681960294d02e76591d3262067a38056d74a91a6dd2dd421750ce91a8a7d284e0b87bf8f4b347a998888e28613ff8c4e2b68c9728812924ee805c3ae095b4502b0ed6f4d823cb0377f1f56a8814f1070fa2fb36455560eca1a24429e58fb6e6b8920cb90dd14668cdca2cf6744a156d37d7cb397d6cc0eabd98fa51028afe4d4885a80d5da24aec3c86d4e47479ce6d3543be6981ca152db87ca01693ce6dcddfab9e7cc3276ebc933f0df6dd6cc11cb501548e8a803cd444eba28cc98824472314336bf4164cfe5b924acf3787e848d5ce4b20b6fef831dab2fed39cafd7bd06a8742ea682ea469320793cfd82831991902d49054437d536ee383cd7d596039d68d3be29b977f0b42f040ce878dfa621b10d5c4a794c2f9b8907ce4b298abc78174311d2855c974c05ebcd4bb72b15c4c229cb5e1169394b8d95e92d2508f46d8dac8dc792b9430866bd9406fd53b8615c694ed038ec503462fe6b3f35bc99cc67ed19d6e6f847116cf8ac8255378dc84d10660ca7ac08a8ce29239a478075fc95406fb567a6d50c3a05eb3299e68db9b79c99e5d0896b7c2fbd39e714f69669d3d11b5706830df1ea84467e7a7c56ea606633e7336cde7a8a0df5f51470cecb464b468d379922b9b3bf20804010f0eb16c057c34274daf0bb11e61d90354ed8b53ec52ffeef134338b4d620da4f8fc8a4c9aeff23eb4e94145884c89a040ffc8ad941b32fc9afc04d185a0211b82d1698199ebfca75c91f8efacf55a2a31e8109444204db9dc95955646949675223b958347f77187d2110f5936d6801c3fd8b0fb383658f5fdd3b086af3ca9aa3eab83e3c128ba071c6be808987d9ee320c62c4d6a1e30e7e5c0fee0461115922916dc27c1ee785b201e2f768c6ae7a416f27ab9abda7f6ce09a2de588d192e33f524e1e6c34d31f6b989bb0ca25924615699e2930be9a36f46813cf6f631e9419e83f314e426324eca98e827a9cc43cb516e67971325aed79214223c39bb66de1ecb42c45967305931db255ead6b1e8c9611d652b978b71039c1a97831cc96d0e06d6d58da26afaf85564809d02b60cb3860facfb0dd046a1ed382c182fb4803e6074b544d684caa389006093d54c1838836f74b6194b05c23cc3a3309ec3d2ab2051b2d28ecf7df2c5f44ae8d989b458c662545ee4859c52447cda30bb0e335da72174510fd18114615cfe7d91eb009c164a5797519583284270a130b2bd71920ad14d55c391fe137297cf0e9ab58e8441fc1686f676aa891c0d3f155d59b7be77e5e99575fec8630c22d9e01da811522226b69fa5b95950899cc3aeb7623b69c98b98f895dd45577f1adfb4189afb9638dab7111b336436ad93382a4324146f5965a7329497a8c7a63d0468d86edf178222b1d187583903da2f0ca0177dde995ac82d921565496b886bd862aa413cf065ee5b0c20e9330237f57589b07f8d1ec2d7199a6b9eeea188dd5c00af3fb3882b900bebabf2d74ce7dfb82dfa69b3aceb024266b3d9e455154fb93851a7f2441a9cec5b7f248ddaa7f5c833a40edd9135aa7be5b4094a77122b344a4c8ff00fbe940724f4ea856d53ec1c840f00fd260f651e51f6324e01aa48cc449a6a086dbdde41d23bd22615cae0d76331cdb0171f130aefa380cab27d68934379648d1f3c99dee7f5db1ca947429ba2856b92b90d46aa4c52f3c2fb8e62ea762f5ef1204ceb1e7783dd1d112100a7faa9416c0df9b6dbe88f9ba2fd66529cdbfaf79aafdfa0f18a0a0e10d6ba657c078e93696e0f07562ea4e834f6098e99cef93845ac54ebdc5f6a6d07f1221e64c133ccc81d5ce740e6b480e9600eabac753c09d0093a6ba9e4cdf1f087b383e14784dacb27add7319b13a81239af43ea53f7e42e4d4944e40a0141d03759bfd3b1b9baf102cfa040d88728268b888cd146f753a874ca94816c9c9ad20c2a11dac66a7c0132e124b2af3e855089ea6420d2de00da5391f4b623654e1ea4568026f1ef1e2350d53f3e166135effcc3bcf11a59d097f326365e503925c8ce1058fd5f4202a2e8274f105434d15d92c2a13e743daa466240a5826e0cab3d2a26fd085571c87fd7224748ef04886d18733f4df0a49c0cb2a622d997553a60dff58d9614ec4518ae083cb891603c1b788e26bf9537ef583ff94c85690d9e13e95a762e908e603f6b5c877062291c758bda01d886231204923f493408da7cc48de8e24764fb7aa60aaf616254db821050feb4a6f1fef787fff250eb0cc2b47ab559f18bd8b687b208c8dd253c2fad06d5db38220ea825c08f9c601ee4bbdbdc7920157629e9adfb744d3a83342ce804a65a5be8aac45811b08870a4af964b690fb14aa72a36d819ec843c2f6201a78b25810b36458ce8f920b22653764a440f5c1af70bfe0f056e7e2e5966ede202cc7d675b6be4e181694ebaa7d7ca46513c48a99e77766fc14f103e9a05c6a5f515fbb39ac28846b810bdc9a1235cee71402c4007f65098aed376700f1f2f6fcc9291b8a9ac47e3e2a856912432002de121fb14cc8347a27cf580f83ea30ed2e212aea2090af6db7c92220fbcb59f4450ef39e1a816df2d2081bd90f38868e28c11fe57ded45d6f653a2313bce8682c574ff72da1683875c2940b6bae314f542e18167e350bb0227d2841ee9a60c8e364da4e4d375240caf7d9fbec4dc56d993fd0636285a64d326f378c3e9a78b902ee887be62f1c6840d987b492621ae633022aa2b446a62d5a47fac5cbf4956a4ab2b68d18f5057f227a8e5ea8b06eb36f5325677e1256169da05dbdd0120ee42f52571b15eaedfb0ce6dcaf1a0b4a85ad7ee920e35b0ba63816454bec83738d33aac9d15ee31cbbc68c56767e2f02879a2dbdfc2247d027833384743497b472c875a559087b48310d1cf13afbb7c435b61ed95c8298e47d10213d028ee49d5bdc0ff8466ce650cbba7c4b58014f5dab61bd782b24021954b5ca4da358c5c14f37fc9b9b5be7f5b2b46553bd0855e3748514de235d61bbd49b65a3786271cc6cb7b80002c15c0551f3527e2ab35cec7f6cb4d6a26a99ae6cecff94580a62efdf80de205ce007dadcde721b4bb2022687c00ba72edfcb6cb47d5ecb3a11a77634a2cece0f5ca2b82763f30d122170341b18c576743ca0dad2e43ed81c1d1b49f1895a5968fc931e50af98443e759069b5ade4ba11a1a15910c9b426225769d7ea93e613ffa5bab753ddd5abb1ab670b345088ae0bb525b972f146e800c19f5e221466f61e8309bfee0e35280289153fb5bc3a1bc8e34d3da58495347f41e302aaa9427c2cd0affcc5c95891b4a5e4b613c985983d882ae619507622da5891eb00c60ba6a36b51c481a69262a20004742cd3562f71d91b8f4eefee9d03b8f00200fd2aa9520c08b5d3121bdf66b60b4c5524c325612ba33899c3f537d5c4c638474c3961d4ddc8f5ace87707be898b19cadf709e79b33a80524be1aa4bd46a762060470856c39ab04ce10454f10b9f38fdb64f97a4c8941245c9f79f3f8ba716a084f3bca51c0f15df12813f1450ad9ab7a2d24c5cb6737c6b78dd0f5ace88639a17033d6a39f4d7840e107041b9eaa1c59622995a66b545f2281e28afcac6e4f82ab671bcf79786c4df00b88d2721effb1ee03b785ec839771e11f763b40daa3cd034f3c3f60b0a8f5d3eba6347be4cd750f012a9562bc34ca7032320d91bd439e0f0b89373ed2fcd8402e0687380952d038f879d90d38c31e97028309d3eaa5bba256550912c70a342eb6bbb050d6f23f0904139f8329cd82b490ec44f5fc863d1f72bce93c75d00b9ff8df53e3b79d4e19a80b54d52d2325b18feedc98cfd422ec312afe2a20e7c5aeaffdbe265f68b4cb3ac08119eaff434d9c42c5b165f3af8ccbff0edc592a4416a9aceb81a7386a20f4b9125e4ba6012167e2ca2b71ba3a7b8431d7b84ea1bd4928eb5a8ee177a58e21ed24f6c8a9c0804d7e78d66b8aaa72e53d5576924fea007e83e15821e83817a27f61b261ce853fde807c658afab9b1fc2bbbc885726ad6bf25aad8b9638d0328849c617906ebff428cd994fb1d28a44040a2847c6fb5aafae8aab84456f15210fbc96ac5483612b2d8a367504b7ffd93cb787623ba8a2b78596187cc79bc30b34606242079c7819e8a733afac8ec028b7780a49fb10430bd1484844fcd5320ec86938ee8cd53b89042b0b9cbb05388dea19b7fc0ffbf9a1e0c0c603d90bb365c0ede03122767b064c6d450b4f797f4b2b82da4c26eb826b6fe88ff7c86fe43738ee413dadc9a2c98aa419051718d64a92dd97c18340a6808ae603629ab95e66c47fea265f40515754176bc98b82f0d44c27ad55673ac9a46e861fa15ab2207733be81fc7b494185e09028bbb13cefdeb0ae750d86822f50218b5e5e87e18236790ae8aca2081220d5604d8d48df2df2cd0a6c2658c40083bb6fb6fcc756c1c39eead90d817015e5959f63cbca48b3cb7cdbec34e8383e754ffea5bb1c3d791c7b59c526e01d035ac567902626313207a90e11384bb29364de9ff9392eed8d5cb46ad13ac57d600e0821cd2a4d352960f44b144dace12877890f9d378c931104c5506363ebae3a95a34793feab55d61bb99ae6254476ed12b5c45c44251cf7a9dccee5aac7b8de00569f5693247a13a90cf9ff566a9fcf24f074a727bb5ff5e256566fe740bf8ca0cb08ff66a70152d5ab9b5cd1ec314ca959a58d3f9347aa7bb390aacfa34ecedfa98a62ecafb74622846af1c5d2a108c14b6011e5afc9c92856ff80b72aec824d664315a2f2dd64f4a83f90e8b0eb2ce16e4003034cc0de2fb08c94eb03f742ef5cec00050dd07b4af3e84cc65fa5c5fa6db6bf250b0e1aa28de36b0b5d5e4e5f5a860a6daca8d1c6950d79a3697fe728f1fda33861dca898af6faa6bff703b392ef789c9fc902816ecaa5f5baf11c925bdff19875d2ea98045b20b7fcc95d7cf04ed638899aa3e66bfb00d038624abc86e1041f18b966f6f0168f51b1aa54963cab6bdc28543d0cd599d3e8798be4cc2d5c82e4cf3729d51886e97ea55c5dfc30e054253b69abd8b42c9912fcae4aa619db7fbd80445e9fad62545d5690aa6d9d092d939a0e477c90878e6f64982d17a6b940d2e734766a8d0dba5b3755e04f53adb0dd2b29f53f6cd0dbd9bedcb68adfe111fe862d22fce3a81a875ba5d994c3b4052d1688d6ba6bce8be531f0a01cc4e66f5dd4132d2a46579aa8cd501e0405e0e068fef473b87cbf3e2a53f996821e5e7121207c05f476143f7ef9b605b6fbe3b71f6753bb120498003bc8fa8c8aceba8af22e92317fee09677d12b044d2ef44aec8c234ffd8856c1f0c870c4e2d2018cc9c6103529a41617e93b7ba696f956cd253a9c01523ac580fccd673deed538b0c7eac591f12b5e78379e11d8eac8fbfefc3c63b2b6f7b1ca7be71286219aa5e1b83e27894dda6164a9523c2672bf9deb5e030da866a1486e596850acd10907dadb08a177cd722d7afec48b9837dfa27e73b01aef8a83cd9305f1b94871ccae7701ec15c67b9dc6c5ab670164bb0e6935e8cb8e7afbe07c4dde68d1157152020cdd021a45d66e5c505819251d25defdb3bcde7ec1edf6ff33b4be03d4a1ea994f996e31b60dc41a89d43f865acd288b34b56e83507965911e0ae9c34d14e1de8f9e24ebbebccc67547fa6c7c6661e9bc99227862dff116a8d344208696413b2b7947207c406b506fb06d94677a60291fe406bb0673500c2586700acc18a5c9b6ca0587238c5ed70df6e694c6831bae3996e54779ff4e96f3cd3a8ff10638347bac78d4c9cea2fd63ea1cd90dd9dbafe61ef6e09936abd22c5c0ba1d4b44937afd73cc945dfd0fed8a9adb26bb85f5b361a59435b8b435ec59a5fdf11f62a0cce7f1a6cc94a2f2957f2313a6d1f736c016b0b6a8fb116d14bf58374121bfac50c82a1432e48c427ee1569ed066482965e7020592c7fec546627ddb8ead6831b46ec75650f78146da0cf9addbb1757e7fb0677da35bc3290ffc061bc48f53d657a4c0b1353bd9c5cfd6fc9853fb8e58b1e70055ecbd75fffc43ab28709f1c965cc92889f5b90b36ad8d47ba37f339646b5affe47c3edb55b4199f988f0b2ee5f88458184b92611da0cc89c8ce3eb139c24c7adc24e99ef843a4e1d0184dd1667cbaedd1edd8aa9d6cd6a6ff7dde333c425a8c4ff7816232afad7b28943c82fbd6a6d2110457450f1da33956c841ff760655a4cdc0ce365c02011112d26460e77f38ba398e4b591e0add1e27ca1d8030d6439437006bb02e29d3b0673da36cd32fd637ca4250281426bba5e1ba4d8bc11d764ca37bda1e32051c0d0863f10cf6a12ca45ae70d401908e4639a6ac55bf22ed844c3b2604fb5762666250adcb734f110cac8460261ac376d940eb8ea9c6202342db4fe132742a1d66070fd3077d877c4cacf01aadc1dce6ffd667771c756acfbf8abc5f8fcd3e1d0ba3d6d0f5e9a1e5c3a611ffe7c36098d36c5f00b3f675acc2cd8e30f0850558ba19dbb1d5b3f6f0d86c6ff50100d62abc654b3d9280b8c7e06a8ec42b3f9760640626cf686056773d6ca3e34fefa666d249526e338267b5687e3ea6ab0673d88ad58f7037bd63f7087c60f5cfadcd29914a39f3d42b536c9dab48905bff05dcc231c9251924325abd3a6c816c7e24243e57f9e75ac9445daa8f88c67d800d6270d62ab0cba83e6d83aefb58997f08b75ab878e05dbfa07739c54eb5f4cb5b8c90a17b86fffe974445e62c1fad1626c67a18841abb4fe35ece86249192b40041afc98c10c6866104f35ea60c88ffd698ad4be1c018739814f78d40804bfc4430dc63c940c04831f58038edf45d8b4354a464938248dc3a5ebf17d049471411b015bf08fad214e22dc93b2308001ebfcfde04099d6341ef69b16635fc1bec9b4185b5159bbea6045037bdd45de014253dfc097f8c6a9f1df151b559986c3c1679b8e8540fe0be62eae80eb691b2e016153c6a52fe6d4eab1e21cc629c2a5cf23f6785602ca68dfaeedb7755ac7c36a1ffaed3f5a8c3d77f37093c8b5539b8ab186fbb8a7a6b0be8145647fc17dd3a6ac9b93e346a3ab80fb3a95622f3e88adac84bdf88f77220f2e7dbc5363a48132303ea709cac9931abf4205eee31d66b2c32556c225982ac29071da04999971eed248fa481729c2de8ac8c736461e359e230ff662a7204c7c0f0e473f88659c36b50de3d4f89ee1120e7e0f3fe1128f1e02ca7c8efdf379453facfbd6e233136ec25e3cf314c17d7c53e36d6a3c33e126cc5384bd78a69db3831ab96b22bda4ef3262c99a08c76e257719b1a47006249e6d78d66f4f7c43844b718fd0f5b1a78b360ef74d138130b108f38f6dd146c01a5ac9938c9070476a8c474098f88f5d50e3b1152d705fa72a97980065184733e157ef4ae381bf7b66c93eaaef8ad8ea88158b1c1746b9ed49099762a7bc7f34e2b818974019c8bccccc90eb6a3b134f39c83ad99337308fca241ef87da4367df179c2593872e070514118fee369f677469f1c0de4e42537fba3bbe0d231131ba874d881ea2051c80e8614a2cad83a70099e8014bed46a0033ded26007638c30c6c71863ac95112270ea0751d575e052f5eaa39eb3fad60407b819a8f38553099827174ae1127c734308e12e4e09745c625ced6aaa65108b1da92e1031b527105371ec61f761d78ee9a0f20feefc731a50a63aca5eb4a2ee437b0e045552e8dc0c857e33d4ed22fabd8eea36ebd6e9d8fac11dad1bb10011536177f624c93b7150860828b3272e7d4ba4cacb8a7da45a1deb868f152e1119797870c907f6e4d2d08c46198aa3e1125c789e15293e95baff485b435d1c5883fc074343abbb1994e198ba1fd15f81e5728bc03ea48bd3ef6e717a6968e2fb7b5d1ed521afce634fd2b4695e320dd3d0bc54af6248247e52ab77059154990683ab0c01571ddbe58cabee7d7755c12ef2b057dd8b32ca18ad0861e247974f314253a47e1e4763e5ca45c18733ecc1735f0b0981e603524a296555c9154429a58c32c6c7186394315e7a808b31c618a394b166a742231fd0a912f26b8c34432a344203a4b2b61066e5bf4f5d515d18ea42990bbec84b0951f250b2708921f94003ca40c8db5bf5150009122448902041820409122448902041820409122448909d435c1a60b92501a7c86834aa3e3b3a2812e96a12b673b36fda766ea5b7832ae6f9ebdb751d34dfda9c73f628eb6b27894422914838d7755dd7b569d9dcb46c66737635b42cc6acaf9d2412292369a48d849372cd4f488432532e14d116d23ed9fc372b966d9b765dd7755dd7755dd7a5699ab6d5c86c6c59f7cddf9812a35b49bbdb28d262bb1ac658f5198daabe76927648241209e7baaeebca7e5ddaafa965d7766dd7766dd9b66ddbb66ddbb76de3b6af6c5c46b99ded90b7bfe02dfbb66ddab76ddbb66ddbb66d65dbb66dc5c4b65263c5c4765dd775a3c6529e4dea96b263cb96a9d88b43cbd9d5b8b6bb6123f60eae4cbbae0be7caaeebd256b6cfb9d27db3c6fc36e7d2265d57f60bbbb25fd8d2ae2412894422611776cd1d5cd775cd896130ae8ecbe695aae8d2ec4cd7b07103020f98a4519b80d8d1419148a9516a74c26a7cf08c91586f8b1f263633ac326558f50f853770d798539c9a76c5401958bb45b70e96c5bcf874a8458dd229adf2eebe4a0a8a28f4e92bebabbbb9ebfd0ff4d6ba9b204efb759174624e1c7b56975d6fed57f701e1b2bdd23a2c6bacbbbbbbbbbbbb5bcb1ad3ba3babaaaaaaaaaaaaaaaaaaaaaaaae66976eb63aba9914aa552296c56a70a8b219148241269669a36a736413552296f7db057a34deedebca6757bdab2cf6319dd9af591aa0161ac43586ba4787a7686e63aed60ce39af2cebb2c9615c867538b2ae5b1fbca71397a68f9a39678d542a959adb36e7a437b6cec6a665d9b5cd6b9bd985b10c9a8c797e955dbf7ed9b8c1d8d460e5b29975376c742b4db81a366e5cf39a17f60fb5419c24921394ceced7afbad91d97b1674dd932881c44e8ad4b5d17f616b669c6110fb878f8ef2806f6e003948932d24f889de9c9a599e27076ff9c9fffccdf60d3f6f4b4b07cf70bc3898f4c7c45e52947f98abe5ce2409bf6bd0e5ccabedf814bd76534613dcf734e17ec2d4f9b62d8a5409b62e8f6d475b05f68c7cd4ec559549ca588087ed451854644c0a362316ba17fd3475a0476d14aa3d937aa71dc4534944251ae4253be42556e82ae7c444dfc041d1d067ae228280c678ae22c949f024655ff173c394ad709bf588f1a0cabfe7bd3d8ad7e4bf78dac14164bf74d16fa6f953ce0b6c5fac748d414fff874f251ad632d74ab0a96a778d466a838cb5bc5598e71d6a2e22c7fc12d2c74c7d696af13eb168f6a31e9c1f2177c161d31fe06ec494155d0163ad96ba1b2a6a038ccb3d075621d857518289fb03ea26c82720fca2a947d5046a1fc833dcb863d8eb2901bca4470b8087bd62bca39ecf1114621e117eb4d39c9f21871edcc5bf1338b951faf7f5b2385f3737538e235e5599ee5b907f7cd6a3da6f480fb788707e31413aa75562267c833ef28e1129421e5c217bed5b3735434b042c4a832834197633a4013d7b0046fe3073665873a2c5034ec40407501653e877f011300ad5f7d00d822568c8754eb0ce48a55b553b16bd8b783bafbda3914fe276a07cd5731c6eecf3125557e9397d790d4cffbf3ed1f1de008c1ce2ee891c1e3d44efd38f5716aa61b4914f235caa2c18525853dadfb3c0beb3ece6505a7aecf3ffea7a3b1b96033bc7cdf3436f1ee899f67ffbcef2aff462b1f13ddeca64a07f945eb523ebf34ed7c8cc3300c849dc34030858d30ec31ec91ee61d864138661d80a6d06b6611886e2b67fa06f9af6adeb5cb41958a775dfe7146a33f85ed65b1ba5d3046b1aa9d57175d8db78866988a8549b11df33cc8369aec9d03e1cd883d87a7dce39e7fce7fd50a3e5d6282873dde28ad1af79aaf55ea29b700986866ca37dc4e2da7adfe0740b1a48dbf40f08637dd2ae618fc90baad5c4470f27a9ae251d28d375f69ca8ae737e618b466d0dd6e767b72356d07380b08dc27a75537351e16743d5b46b9ac66947b928b7a31db2f6176c82ec824bd7b51655bb0d507b0c94d9ae699aa669d7b46b1a87f24e9ba1719aa6a1708a4b16f70fe51c47556831502a772dd5a8e598eb6ab8e8000494e14a04ca30333333730a0d0706511aec5920c0a900a8d008087a540c6652e8e96095630c6b33f8c3cccb4b4859e4d28774511cfab339f2346055a1c58835607313ac3313aead6746dc00b2c84bd8b3386544a3220fb6ae8f7e703eac7717b05bef275066bb6975be50ad378a4b6c2d4f93d112ebeb24dcad4fed9f7fa07fac6f92c99ef5162d06a8dbb195eb3efee7daa106034701326cd749b5ced0097b16845d012e0a57675e735effccea4aa1c5a8e693fa391f2b654dbbce39ba28ac57a1c2b22ceeeee05bb419f19c429311bbc8bd3a3a3a3a3a7372dca87966755607c24cae31c6643b29291626d5fae75297a75a256bea749c3ce7849f39ef5967a1c5585137097bd63179e230160142b834bb28844bf206ca68326617279fa40d4f6b4eabba2ceb9a56d5332b2b3048a2481245e4ca8ee3528ef82f768c59d360041c1f5b6884d48f301a02b70434628316a3eba7812331a3b1882aff91a675f86b49ada142233d7ccc09e4077b3206a21ca61ca6386c399ce150c56189c238e3c230d8d329ce449b3833e31262203c846f9808e37011cee1233dd89348705b3f8e935c02614850feadceb749527cab6406157b72b95cc1f26fb2d0d057d02f12c1353c3cb33bcfde740c383a23d4bf5532fab74980e1288e62fe6097900c2695cbea079028240e8125d16b329b14212c3791459d8ac7a8221e455431335d1e9f71297419be82e5f12c77e1d2f5edf13470897b3c89458c8b1709e012ca531ebf0397523cbe071fb834c3e36970e98fff814d2a8fb7c1a5181e4597da8c288a317ea418545c85c6997e9981aa780b9de12968cb53688aa3d0946714e518cd0ea2d83f9f73dc2fbadda2170badbe82b25c06bae2212ac3a42e2a34f2e01779ae9117c91a67b8f44b7ad92df35ff00a5a84ca59544ea71456d1ee61ef17b52e0365f946af73dc3f543b887e328a7d52d151689682a6dc85ceb7d0149f81b65c85364fbf9056ec29868728cb29ed7e4aef42435741ff19a88acb405b85b6137e913758866e45e7d27da378118d81daa8c13f501aecc5aba02db407f6e253d01dd88b4fa13ab0178f4213c02f5cb0170fa2312c483478ec72964859387e89a1cee923a1288a51342b91a88aa2c903050c276446975f136c0af604b014da1b6c11fb822a43df5014c5289a95485445d1b4505014a257171d068a42f4f8d98d44d439f37d04ca882edf4ac0cee1d28c2f585293845311a010aafc76d17691e8536627680e27aee11fdc022ec246b00d82cccc43999188071d3286e8dbfe586dc69c5bb727a85da2effa56c32a80226c5e949b1789389bf3a2330f7b1f6fa14a76b282056e47ac3996a75e5fd10277aa7e6933b603c20d726aa84220dc6048153df670526115a2f648127a157a0c8190ea52a19110e0546c6627f60489bcc0063541e2695ba02307a85ecf4181faf9273a778c9b90009ce81007d1a1a88bd9129ce82f189bda27a7b9d40cc5895efd04dd2aa2b08e6810b15eef23a2471922910842988b79e6b1eb7b52e563d733b163275afca49345dbc824954d3ea8f2cc230d004d7b824a8842d1e29a0af7d4dd76a2ee9ba17e7bc2b44c22c1c1fa310f8f54e20894097dc989246c11aa8cf2dbd38d9010ccf579aed97a6dd641599ce938735d74bba876d1eca2d8453f17bd2e3a2f6a5db4da91d705c4351fb762a2ed50c98b30124275800a2dcb3200e490060343c950503098aa591eec7d67c4399dc38267c6a5c8d0459828f6a8321251659c59f102f7354f3be1d2c755a80294e12edf3c539e87b089932e44aa3c03f95cd2b035482e812ecf3f32015429794e51f771a3d14c0f1ed5abd0480894540c32841b371e707166a68643e9e4c419c94ea852f408a10caceb845fe4f7c9e4c1f5c8e1f1983c35faf6f4ede9c704c20d1911b95c596152833d791b9d8384674a275e932f19294952654e8ee7fda3d18947d650b7271f70a17fa4ca3cec39e901b7326e978f44409915bc805900b9665b74119c236e6e50b98850c8e3a17aa80a59ff160bd117170a0e175677a1089d8a8e612126f23aa4366345158560e82f3802d064e2f2356c4099d0a54b8a957f2a2affb41aa2eb03c2c88be89e4c748b02f8f2a9d46f4f5a8afaedc9a5bb8f8f38843228ffb4172cc1ad0eee28c73a24531ea2df9ea44c71a928340705aae8198a0bdd3a4c686b88c2da367480736932428f211e428f317e38400f622b0a192e55bbf6cfec22dbc2a6851489dce755b41cb6937a4e8e1b8daa6f868a3dd2d0e757831197d45007844b8d95a3a0baf107849907558e6e3d5be5a89644ab1ccd6eb2ca51ac7b6095a31f269fcad1ebc855393ad966568e5a4faccad16aa7aa1c954464e568dc13471b080833ff8275e8ab7381301c5d8ec20abb180361f89352bf586da87c8e06adf2e5c8e326a8f23fb911c2f0b1135bf5f669a0721a6821b87640145c0084b9f9927bc6f2366176c24f6a8c8480a6422343f0a4aafc6472233291a1792d11b056510a8142d200c15e7c4529903133c780fb4815ca90f525022ecd0e29418d110655bfe1d2747723865896498d5f9ab883263b788546ecf1d8996b07832ed711e2a64e08ade7d87f15e0ba1cf00bb588ebdfbc758f4b38b87bec411a0a8542a150777804b71542ee50e3688e9ddcec72ec3fcedacf737fd193eb72605fae565d48f46c5b00bc41422d07d1b763285484828222429174a31cfd405de8ace1101f7be80e468efd421542e59e63bf800392ca69177d3bd47240b9760c856e26a29fac2294eeec7122fa9deb70e4d87f2158e77be8eef3b8e76e71143b88c2bad1cf005a472aa2bf9fab13e2a65a1dd82fdc6003f91bfdc2af3a2535fa0526a1f23512701f4cbd5f7808ae032e3a620a7b550fee63512b16ec7110a67ae492f78aa351d5a8d6c7d6e0a0409b5ef44bf5ea404800b6f8616b8032f3d5b91136e9be48fdb02d8e50f73d5b21a1ee3fa8845a592b1f6bf5b5d9c0e792ff581fa2b2aaea9ff921ac695d1fec434cecf3797daefee7d73f9f6bd1661abbe6eeee438d9b992d21224b2468d082216430c410fdac9fbd7a36bb25a83f65dbe0aaab6e4c9bd173f6ecfe0799f0c0dd991bfcc2deae0d27c1aceeacbf51d8f95083e599200e8eb4e589a998ea7ce8ba6e9f753eb0975d76bcd3b10e7b31d526182e78d2751661771759c0e1481097e61773b4235026a4049411a15c24ba463f1451178584a210088a3743e82ba8b7b055665d4fd6c38dee09e170b8846a13d6ed2e8e78c3de0e612f0ae99e9def79c2e3a4ee57a8c045217148bc290243c226aceb25ad649320c97a707daca98eb8fd64cf3d10667154fb88c3a52231874b201cd99b4b30368953803221d143d94387f41375df36ce564ce321bbf6656f9c18d98ea5af93ea58109d4f68e71479953ab87eddb30528b375df5c58534c2d130e441098603441dd99549be20ee0cbde486a89ba3c75878069f2035c1fd6bde4c1a5af71eac6253f24cd667fc159e370a948d549faeb21d47dc226c8ddc3a5f9bb8c58b8946cdf4850ede42ecb9c25611d0d87cef6639b26b245e8f6c8f152dfdd45d93010245c824ee6ab2cebaaaa2ee7ab8ca290b1357b3feb36eb82d83abf3ceb04ca402e413a5bf6ec6d1af67667d88b9d9dc45e579d553dff6dade80b099a1d0e17b36940983eff2e3460fb7da1297c463b44037ba17fcbc3b34eb6675bb0c828371fa23db007afd11dd89b215331319afb4cb42ba3ee9b26ba8fe3aed27da30ab785ca3fd757e80af8925d050aa1664fa14ad4ecdc016af6cd00d9d9a64ddcb1a374365bb6d9b317ac2dda01eb580f3bba166101ebb0360684b8c1b22cad73813059873da35587d1a6df05666aff4567b45200bcc17ad3e4a0ee2d5ad16fd6d88fd4a5c2cb4a0b4014220d28b3dffaa85605bf3318f0e1b3033c426251e1f621401b687adc16fa105c8803c17ed90edaf8ad6b2b402854782670a41086e2976a31c618638c11872cc3c1ea52b8ca90bb99b1b584247e9044123f482289247e00042014c9a9f2186f906f401899418e9037f8170cbb8e19a8909323094ba2c3ff004d282c3e981529541ade85064d2438332813ea438c336cd20d51d668d718e3b66d6fd01bf406817a492b5914a7564786feed10ea479b017a442d122ee940192803f4780c0716cf4f6027c0165cf360138a06e3a6cdc0442294500f9481f1c4a5ed4c61d528d7a63b66dd8ec472dc96006576709d04d8a22f17c5298132db47ddc38d38d0b108ba3c715b060552fd38b5052b6c1946732c0e7686f0f1cdc7377c83a9348a74131a617471b03384258ceca0071ba9f00ddf74ed6d036933b6b78c6ddbba66746f4018692356bda1308d7ba83b21eaac0ed58d752114d77dfb842acf5b574150773738270a318dc69a452e815662376c54a3dac436f184f39b9110ce46c3017bd3b841d825600d46800e3a163314ba4c50100653020926357450ac1b040823bf4bc01a90f09291124870912acf82491270dfa2a0d5b33d3d1cdfac5481db9913e5a15010461e862ceb9ceef8fe41a455006b90df68d74098e6254c5850a16084dec01678d360c8aa759c82725131f6384199aebd1379fcc9c80977139647309150a8a04ea4cd00bd535c82e7255006a88b07cd9d3977bafbb6c6c74d93110f8c6ecde85610b5c1dea45b4312802ff27b3202113abb01d416a015b606c2c8ae98c593b74f0315eb78098449e570fbf823c050ef4099eda17f3f51bfb6f9acf7f6802eea7a40c7ba1e4ec106026ddde1351a6bd46684bad89f314087816382ba7ec1a0063508d48b6ad3da4c21dccd08e78b02dd78d7285710e51b221046a2d808140ac54ab80842182951e591c0d1a956d2f54e2be19b10c618ef00d43191452d6a518bc23adec938529b3a09f085b91bce09730a5c16aa7c177818a1775a890643fb4a134ee6a075dad67dd203ea7a873dd82fd89bea00bd33ad9910ea5a0905d120b80a3adfdcf8604195ef55c14e9b6cf08b4c4dad779eecf4a852857ba18adcb8ee1128f3e9605c5d10db3167e31837b176b73cb63cf6105a75d1e2a8a2a553b0a080e1c4c8c48a4a0a8a28c481362dc3bea7b1950afa4fcaff50fa8b16f13986b150144f4159de34c55b68674bd372d2d0b00f7e916f3eb5e9a36571f6d6693deb63ddb1bbb3167ac98393b4ce372d340705306d46cbe33f2d8f55cbe30e1f6c9212489543ced10797260c16852d735beecd7a255be80e699a822e91c5d9229bb3474674517491d0d5d924bb4357097b4b986c93e55927dbf3846728f3e0971eec19e1faf3db13977ad8c96db07783277bcc194f6b4eabba2ceb9a56b5343d56c88dc4410d9a52a854cc37a5492d191a9101000000d314000030140805c482c1782c103475f70114800f89a2446e4c9989d32488711443c4100208010000000200020023c4010ca5ce948237621cf234610124c9e0434aa5c92fb34939a1634dd914c621da727feb1c58672b225d6846824bae6784e0620ab08f9b49d2ffba41a8357ff25a200e9643816014c5cff63bb3164c90aa27c8cf8739e1e45073e8f81d7058ccc6e065ab6d3f0fa9ee0dd0e541558928331628cea1b5155c6021bd936fcd597b22a6c9d90eb30fbc6a76f036bb060ef7fb24c1cc85ddf0ca8b1ed5e9ec1b725a526622f315adf4ac98942dc8e860b4aa08ca1da9d04b3500b8f05c462995d1a6ed79955b1171a9269870ada718fe4771ab7159e044333685cef24b86af1b73ee6f863031bc0ecdbebf19823b1f43efaaae197986e7ff40f18f336d3fe3dffe668c0580d6d013c834a478ebf143a1c7e8bb725bb8707f123e26a6e66db667576064009275e7c06408017f3b32000c00be669ca382d63f2680259aca67889e4ad776aebf19bb249d295dd96a10883ce20ed0195f11c6934f0f31bd97dfe0b7e2878cf7fa171f5ef11e979cee77992fee1edeedbb0a9612f759127e546adf1e817776872776213d297a9f45096939239831ed2bcc03351001cf78656d95649e2cf1c1ce8617255129544f6d2610a6c3e6dcbe6b10070844eb8c9a45954c813e3f97c3315efb43a40e7e17b7cc2675d2632ee355794f33e00b553c6511f8a83aa5375bd0f792b0b48b65009d71004f1fa6b6e9ae9061eb25e50140f8d0ac7e559fc5f3ac4e987042eacb99e22d8700e2f0b8e121e24971573002f7febe3a274a5a94c1628c0d35ed2ab4e62b151b3b2f985a0915e18c04dc38b1d71367d149412b115adb3196fe3742c42befcdc35992b6be65d84c943da8159a9525fab920dd0e01d5066998663f6c9db73f34909ee2052c4b3f9995a74ddcb46a2ea6f8adce277596a619c5efe0a4850a5196263df402858d6c63f92f04c746fd2bacc3cf5e3ab23c9ec4af81a8d939a242a6cae699242792ca83e9e713bb953e5d1c70db028813a5aecee4819e1aee864d2908da91a711af03998cf156c1b6c67ac12a04e1605538711ffe954c54bb2f7409a76129e09a9085b300c01549da4c32236ba8a68a2c31a05163761e4a6acd76f07613b0e13e1595baa6acca38fc62b01b04dc3cc4647e3c85a6372106f238aec7e586086f4c56bc7438c0e43216847e1fe95011907918a14ba45bfe62aef4c44810c90e176d6e3935fa6326b4f090b49b0fbb073e9953b7e7a09d7aa23c5176b8e0d9288a567d3a3cbce6fa9b3778fbe0418577ee59491ab1705a665e165613b036188913d98cf7e6fda3eaac8e69ac2c37ef7bca41ac3e9ff5809f36089d7b9b952d1947e9f42757eb99d8dcc05962e28d2f58ab73749a7aaecca965b541193a4425c72ee118648ac36830d356308813a43036c0bc0bf3ef53f9d009b209494f9eae1a4b7f78169e8556c9d448d0cb97aef4c29db1eab9b9f1eac67905a98f40eb2fdea24c4328d36cff987a7bc871ac4b306342bf1d02053846aa2690e214dd37a72ff105e81a35b4b05401aa7eb6d8d5c10d2a21c8cf136baa929e57d52734ff686dd468ede029cf4a935f3cd896855221808c7164690be105560596f653392d3f3470fcf64aff107642a49b50f0e6226356f15fe169ec2a246871a8f591711f50a6e875f0ae55680b4706e317cea93c0a7581e51a76df04cc58849a006ab8bcf05283b3d6a8dd92569bd17b6d893f8f96b2b596cb6a84e9d7c530860232ebe9290f4a540b24cb65a65e46d2c7bc54108a9e907e89ae393a9664ee4c63fe5d8e8542866bf4b40e46f3d0d066af230cfb91e81941bb28f01c1ae2cd065351a88e4d422021167818be946a7700989b9a2cb2598627a3a1ce8d00d24e91ddf4404351024352a665ae80a294ded7ac62ea9b79fbb7be184298e02e29a6d07b8ec6c9aa5bb698eb919c06f34e7c26a99003b7a902a61f62456357441f4e2ae4a8d0028ea862a488f37d8392a961656eb9394a5614bc595185c23724c0574539f04f0d8b79a70e672e150d5173454788b552530dddb01a9472b9e5f6cc34f6086608bf5d526b940d80a7917f9159333e12b5e20fce768616672d181a71529a9b0dc7e6bfd7820db2124537282cfe2defb94ec9793dec645e972833957aa75bdfdb695a3e6d5a24ad62f0ed9cb568a2d91a4aa2279b3e3058f6f4fc0cd3d23df28a7e2592a7a66b92a119c8e738d953742898ddadba1f6b177c08e092784691e7d3e45d37042c6862830fd02f8847d88b0322f67f5f4ac916b6b55c77f82122013dc136966c49eb5cc413baea30832023f1a926948a881f959fcf9e8202246363496203dc29aebd2eeadc0351370123c266add3544a24cf34c7b837048c723a248ddf758500b6d7a9e99559ff7fa41c1a5fffc68f2d8873af0c657de9c599dc38e739e1f20bfd26edb432967a326531df43932418e16814d89206c99eecdc4b758f6e4473c892bc6748955f9627397ca2b4013f3bee5affcc85624355840ad27b692fc4b3f22f740866a0d08ea177d6425f4643d7d4432add2efa7c39624646a9689bcc0837a8ea2fc81865333d556b3bc6cd27d4208a83dc528f7ca64b155e6f3943ed9a82ddc5645fba873e52ab1d42a53e15852fb68844d28dbd316753e341df38c074413a4bd603bc7af93c7910a4ab735ec485ea618dd477598180c01f909532d26540c78e3e70629dd0f310d0135e9d3b2a1220b77281413523e74a9b3e95a869686dfe637c12104ff60e7422c024355279d9936b2e35e05dd0d2a91c04bc009cd3a6acde61165e4f8d616513cd2d6c34d7d8cd8084c15e55758abf2fdce8a49452ba6a1370224a24cc089faa1b8bc5530209084a268ce423f5364fb962ebf77fc5a4a0a6647ea9b1c53a0f465ea8f1255df42117e55d8f98299696bfb4efd6854b7c0ba56137646364df7fab54500c9114a3f626c682fbbdfb26293d4e99c37cc75c4358b846b1180ec69f4316a916cb83596e556c787ceb4549fcd51cbf74bd55163f5cc21716f13724fe7b7b1767bd6c552f4eec5742d19f6ca93a82af0eb3c75c0fa57a9b3abca744e1dbc42f732097a87cb61a314b884ecd6c12127e85614da55bdd3cddd811f2f517f62b03744f507bd78d3b17121afe480d2326e091377d6c1410f9ee27b261986d0dfff8598c7d84a0dfe89bcad1a29f7d618af601d3695fffe38f07fc361a2299f8d040036948438a9c0d18247640de8d5672ed3902d2be8b3e3917dd1e377167d3d048d15ebf65375c5d6057c92d6dd2831655a1fde4e9a6140350c8b23509e6a89def3fdaff2a0467bc3d3e64b1515b5659f2ff5ce436ffa48fbcfbc4c1405fe26d4d7f22eff610b827947392332113c8356af24fea71d3ca1dc567a2d9cf14720e03cc667a382573fef1f11a2d94a81cb64ed95863873c9dae2d419ef5b82a2167c9849d7813381d047bbb9c05bda979e0b62e7a7b49d8267aff27f0f36f241f4027da3f274eaff253657d8e760f8ab88f9415042e0062126ad83f51103c0496bf312acf017021546a56b1364685ff4b581977827925e1bc75dc02e7e9542e4e86c2a0509ae45a844208802b5fa2a06ec75a14d10b1c8105bcceb6edfb0866f0c45d73fd92d295c845988b104370b2cbfd8a1f046ae78520f7b4c75ed1e1a0c17adb923cd7446a0aad12379275edf4bd00dee7cd49add85487d09a828a8e54072df82ab3998f35f633c19c74f7d5d483a222d8717e41e00c8a7f4f704308c8c3ef12b5866f6457d407ee79aab0d4fdae099beb8f037de25651382fd81b05d1dcd0d7e768892b294350fcc2f185a252fd44ddad5bbabcad9aac5c697f5948860382bce14e35beab53b316d155124d4bd848df05238051c65d514543a4a8113bb35b6d5b13e02b42b58f2c5eb8de84fc56db501f451952346a56fd1a34d8173eb5d3c0fdea6047ad19c8789d447401d6de3498c74ebbaac492f658ad325a447bc74ec6688c2a6bc7e14581c0349353f91f0f6fb84c6507fbbdd96dab63ea8c9647aea530675aeaf34a3d4b7c814b4ea2e4bc41ce81cb98a9e266148b00639c937ed7b39f606bbefcc34b4520ba193458bbc2767843a27c1cdc2931a039663ae8f6fe7f8485ec6833511fa496f89c4890b065055e48185732db2396e3524986e60bdd72d749c0f29e2e2a9a1529b139b5924cae52a5c9b89001b89cbd627754b71386c4cf091d75ae772d9f23e540b1e4018dd07d0c19ce46b0b9db4bed068b1572f134525cfba5cea40d7ac7cb149008a31d23375801166ef9c65a041f8a996b9f2570c0c1fd4e9a662aa86410b06819a181e20dbe91f6d0d592aa8c2a32504e5e73ffbcb3f5e4225ece9181580341a88478b3266d13d04819ad623562ff32895fde2bab65615d4f090c0b8651b73816ee673b6b442997d02fd92aa228cdaddcfc8d2749d945270365d94e177e0400484f56bb853b86ab9e67f6f6e433d2d3184b0c37e9386e7b91ed1be8e0a173d7693d86fd65e027c2365483853415cb710863c0f427a4c9a53513041ae53d53f251b76c208c5dc4fbce93556a9854ca82174fb7f891056d25393593f852be25a0beb809aca9db49958ebc556f70a436acdf83f382435dd309ec34525d02e519fadc691acaeec941c03c446d40b40dc9211712d35ff6b3ad57b291eba39d8161c8f4df4adb1cf68710ae3d1a6f1f188533b617d5a57b0ed5bf70af0b15714e552ce3e684dab355b321ebb8ccbda95d5043a6d286f6ea7996700c6798aa4971873215841ab363c55a9748a0e771d1ed56d9d676dcbb076bd837c3bfc11ad39c813f1724c0cae134fd02b882fd98f89780f79210d1c63d729f42e537206db0650b9bbbd035060fd61c62bdfba5fd1b4dd1425df1aff3400139bcb7d5708aa204c67c09e34859ebfc0c48d4e056c6aee61da55c5522f4d13b1048bccdafa50de4d0d4dd5d98b42d0f691d540443d7df6bc6ad350e499ded00641b7f01814a57ce768971c43a3a42554f4f480a8668ade05d8e932a27031d21d6e9d8675003ef29d0e1c7ab2b10b4ec9eeed6a7d68024508167a112e43798ac4133217c20ef61b76d30741270c2f1a1c65600b7e0b0474e4b0eb03d97742c473257261e826c4cc6fac2ddb5e9a27d0b9343e26cd2409510bf8a17650d13eeb4d33799238f94f3d8e2a45b501d5e4ad4b7972c77886b6b1225dd1a84a7defe80d99634081a96425f774917e817f381582896346733fc6bf4300c542d0d1e9a32b6881a74b6f80b3756bec00892f891642f90f1cecc78194965c486c8e83b2605111b07f52f5f407b0590d8378ad13764901536c4a8b5071d1e8bffbebefb6de9689e19d9e358528f7e48c16267a71bf6708875c04a8f008632e8b661a76d891040294e4453f0699b31c902b75fb0102e06ddf7cdf9b0e90788d0724097d34485eeac2fa7c5a95da37450e1aabed206008b659a4406f843ef00d66aba13b37a17884d518838d44d35494676a467c11722e317819d7da065d4ef4349c4813193647233d9e2c353c683d22c3f36924282c03c173409cbe5d94885887190afe58555417c159e637a16c06af224fa7f6c2c015b059080edfe25dbdd9572498551421d70ee55f141cafb075fffcbd967636f7643a0ec8d70c595f186d73ed359efbbddfc68fc5cbd954c5684f061880c033c24e273b19c86bea831f1f4198c516bafeee949f9561cbac8eaa3809a046c456b829ea4816a290208e87f9732312724b68ab76d1a6f5d8998055e10d858f6ff0a6910fafee1818be434f91f5a632875c75e9fecaa0530634c1a5ed20c3dbb84affaeeca0f4b0b470b735c92aab622e98925b9ea0d835134cc31adc4400c76487f8280786a7da7f65adf86c4d2d34c23c2d6601adec06311b46920c2660488583fd4cb96abafd1d1736ac516303dfc315d01fb305eb0b75decf24fda10b6bb49a4e2cb6029a3163ec2b80986400421703634d952823c3ca4c40bd70304a5fd8a00f2b29788228d9d561352ffc205689adc10e3d441d3b502dfbb58c98b4f6acc6311cd94add32e39a6a68ef589e8fd0bc02009892c5f2f3a93f7a2e78fb673608fe3180f43199ea90c7677f274b59f1816f1370d63d360d92c498dc013715fc08adbf6784ce73b3deea579dc12db273cc7b0ea073334042d6e7b8ab3a394a1a8df1ddc49ede9af3f3307b9f0ba2a9c8a5c74893aa93957c7b58e442f3a8b68ba9aa1814c219f601d0acc8b4b97a7a5cc96d858471cf6e06adb7bcc3974e257197c64538490070e0d0111d0a9ec914e1c942e7261745f4f02dcb3752b90238940842678ea6b2e6281b4c830a42a64681023eaf7fb67001822842f40ee92f226cfa343c4a147f187c682ae5febd833173b132ae1e93a7cc8954e0673e053fe778a3811155fed6cebc420cbf869e66ecef20f33da3356d1cab696b0f212d896cf5aa7cff44c13dbe34d6bd5e164551de89b6f2f6e17a2857f569d59ec7326b82fc222f3b0dc57e03a9bcfbd81f12b19f2a625829a9c11f99bfb4b8743c04fb0df4319df3b30bc87059b305fb338568cd8e182de8294af828c09cff143082b265c10440847fb081d0b32a1207679bca6ad95cc819a01548f5374b384d93e64be4483741a37651e4a424ab2e7fdf662e85aaf47a28d892f02ad5939fb3de4ea6ce026178d8519a24ed5f2f1ea50fd113e50d303f8c0d2e8424db471e41874515f1a9202a8e91bc603a1a1c7c46156774a3a1f7c6094e7056835a4804b2285d126095a1f91d8e8dbd0ffae31224b4a76051e32a8ac24c9a822938629a5e65652111eae9d41f9df3a798a5d8a56ca962d0d438f2504ec0d1232ace601bc76db96ac00b67462dc274a9e6fd92fca54d207e3ab0effb893354a771ed823271249f6510cb518cbe229c65eeb2934e88887811e706cfa8191da6639e81d058a911aa57c60951ca077145725f07aa3dab130b7a1403755d08bbcf09fd28c18fc7dccabc58222a588eac1002fc73bb398605a339554f66029e3db0aa37281b03fee8b02fada47739083107cc8ce6454d6d9d56a8483013092a7da1600f0ebcff996caaf145021df0646629700dc9a91408e22dec75d88ca7c04f77db2cc9aaaac82e864b0b6749a6237db021c5baf90dbadb182adc2025b8f95f63e1d5dca3daf3ec6fc0b336a8e6e9195030b80c9ae4be53fa68bb39e25f16dfca09f83dba9bd1f9eb11219cb2ae8ea77e9f73f207f247f94cdb2121dfd7d61e4e6cc265e7a61f2885230008c943114be49f8b3156e8cf967c761e6a06077af11294ac0b9db04e1619f64ab247d4330bf806eb0702659cf01cbd4331e47c1b0fe5eb64dbf5dbdec1da74e67aae046ec32ff4c93558503e8fa87652761d092ade2947ea6a9cc0b5efc574cc0adf37e437d9031d03711e8167f7a22cf7319906f6bdbe53acb324619d012e3ecc28b9fb2ea1df6fb3eb3595665c76a753deab1cefa56788137488f48c3950e4563be64ae77d7d07eeaa76398a8fdcb03a0296ca68d44e2db9edd41603f935b26b09a15a49ab8fcf040351a5128e5c07cdee9ea2c20982c9f1e3100532ae896d54662bef9b202ca4d5fa9689d95d864f68bb4a9480e3c5780e2455c595b891eb1768778e2af53f8be69fb9342e37814d48ea5d1c981065387633683a633010f890909af11a4caecfce2d5d6352db2a408cef7aff505b241e5a2dc92e8d2fd45ae34b6804bd20d6bfe3bd0c1cfa9f78d46838b9ec88a60af65b3a20835e50b09dd26686512f9f9f303424156bf6f872c20b7f7ef7fd629d210493857772798a8b5775bd9b88055e0eebe0ba8830049e2e5b9843bdc80e83b7da4609b476bc557fbbc4f89bc765bf8f42012477408d0cc435bb0a27048ffd8dc8c23f03b98a8ff3e51a135adf2401f99466b359eff05c5de21c16c47466428098a0f38520fa11df1890929a3c84d7ff3e0fdc6d2e699b88f8f50271dc51d7b0b72fdb1c78494c39699c27c143aa627f32cf3c3a33bc9db536531acf8bc90ad506d7add57fb1e7e23514cd3bbff14ebf7779a639dba32b7014a8012f0a02b0cbd9722b141108a104b4663fa1030121f58447c8259fe7afce114bc80126009b02a296ba03450a130bf48e17ad948c23201d62a354b8736d2f683cbb2020ed1d3e6d3119331596c82cd0d15b18bc4aff84f2c5892d8427ac3815bf3773436d910609c4508b43a266f57d18705b2e440ed57723621dcdef76fb737b2d227ec8a9ce954306012b76f0fd6e306b1e5aab628212f07dfceb5207bcd4b9cd3f2d9d9b11673ccce35b7082eef3e37d0969c1d5f4e63a32584c27f9a583753bbddc4e3e32a5b3668e4e73108d704bfa35fa8988a5942c569556145650d7d2f33d14487e6ce4535ae80a20cc5faf9b483878ad56345c10931d0e0e6cbfae55e797a7a76d43ccd621d62f2e3d9dcd124bc542676cad10e4506fd53a04ce4a537f812589a6c0c29af280b63f3af1426e392325ba9af1f96076ccb55165ac7752005a9fded260f3246553276c916821684bfa0f6f36d7ccdaeec8fdd55db396a9b1de3006a19581ba0cf8df7a14c2280e042991b1a26edbc108207d1d32945b69750ecf7dd4f67cfd5fca22e0a0941b62e1a047a0788433699480e09b156bdc128f1f1ae776f28f67e507d8a838b68b40b1c9a6f3926b306a3a12586cc2e6a3e9119b110bf149fc0b669d546cf9d5c4816c29377543226a8dd4771def0d1327aa747a435f69701c771bd49eea45d47a30777a02dd102885cf77168c3cc65c2869ed8549c551e4d30ccb0bf2e23b3ed59a3cbdcb6eab2d2fcce1156e1c872ada196dccf4b93a59324b0f25984c6ad3a0f22f0f865269eca5d5e17e11a11883f49fa9233f8451d2a9a95fb5af6250cde99b0ec980e2b93e7c4a0f91814e17105c3243e30cd4d1d2c4aa05ce90414b79598a2449ca657e07535f12ba7fdaeac3516b9844dd0e9da42afb91a372b8ed6d7b48871457630529d2745fb747f279831bc5084caf1676f8780dcbbaa951a84da68eaf4c422cbc8a855a6b3b0ffbf8d5214f2de6e96a110d87f3a14e4e1e3bd2836082972596a904c619262afb1e15aa0401c8d4bfd83bcd9edc3294dc41a24b71250d2079c2357de341fba8bb12ead05fd00f04568dcd5b487f40b1f800d5a1f941f113f3812057f18423c00216a6d8178dd238228d00306a178741082a168781ba2ed023dc11002f57208a87ba81620e397444871220569a481f5c5cf80e3a670371dc10e0d4b84c0f29d6a43f3fa501e15a026658c56eed6145eac6ff48358035040403298fbc900d877ff4036209eb21b907a73a3e85841ae3b1504d86fb5a864ec174e0c4f4b5dd71f0bb2db01702b5feaa215c570a6f813347dd76cac6c8dbfaa2ce9524cffb25c1f5ddb5a2802a8d95cc465b21f0daa175235e62f471e0829bd19d507526f771b16e0a805fb0eb0c3c623ee2cba1378e7830659fc26efb6141fbd93b1dc2944f6ef7ed5dda89ff6f9d918d8da5bf1aadd8ab4fd5b31f9e865d0de0f51a3b68ff00f37a829b1d83407ef86dc95b816cf2ea7f68964da82c40211fd4acf1ae2ab393d4e255827d64595a3d9319e6533205dba67daf1e63423623a0eb5a0bbe8d376cc9bf2dd37483bf14ab756411e3f4e2a0713bf9f83c8552447d204789478e1d8c3909c754c19457f6b683d64627d69c330182976e8be9a6beed7c821f309ae07f6ec3fa37ccb05002da385cd38242373179f06b6eb00864462d93a53f2e8adc318a805af22bccc8bc51ad7573be328881c50436ef5d11ea83490ea46a30061b28dad2a0de7be8653612bd43a7cd56edcec72dc96f4f687361b032c9513cd3d131720e2d98eda6130e3d4502c0212987482c5cfbf8c10ca2cf4a1d8e74339deb897f696b1db28f92028fe56bb8320c195de14c78c644cef4c6c502d1cb30dd8549fa1101be19686144d2a81e2335a1b9ccb42430314ceb341a25daaca1afccd89d09dfad70894fd2eb26582e2ddb3b24d91c5597db9f407fd08e8c9e678b209dca9663fbcd66664d4f27c79bceb6eb3d8c920c6f075e9dde11000d052bf636a1a46ce3a771704929134b16329e5c429e50305776a51ba467b644ca059f7a5a2f0457b31019d252d4ad0ca05186b8b5a9869bfce687899018446f6679acbe5f675b94aabd54b844567dfd9c8c6b79411e09967c4d75b40f4c00e2dad76e12515f7d31188bbbaf7694596301dc98edd711cef36a32ea72bed71581540695f1cd7b809bb14b16b1c108387c1af8c05d9d6d16d276aaafabc383fc5a98e0f5ad37316145052e5f5029b636ff566ed8053d9532a1293478c22f0b47097456ca1aa0f926e82b3b7ed22a924cefc5c331409aefbb405700224b53aff3913f1cee847ed19f5b372887174c2eae6abec5b2c9abe5568d294f3a180bc02708482d6458c99576540d5beebc936e0ee9c12a0b8cd6e422da4d93698238a6e60e49ea0e6d4dc4e8713289b3ac85607ee16e65c214ee03e43cf5b5682184d3c8c4ec8c9bf075c3e685f2fbfca9c520898ae6d445e310135fd0f2a909006ed8d0e2903e405dacd3ee3397b270145a49a615bf41f9ee872667400cdcc50fd83477514ad10ba9a5dbb8a365330bfef0f583cc071c21ebfcce2c1df8a25f824ac0b41fb0096b3d021f3804476c9a33d9a5c433e126299e3eaa2fe67d7e94f6c7ab0361646bd01b89fddfd39916294114910ad3808102130046e1a6173a29145b5c364f0659021464708c709281353f4de404d79eb9c7df5c2383429e608bb8245f10e2a6e62621024ed7a4a3a0a2b788e2bb99aaa1a052fbccfc58241adc855e4fbf7a6877a6175937d3d8ddab317f566b9dfc62046a934d63ff6793efc9414cac733175311a6b59cb8ca98101d015b61ab873c6d197beb225df3fe640924ab61ab75d3784c0b99898b022713be41bdf39204c485f3321f677d13125ace169b02a0e626e69b7593c618178f5b88ac49df23338f0e4181b86c40c65d293a76fc6b49ea63ccf1d344cf9d0ef7aedb5b7dc16225d8bc6922b820afeda772834849dd5bbb8cbef33337a0f9a65d1deb908139a2af68337bfe1457811d77bbd7788ccdae93e752b5ea42753cc906540c2a298420100481419ee1f302801c4181482a32ec4924d88cf72374e99de4b266905c09c249b7aa0962485d9704c55773c89f66e38b06e28465f1ff0135515340c02155a9163cc4b3e864d987f184bb7feac54be2946edea5bde603b0872a93acbc5eb2124165062f39580ca6f9bca8c81375895b33d86cfb51fc5043ad124612fb52ee48016a5d2a2abfe3faca798977151669a1d3d171ff159e803ea28ebcec62f26fb85d5bebc2f081a7a908efa9657ca5b3335518ef94679a9e9d644f0c5d5a1810d90f0e9090fce36797dc35994d27082162826ac4f4616493e45fbfb137913c46176b48415026f9d7155c7fc969da5904611944ec9ac9f79073f6bc9ed68ba0588724b279088e6a8ee1dc4d343a32525bf9e225dd0449a84519c6c680c9e1192343550fb28a18c46d92703177a8a2bf315ba8f5aa6f0493d2b853eab3e221bab8e0091d1ce2d6c371a0344a5ca09efaa0c66dcd2e5172648821ca3deb733bb0887a6a57b7c988d4d2ab31f7b3062613d3200cfbc45e2429e2103e9549864875ce3f1134d998b4cd8dcb6e97f6917f5de30f304f2f24b226f9e76a306412e4ddacf6463f16625da2c0f44543d45ff9b6336de0e34749be125a49e6bb8acf0c481d367398be162b4fd6d090b54e44765821fecebef1e39cc5ee4ac8130e7cfc6924e9425b67c86a541bd23d484888a317058b93bf65b2fc50560d7746ca3485c5bbf63e74a3ec10857210869e37f41aa1376f38613ed8c57e0401680082f7a24c70a3c1e0484b9199993d5d349706f90471adfd1b382b03069d336b453c66f98e7f63954a888ced1b70af1c2b68f0f8c4026498d9844851b3d1c0a2fb3734b6b6eed4fa6ff0bac530ec96c44083066dfa4486ebdf686d0d4bbbc659217f130a63519b6c685c00fa13524225945c7907e2af76088c8f7d17a194e7b7f33f7efeef7f987efa9402c75f15003ee971f067ff170d34bf8169692e074d53017d185881034a82fd12fa285ddf2973c4b4dca53d1fa393f22ee3bfd157cdba0545f82b512051e1fbb5405c81e8dc46bc007e7f0c901043a6bcce59bba02a149c323de6d756943bfc463693be1b2f2654bccb97503415078eb2398013fc2391fe65c71ca3ad7a1a38b8137de73fa591924c5a68e2fd240e609e0d04d5262a039c5225e3a10b87dcda4bb91467037738b8c7f87dc179acadd4c59bb76c1d4c1c4a325582f59a35327965c042a9fd5df21e19971fdd88a09955a2776e5fe14ec78fe966bdc6f819485b0010d100032ac8b25c5bcd137965336afbb74380f14e588b0983896e49b5efcb0aa72cacd4c25d96f200df37774a3086c65f974514e2360c709d1003b26eac5edcba4e4a837d6ef206cf401a5c4d393390817983a74ab421802f7f7d3dd42b0bc540a4e21b42fdb7f396899dd0bc1d08686e32ffcd24fc8c873d6a3010761320335a86951b320c15c6a375d105c2e6d90563ab0eb5f94bd769eadf651af2cb7164ead7025232eb4648977c81a401fdc6f60b57a39be383085c200d50bf615c251669e0a758182f909ab9f5709bcf934e0862479df07d85e5414ae0e8a142ca153fc280d3a93274eb9f92954c890acc2558438c5ea1d68fa41ca27a027e74709d872cf7c2b1ade02abad0125fa0e0c0de3674f398bffbd6ddc610dcc8c34e9189a120718284a34c4a51ebc665e1e8a7485596a8ef2f48b6c04f5d92629bf7596a2094c3b29e8f4b665967fef934450a2b09f551ed975a9b346603138a8e9964a02220a7c230f2ff9356243c80345dc80beb269122dd10938da0779e8301d6cb9a60541fe388995af991bd0ffa089e6480de2122606ce9e63b987129a59970cc26771a21583f5361acebe40a4bd2f9252e261c5e50c068876fe0819a92f9b3134679421f0991dec44f8272b905de62aac9df852dca4df0af13b3b2c0b1ec05410a78b60bc80eb8b36a1a99e39bcb2408e9f13259ab00f9b8679f3339202228616c76162c433e5c851cd9eeeaba1dd9d8e76b92e5a6a111ae9548caad8975974d9eee1ddee5880ea880e03a9b70acd75809f1d867d9306e2c872094330252f2cee0a74ff608ccdc5d622fb83ee196212b35e062c79b07b8972d350c6558536a1b5a20606fa12189df5e8a3687cf1b6d20b071335de370904669e446d46889fe870f8e81bb04734ed1dd6940f6af844a5102f55cc22c3ca62100352ef847127800b7f347b572b36ca2f725163ab1a4f759e1c11f996f14fe9237a1dd235ed6131476891c7946e9893c79c1e9ff693fdfcb2b985b982b1cde4fd24b9359140c48524a091c51c9fd5e9bc257d4fe99899d94e84c668b932cb34e97b902411c18cec8a4bffbd5fd46f8c302ee06c38675f360294d037662c4f1ffc6b8ab2b3677ee80b03914dab72149e84db8effb0235373778a0e8f5c21ac08aa4ea84ad2765ea66d081d44ade9d23522a84ce64f4453aa1e7683e3800bfb718bc126de8a1ebd7116234fe5d0ea66a1862ff0252691e741ed0330b2a845844e7bc73accd757cec216bb8fbcb09784fb79b664180f78804b22e069038b45038b41d8104833dc6cbc80aa8894dc0c83afe91e2f42060f3a4b6d74a7c83fd23319638cae0882c069b48ccafd37f8339839f39335f075d0e33ffa34d9f444d5324538e86a56166ead79f9a4e7825b1b677248bc1434549e2bc53980a75d1d27f20064d319fefefd403384f922fbeefabaa78905c18d6f8980ae0f15671c956bb37c58a57dfc7c7f2810e0b7456e446d03ae51e35463780e0dd454b3f35dc1f4066d1dd05f241fb4856282640ce50d86629c08ae900231b3332625ed04c5d466aa6a7165f9cc5aea236e2619fde94812180cb6dd26f7f6513fd90dea50e0cc48cebb3c185925a932ccc9bbd9c25eea83ffd9d26dbd10f676eb9580b2ed047beb21b45483bb89f13e15a3a3d02651f389d2579fb55e4560dc6857aba1a5a54bc90f6a11386c4934f2a0083ae17d6cb7dd287a1a831afa6c00d041f49302c1f0c250ccb6c150c14cfabf3a4ca39cf67e66770589043254d4b7088f2c37aa98c9f681608e250b5c4e74aff00ce38d255263cd61480cbf7ca0cd87d8ac946816a7e5c34caf5bdb12df86eda77352519dad50904bf180ae2fd6d261784c74cb93336fd259270bcb61e0fec695837b3c6d91771518676fa039234ba35bc31d74bfad7dd8625debda5b7c1122df9e7acb17e4923a9fbf3be07883f891fbdb841dc3d9c7695459097a6864e3526f3303c7abbb853e220598a94c18755499ed5733a8b3e823b7d567bf560f0d5735fae890a3eafdd14639e7610593f47d491a815cd43a330c670504348201839cbc0d30336e8af35e00f2c3654507aaf9a50ad2f7a5e09ed49375996216b50e3718eb9dd5d2d7b20ceb0dff8962d8e496f775dc13faa089e97e1138c2939b9a18c041cfa8aff595cf892d37f38ba60f140c27a8043d02942ece898711b3da43d4b471dba9244bb7cc299c6308d4e870fdf2d91966fd53d01e32a18af5fdb9470c745ac90174010ffa6c782a066dea412d4464013c5359c46660a437078b88748bbb2326ae86761c4f7b37e7751ee594debecce4e8096e9cf93acb07b66e5f201adef44072a9ae5396daeda243d8eb7b6d361c36c397bb45d04a781b3e80655cb46fe90e6263d36e4f95e467ff41da8c8709b721e071f4ea8b52a58cb4bcfb4e46b4c092652e658f22868c5c18d71d8517a04e6e9e0a361396873d3101dd308247346842268709a59bebe20cf28a11f9289e4f7590893d76a75fff1ba5c0b021bf7c3fa9f7eaa0fc2c4ca4a3e3bfb7f8b97e3104eec261222e00c5b74584b9556a80c161e1e402afcd5d77ec8c1c31891dbc4dd0f2cbc141dfe91701157e62fa3835dced2b920423b150c24bc53ccc3c45d82ccb12b601e6be1e521ab5f1d6de7a721af629eb304518cfcc1e29ee88af28a1bde725cd28d6a446e51e1a93fa7c7a8637476b4bf20d0a250bb84a237c66db078901b84872d8a8c0aca8ced66270417510ca22f85a4688044f1eb81009406bffea5e12ba078145bbc2685a13a52e3436c73e1edc727d3c3bdda3520e157f3dbc119238c1b777fbb9122b40837f88628126f1a79c7f24e4b65e7a308d2e21c2602b3968226ccce8bc590568d0d8f888dacc0ca77f93975b36a7eaeb41540f8c98b1def664ae75824e5d678396e3699c472c9ead5c236913673e2af555c62780488ff4afae6c234b42acb71be5cfbfc7ba499d7e5568336f30ebe3016e9a50d306c591e4bb76710a6b99256098250779503fd8f4996c209edb51647c5005c092eb393785f66ff771b5d2b6bc62ba9453f092ba3c3d5a73f70b1907f61b1ed99fc776909ce03bce5e5bcd5bc8580657a574b0c344372d1f0f8686bd147e7b32f970f5de10dd413e6f7197c4dc32f3daa2c50f29919d5710a8821267e3a9310ddccb2d2ca0d530405e84534d92be5e302867d28a251609c34f0050519812c0e1705609ec94716589a3216e231b5e1118e056e7187f848550f8445bbc3f5e8ac2fd5784b6cabc4403f3a0e758b7605c0ebb25afd4be47714e17f6d3c01b0525549ac8c389179354c54d58c14e36af36a96feff32a74e34ab039906aca77d148bea1dd5c63ae963ab6ca4195b9c2030b437d28869fe0d43c5b85d7112d9b2eadc1b17b131bf7d334fd17d220748e4f0f60556ac1c018195f5d4fd0170a686bb48376cb8f91eadcff4f837903cba62daa49bfb1a212e5af584bbb903df4a726489002127e5642b7c5fa5baad017e639f3885a6ef35b8102d4a1ae7427654439282ec56fd25331f15f23e501cd7cfa0bb38d6e6bca2a0765d643978145c26f2cb36dcd86de1ced379e5a28380f808de7e328353f9e5aaa73c48f3b1a29182ce8dc840a258711a90a6bc7101e27f890b387870964616e6143ea027a59fd9880cef92e31cac2955a7c77342c0ebf629d29ebd12746705d6ec2fb3acdf66b9967172052b33599a23b34c03ad91ef32c913890c5dcfa14bdce08621631928541708a44822174712b4efc03562582c160312077f6a3ca72bb21737e6f5654507706d3ae5e1ca32ca7805da4c574fb0ce0979c100726ed833c149ca2596730a508fe2a8195930c48010684a9f828b7a537c46aaacf5380ec26ecde403e8acc4161c20a8394b801af0e3e83de5d34bebf73d68bf36841650c08ed887c6166964a399adab08064ab618dc2f3936786e5659807af5c940a4c2a2eb25853b01e0749021597400d44910ae424e810940245a766995cf1cdcdd930e5238b83ca19343185ca5dbbe12fbd41cb4b7072f1d63bdce38920714c2f99cffe8445c98b9b0e2893d83deac798dbe755fe2eb064958d7b8a00e7810f184f7ccdce7d54da7000da75e0efe9a2020a75c3e86930294bd100c6991d8c5b9068e5456475150f3a4543e7891a6bc254fc2c95a769b2798b61791eddf6dfadf3671d68047aa61a7a77901a57d65a2bde4cfde033675f9cf3919953e07ec3dc5590da785f5300bcbbb67f5d3e5b93837e3ac2325dfb31bdc62128154a84fa3146b9fe7f7e5de7e5118dfc030cdc9b99d0cb3c595153f57f8774288ffe5e13a1bc611c753d4e356dacb9b8feb72193fc1b68183da7686a501711ba50c3383828a96be3ec853c7ada60a27e24e87996de0662421b8d537d5eca78dbe37b1991251b562baef329dba439daead98fe70a03dda0a56f7f560423540c86a56042e6250f6bad920053d79c11d9682a0734526fa737628e9b67b1f071133235867144c6ec1eefaab320c24391f401ac1fc44d777f55de57508daf4f97ac224018ca7f0c8a5b924271a5325525bc0973ca538cb42b5cc01a64a2c125618f02be18a0b496ae455cb1f7d5cf546356e27bbc0646c267fa5c0b3de3888276ebe92d9ef749f2b3a683aa07be355206b9aef30af9b811541d2d81492d181915f5a07b98aecf5e90950b3532cb0324cc7d47337f3db9b1ee032e1d6d359d61565c8902d0d5dd0ac5669884e1afab9210f988fe5c9ca6c1ee180ce930e73d99a53ffa67077c656e8803b89d84b7dcb53c9b3271985972c868727c17d2f0c7dab97ccceb505cc5324bc274b5a6d0aee3367e397714ccdaa2fa61c8cea112788a50509644e80e9bd05c39a913981404592bc97e85d5117d5f86e91687c56a0d8c6c9e242f2cbbd6d585cb59b5b224648d1834e65e3a710e4830b0413608566d1159fc02c15fb85cd4d79121f9ad6af15a7a7c1b9b390d687fb331bf7ea54e9ab98479f116242bda788355120c4f2a787e24cc5c2868975dae588f4ff6107f5c8dad74bdfade9268283a2cfeb985f0842cfeba4f50179ec0fd1d52b68f55c398281ae1aa5fcc08a256038801002b2abea0a499891a6e5fbfb05fdb4d38995b2a08cd98f26f7889323b7c05bd4c17c738b88c488c29a210dc30769707572e9606358599c2fc0b3acbdfbcd97f50dc63d05bb240118a2db796ce84030d67bbe948b5a35442b7fd28bbfb155d0a11c459928aef4e4ef51716350364b7aeccc499c15fbcc82ac141f59cf8c56a9d4694ccf690bbdf6a67198fd905a205419ee0982f83e3f0d53134fc3df9387b19c26299b49c7eb12fb60eaaf1256c9ab9bc355711b9a8b870e571a1529654f8533787774fa3be51edb9e978a18eb14244df9ba9a065806cafbdf848a0e021d4fbbff1e0f9597e9b665ce47e54310dda74186b7a64b4e7cdd1bcbe73628715d633c1581e60625870a4087aa343e54b560fe10feb802641b70b14a88d04563014e151876d25b5db543e4e56ac5a38b05e42b63db694dfc96887a18607e2debe3ed46a0e6850acde04a225a04e84a48db245ad55138e1f64ae21f5203db2b3ebf1ccb81c28fb7cf563d71ab18987d3d474a2fb7ea89af80cf3adbdc35b2b4e02992584b05d33c8994bbe31523a517928f4d1ced7f44e5459f002a3661edb09c0b02bc98c0ace8d311a0b22b114042e11a0374cd4126e08d1934ef3948d9497f67dd652690fd559fc133168d8310796f7ad9859b7a4b292a450feb7da74f718c6eb77b0026d458390c97eb42fc9cc8be20a6ddde8f468ac36b41e4d059aa5870e4c9e377b4fc37ccf4400f3ac3b640fc8818733ed09538cc4039041274dc0d874ec97ec4960e6b858552a134a220be39147a84e38167b910f14149761542cd551f85050471f23711d566cd56eb5d22cd62b6da06c0cb1025e12585c412a76021ad3b252e4e4a15801714a65740986f8c4bccdc8d151012aede85849ab593dce2cdb162ba9621752a9096a24179a1cd8f9c6cd91400e6836a324665cf28c6577ea4a616fce201360082e06f75eb39e04bd0d5d8dcf12e1d93fa00a39f1a2be734f4239653d8376df0628d9fbdd4ac06ce1ecc9654a237f6199bc8c9d8a677ec55c84c37d8b9ecfbcc176beef2b0cd5c52b7e2d950bc6f364a9b07b12466dd1d9a457cfbd8d2663dcc6d027ad56703c2fcd491b8a9083187d212b0926e08caf54fad74a9bc857ca508b34a19ae9291f368ef57070d837d8db0c2b37e7e0497058debac08897e061c4813dbde35f021148b75c14ec8308b28a3220100f2aae0d23c4a1880e988f646a66c05531ecc0a14d59a3dda62599187b939e929b0729a4ff9812accdcbff7515e3043307dda9365d50afdb8b2e8711863b0aacc67a20c6ab48fe186e2935508c4ce5f86a2172b34935ef36538270d2b9f2f421447c9f4e206df2267acc16226fd158ee86c75cbea79f90799f84950bef8b53382410f7e6c7b00c0330a6e767f75b8cfadb6cd91a1e94b0b2e738bf9d516f3da011798f7a4a9e4f7fa8b8afe69750dd422d3c9edeffe624452a7d77f9e4846c3495941b4d36bb0bec509c2ea4b96afad4e12810ac6b772534a98808d52cfaa405ab7eabc659bc26dd1e642a3303549861f6953d246c6c6313926eff76d5cf01ec6e6736695a435686d13e5d9ef624af77a67d734a91a411bb3734030941ff15d60ebb07de4a048104bfdc8f601f5ae1f2250cae4fe4d01528e81e42eb4fc59a8de322752a6bfde882abbf3bd30e603e5f4f9ebd4e0de1dc7c6817e0ccef42c24780d9e309175be530821e4ff0d19a149705c4b009369b641451922a514a5b5eb8d4e568da34de7e424b004f1e18f2f0c1d652827da119ed702b6a227319595dca515f2045c161644dc9ef7ca26f86cf9ae67eb3dbff38588eb522565cba1f63097fa1e6193a7744a8b8b1b24d086d55ed9d7a915f01dfa08dc3465d789a6956c51e9c9197c8507014a7ed28398799559fb89a66bb8677415f624803e8799a6bf26b1a85753080b9cc9a2de940c1dea22090f225620a577476e09512c4ae470c78f8f53db375c2c4260a2a28e460f60cde98a3a50819a8370a07f194841d31598b5c1cbe97b5123f1d9ca7ba5624c787676ff3210ab918a1a24a3979ec583a037d97a73ddc89bc42f591724ed40fb4c1bc04434a86261db7710024ba3cf81c0fcf03b44872d53b145d627c0f4950114b7eb49f680c4c73c6a6cbe44e9aea3897b6b0f8bffebfee7fa43ffa9b2e5f12f1bef32236ff48265a3897d9c5ae173b6ae10f3ccb665105764e112dbe651c9ad4ea59c19a1640dea980948154f91d884e802257696197666a84024dbbf0db1ee396a0e335529967d98517b3a219af61566b501bff3389741dfd406caa50a038129467c81b6cf8981f1e1c6c13952d774b9862354559d4aa652488586e21b443c12f7c98b59da45c8b5158e284c61d15325ca81b53e28c83fb5c4773214a2f9fee4164c6f0ac14add4a69b8ad360c29e3be0323b7f81638ec9d323c2e483b26d8aada4c9f153e864bad0e9da21f95ac101a5ea103e21f1c917bcd8b215ffe30b887a712230226281773704e9e0c74b5110d7a43d3b3b05cecb4fd09ffe32545605ba9bfdf2290e17c8a57bfdf4237a2d312075015e37ffefd169cfc55e85bb060e5b2adde3cfb519f4477b3ed015dbcaa1757cb964f455ed7db91ea833eb7c9314e7694ef132c05686fbe7d7a40fe39fd1f977daa6345a0e6a7d0187f8db7c20445bce24ac3f36286ecbf0a1dcb22b395837026427953003601f4a45e15debe58ebe0084cfd3ad538b8c62ff65ea22dbe794c19834124a23942cb73cfd41968efc3f65baf453b0b66e8579296dcf1ed734803b44475500b7a65d0e8120a4141d0555502cee1aa49542c50515c1231a1d289becfddb5d482858e45010fc4db4131e99ab54b2d9dc036b060af0e348b4e8736ecf57273b72dad3464f2140bb1906688cd2285a1351df96d12c9c1924355b6b0c30fbb8baf308273439fa928f10d4147e655c5da79bca4b24c195b18eb12e8c0844718161d5afa58d83bfda926483267637d70a8d84ad4b7f0b50413cabc8bcef295ecd376d7e012a57b1e1ee7c50d78451421e437ba8ceeb5604f2ac527c855b9f975c16c9df82b252238d265244cd908124db9c991dc5ac27269d2afee838ed05fe6a2d0838828b0d156aee79b17e17c653c8d93f3b0271c72efb95185624b9ab65110122853ca8b0ea92a9e2856890cab741f77a49d61907b4fb737ecb5f200f2066ba6b8c9728212432ade9aafe46e81fbd914c959d5acaf74636dc3930c375986f6c02b93bb3faa486911744259558350a53d0ee8c285d65bce272bbfa252e5ab5401091e12aafb3da8269b897ada03631c0cd647aff46336074b3aa12855c0685b59b65a715cb95020801b5415f5d5cec83a06a1494027f9f72ef02627b6f4f2c27b2957765ee155ecf61166e95bf0c06ea6c0e28f4440661b0c762f50e4148a36a2348a521bbe5b08fcf60fdddcda98fd00346c63ab30697b8fbddd84c665e4762ace167a30d141690130a9ae50e8ed290c0b47e18eee8def1136f0968dc0268873f87b00b4c296c21f78dc1692a355f3d92f27cd276c9e4401b72a363ff24598da1adbb540c02f16f4e1bef041814a91fc7063ed24309563cc515652dac9f276f7cde48a4da8252e43527a5bd3bf4eac57e383d80b8077069e0cf5e541a49c6610d48c742505414ee600bccfd47ca2fa5010317f3553a1f74a3542021914a26a193b8852d6e6248baafca4f81ca6938a514af70120410c5d2f5a2880c4719867b1c955437d988372f3ef0a99465a6c7c4532199b8faaef4948eb4bf57bfd6792fb8f9a10ad98ca6251ea9bc4779ab8e659e080723397bf22e8d0ae12dc7164a0c8c983f06a9fdadb50c944cf0f65637be07c9b0725ce8d9321c519c19ba9e0f5875b783d16b817a38b798ce912be77d953f438c3b7e76168153ddd6553f0dd150d1d8f8253bbb7650041045de2856b280d2a599406faf274300e15bcd2805d14ca90bd34e845a54344d3cd8d48134bb3915ca66fdd73bd230cb148ff5449193f3a26bdf392f547da71ae5863a11282c7d8025d81b67e091e0eba89e05a7e7b148b84c4ec0805314bfb84d26c4a7fd3f086150fd82b06410ff10dec7df4e2ecc442fd381b33d7796e5aa1358363b4d394706dc43885319cdb93c94bbeb9cf7e7b484f5ab55155e9129f9344b8694d223dc85f444038233ed7123c3169e8c0065c0adf09baf5cd9454a3f24975b1725e83902c55b9ccecc69eb8ecb898a3ebb2a4010820715bd2a33e822de281f6155b3e6d591e1e88e0d454f8f10c2d714241648930819ced2a3ee4dcba7dc568c30d407895c9d3fb721872f582c56deccba25caa523a3d01cd4d1c59f78da054c631e5273505e11f685ce2146c42070c54421d74a8a2c9657bb9f3a8663f9a8499e247592f0ef243e96129c6a8008ab6d1c7a16d08b7628aaac320027ee33408924786f7abff2ad859509851b546e33638290b167b6da07eaec8614922ec2b60314e916bd093d0368cc03323c3d025715998149e683eb071213871cd1231d3ec40e81283e4f2a330fd262cc590944a20e454fef46330809d9e82ee3f16c3f916f4f787da0c270660fd68f5a6cc0ffac63bfccd0ded2d40788be649a07c55160ddfcaa3110e86678060acd526c84105f79f6e26bd9f12a2c9c11902f917aec45df262503aa6a5feed6f2e451ed09801929ab34bcc455bfbd4c5a310d45c205204ddff28de87ab1bfc4083a83bc9af0ff9f42dad1aaaebb350420cd02f0af7e36a3e7a053e6db6c9ebfa688d5953263166e7e58969d6a38cd7070d68c65a643d24dbf1d73ad6a39fd127c6583ce81598ace6f7de03208256e23cbd3c5d322dc08ef08efe616bb4fc8c24fe0318952b3da9b9330e16f54309c278968cd4700fe8ea72e34cb946cbd7ce3ecfaca9fd452bf450f0c5a1b67eff4200c20ebf43683958bc6f0062676168c4fcca02cf96c20fd89fd160c1ff8836f4ff50b784b62103769d5a20e8e753201ccc8498fca55682daa67f6464208a944e2d00362aa52a00412d1a99a75fd462fdd42e86f1067a5071c218c7c0d2156bf8f2ef55241881f6bd50b3b1ca2fa70952cbd0bdcd0c0fbb9bbb795a663d50093d43191010b7b0dbcd2fa44960dd97ded60cdb704194830e0cab7fd02bad8b92cea7124578b8d47ca2044d483d2cab2ea74cd5e9bc399314f4902f5ed361cb331678c9ef2f539a1df2ac3142254360cbdad389292df8770a1a631d5ef8bcd520d3d29216ecfb0b29492442998f76ca427571807ceaadfa6674c15fc02593409e3c555b31865bfc33f7e1f22e44959ef9ad8ba5a277ec2a08a9f03c413bce39cae4316eb6f3b1d6d568caeddb1ff35858f6b8595b391abd1f757a92b94086afa18f602822f8ce8de68f70d9e710aa732fa63f384cec846140678af8b3fa6ad59a19e82198224807875a51378ace0079c403819ddeba467bb36da59b3d406f5df71ee30e003d860ee01bdc8a006fb115806fe05700dcc0ae007003b312e0267e65d6f6eeeaee5b79778f567ee108547903aaa6d2895c3052b29a0e66f1684e3d4493f016222efe082056f36a834fe9959a8a7b01f479944b6203ea68d115bad16cbf50a18a87888af033f7d17dffbcf3ed0362e1f9d64f9517ed129555714dd45ef5ed6a9aeb61db8decbdb7dc52ca94920c6208f5074409ab1e24aef36a0e5df43ad6829166ae98f14c4848f1660824533c4d8f913e0bdb4c5ea8f8212121695cc416bfbef80152cc113f1487da2a4aa313253d2063b658c1f23bb1f4635aff27926825e6f9aebeb9fbca3a0e0e6ae977df75347c6c299d38565ec86d15c63c0c639e66695ad7e1d80e87edbaae9f8b449b7e221c382b269c848f3cc33c22df6523a35ab9a391d2e6441424b2284864247043b663eebb2ebc3adc90918f2d27598b6c7574bfeceda329671bb242b8880f0474667bd40e05dfe5628e09d2ef6678e9cc040358ae7f5f844cf0d21917c0725833d4377cb766c806e0659b338a37cd19d59a21db34f4421266330ca07f9c1b0ceb55b776020c17c813f9d8caee8d05df5ae258072fff4000e755ad859469edd6094e29903dc15bb5c8a7571eb2f063fbbbbb937c357a55351e5eb6d6ee46af7a73ce3b770d9c6110a7e7633b9b0b719ac542d462fa607ba303cdaaae7dfd316a3adcc694a3bd6cfdc067bbe2d425bebb4a7277770e9bc8ff78485baa4ee7acb2ca5abde5fd7474ecfc0a7c4c7bc8f7971f896a18b2342cd97e6fe6c8f64d798e63013807b001a356024b61b054de1a6b19433adc90edc1b53e7f36127d98fe9b91cfaac8ea9e0b81b9103acb395acb948a18c8c805187668f606b1ec8266697877eecace4a2bbd55a3dc4f30122de00076feb5b9ab0fe15ffef45d083476de173bff6eadd4cfd4cf84d786d53e84f0da307d3135156a1f6784b77e618c36a8f0149ac2191dce812e04b1f5ab46426b448c3a08731a17eba2eaf6d08e4babaf7d0af5b597af693f79ecb8b4075223431f29ccefea0cfd2e0402247e3504f22485fa5a08e4c96da27d0698073078e2e53a4190f0a5bd160639018e775f43b7d971d178d5d738a915d01a0d19bee8722c3af879b9e8877ce436ed217b116ffa5d1d05775c13dc71f14b1005e7179016dca67f05b7e99f210c6ed3efe302d1e5c205a2abfe05a28bcea894e34203714c0614dbc0fa13d43a0972e108d885e621930f20f56ba88517882e1e16d25afbe8aa1d5d1bc8ae09b22b9a72a2d5d13a3aa9599ed44317dbcf5e30035a888f90acab8348aac578583971227d1dcd6db5d6d7428ad3435c163e8697be46b5edb5908655d399b19183d0694f6ba745edb5e8d25b091373ce39e7e4f047f30c98f27c0e753c2bc32e12638c31c628a58c31c6253aa9b9bbd42878e94777d7a40c753c3b7534f8449d9f9d2fa59c2fe7f790e19471d28f3fa48dde61745d8c56f608360e590eeb141f9f292811ee6e779943a93643dd368eeb3acf33cdc0e0e974427d2f26f7efeefed13a2bdb355233a9544c0cc7cc130c4c4a2625232303334346e6e593919141c99c4c3232325c1733f874777777770fa13d42beafc8773f8fc23e4a84854e67c6f237c8337488cd185dec4ec62adbe5a49a0c3ee404493b81e24dfc8deba457fb8b3726695127d497fa66bed40c90167ea46aa0606052294ecdd3d7ab1930a89429272615131303332326e6e58b898941c59c4c31313101f09a745dd7df7ddc1dc38f8def1e29912f861af9aecb88cc496995409f468d7cb57a413394c8478d705da53d1fbf67ca89f186e31606ef477d3e0e9f12f93a540dd4a34c26d4f8be979719a69c1a5e4da32f7524c3cc8079f9605030303030273ab331bc3a74f6f2451799ddddd999d946f904cc65b6c889a4184ad145133a6cc0bc73ce1941a97943d1754768c124785d1040c800bab0f1c000a9a7cd6c2811a16e4364645512137762da94a894133f56b6eeeecec180cb41fc8e71ebc1dd6397e58b2894f5f096c07259f0774eb8db89b7b7b7370883b72c106dfcafbc40bbc423deb81629bd8a2e1d857ec59ecfbb2d9ffffcc1e71f65abba7ca8065d54bd9c956b205d4af7afcfae7de7ec6edae1a10ce59c524e29432f9461c496c7cc7432738c36f2dbc9fc267f65adbec6fc314763d69843e972ced07dc236d2d5a54fea7043d6c8a76934ec31bd47537e4193dcc716cfd70a88ad80566d1bede357d5ea86e9743279617df91ba83dcd91724a3965ad6df3f58a6a614dfd683dcca9218e5ec9b086445626544dabe075a105bbbd565df86c0d6f0b760b7b685f6958233f5e1254b24e51c20391a51d9a28c9a205b01790a8b8810b0da49012cb42e4056fc84e114fa60c49e1218a2bb09e0b38b1822a473c5952050c1e1ff18b29829003307e70d2451832bcc0021b9e2859e2411086cc3806244a5608c1c48c06214d0879e1013770c193256168c1831bf8e5238a17645c00834842490a66c8414f165d7c2851658b2a96387212e58b3204e042c4224a0d65ec60074d90901184174270610345aed0a0430f93299470994d86aca00533307c3005103c1c2c15c1049132863c41868fce85e843063dcc8498cc9210a2618c9d253f44312083e848169ea538c50b1d57940f194a2dff8cc5567d9e324bc5acada31692e5f0fe90b6c891234992cca0201b7fc2424bad8713166f38bc3a73666baf24e7f35d9565379960c3d250cee2cd0cafce84591956c9cdf61f6ef0b8bbbbbbfbfc68f299335d82399e05259ba5b7958e9fe5189fd7ea0ceb905aa082b75af03f406cc97777d3bbf74eb480113477ffcd7d07f52876099a40bba7d36ff3e76b9aa6853df427fff6dca989f61ed8843bae497d5ac322f27be694f2a7ec915fbf876adf53a9e6d4a74bfeb9c34e6be8a2aa8b2df878388b0f105bda772f7f4542b3507f7ac9fd5dc00852011cde10acfcae9b04f75e9e8497ef28a87a01ef53802da5da476b7a0e94817b203b2eee35d0041c78bfef5fdeeb578e03392eecd9bed2e726dc9f400a5e1ed6eb7eebc222daf7d4aa695fb5d87dcfc67d4f17cefc7b8b0bb75a6b18a38d8c1f9f861169eeb86b34e8a2aa19ac260944ebe5d400091c9c38b9f28cdb48668e36f2551852a2627d482ba5152b7fa6d02b293b7207e28d5c61e8bb3852461d113e101950830d1073d097331ee2c41c6edb67945f83a594314a031121049b96b29c119ed55a6bedc641bba9f49a1dfed2f690e18d10c772b8f2a93cf36051091538f03a2dfc206147d65083c9153860241ead563838b71a795af87c8c83f965e04ea6adfec9f39e4d5e17b24c6539f0a72736c370863f6b0881a185aa86101859f8b030c5773916fb596a9f8ec97e7a621587988dde6fe055d9edabbb7b78b7af324877d756ccb9c6adac576bcc7edb77ec42aab295039656146c58eda3263b9e3d0c711fdd9e7e6cbddbd4c95a6b0d25a76da0aa6dda54c32535d297da530d477329447add4be9ae79ee9ea6554d5a51e9d7ad7e57ab57b9964e48707ecbb881a9b631a10b2b5b7c2dac8de56359a5d534d7bcff6d203bd61f69f8381ec6d1382c242d596e9b1ca5cf1b9da1cfd80ac6211dd2eb10ce0d4d07af7ad5347cb7ea90e193d2325386f968d64386f17cb7d6dab18d352218fbb08c97d4da3f332c772d09184f4c820817dbdf5e4424b8cc9acb6ac85763c20a2754354993fc2fdc4c60cb626007f58afb8e7507c312c3502736c1a409364dd2f3193b3b56baed034c4b721b4d1b400d80b081e57737a130801a005183957b563e9ecfc3cbb0da499428f6f3fced41b3151cc9ca77bdfe6959af62316a39ec25bd6a253a06011c2b2829297d93b974ecb292872deb1fd8d77f3bf665a146969963e40284c709337c35f377ec8ef972090cbde29a08b895cf2af40a48af2270e391b7ec6fefef0e6b50903bb677f4ddb1f13bf68a7bc53e5f875765adca8722d504cdd2a078c3527cfc97812c175123fa847b7fa5150ebd3058cf437d3fc1e8ad1554b05e78bb5fd5f8ef58157ab5b224acf02fe187840a5f34213cbfbcc5456dd34f13de1ac28c97d7c7cc1e72c0147220c8fd65623cf44f3d9b787821178fecc551abc3589910a657cdda1e06e63b18eea3896b9c0ff216f7b70615cdf84ede735109ddaaff85f74385f70bef4c58e4e1e0c21b95a0efcda14ad5abd78fd9f587b41eea28811bf2757777bf04993bed1e135ad4d185a619ae27a97b1d3d63a9469f7e3abc7bab813ea4059f9f2ddd66ac6bda8e7fbd5e967e4f146cd4a0c042af66f8d1f8fbb3700f60fdda58bfa0f5779f71f722bff07c91614c8768643b14f7ce2de9b5868f2d77177e181f75f02d573ded376e55235b4155db747f1833d3bb0cf91daaa8f2689694b3fa0913e855bfec9c5f67c682ccec352c83df910adefa3ffaa33f3a61023cea7bcda2bf6ddbf32665d524eba0a09c949a7298b9c637373391da7d05672e100efc78ee97ea95f7ee8571cbd9b84da3ffd90eece12f2f7f29c97d74530e575fe35efb0da4e9f2755dbfabda34e56c7493d5abc034d245d5f6f304182cd7bf40b86fc288163672b1b1892249b6a3de900e1d367cdd8d2df48a7b189ec15e41a0577583a157b5fbc1a359bed5ed7566e456e946ab8c3c7ad53f6bd80ebc714bf743a059ccdc7bda735c34cd6df2d7ece00b8219db63be2967cebf513dd66c96dbbef2756678dab97df1ddaf89bf8efebce561eceefe3e1b8477d839161859e7b104c807198c7d988785188a0a4e67cda386af86fe8ea55466119566517ef5aa86797c7ae55fc33206ead52a855ef9734aa857fe3722592afcecdfeca6fcddfccd610d0a72d6b53fffe5598daa06871d81b43db884211cbd62662abf69370d6f0b56863d9ac1a6aaee4a9bca4a4117220e0b3a9c23e292855ef91748d6a9587efe7cd075d1ab60f7978b7c35f5776cb5818ba5b1912816b3353bea9bde145ed3d7dfb19da905f9f341207f46dbdd8e9d99eb9bbe73538efb9bc068b98fb68226446b7a1956a7afa3af09d1f6d0911f8240fe7c37e574fd8e8217f4f143da186fe84fb007ff7c25cb96ab20d9c861cd0ed95eb33c7aacda771cd8d603b98b8dcdba418458ee5767b7efc0ca81d172dceb687e1b46c0ad6fe0e6c51b7e0d34c59bd48c871e6b3d4044784b24c1c40d256cad499fb462c15b499f54352d31cb13732a9b37cc54db4b54afe68019474382ebc6f056e771347833e555538d3759e8ab599752591da388efc6a4246777945b19de12580a745a01a53d5e3f67c34789a04714cbc645dea2eff33d6e2fa95ead787605670c9c3fbd42da81b482065ce4f374389d603895ba18638c318620869163e4c8d1a54b97eeee6194dc4474e1f518a58c31c618b28ecf983fc618638c5d643645804dc0618a0033333333b33b33734d04a6fd1ed3a7ac9b38afa05b1aa63f7db799724e753b6d26eecd07cae3bee3d8a2bcce721d68faeac37b1782bcec697b0ef4f1e3ebaa29670b4da6186f0cb0855527e5d31390840a173aa9135321d86bad35e42427b9aa3dfd7e39b95ffe901bf715f37dc7c1ab82f50abedbc809f6fb943588dc91dd45b03b3337f7f80ade39b37506592098b88aacaf3ba621e49b305b5730914580b83a85b77a33ab7e0204b98d17deaeeacc191f413465cef808a223706c0400e752497fafeeca6a7e6b5857a5ac4b4d55a3eb7c7bf7299d2d074699a33dad60ffa4a1b20594ee9dce3d89dbe186443064b9214bd3e5e386da886fbe7c36e5b03f833adc90db1d314b1244b8a19825892175a80cde5e82b78497bf07b03ce3af8d65d0f2c9f45fcaf2a7c2ef5b829ddf7d20dbffbe2fbcf26bb4735e294d39db6bfef2d2e89440f905a6440f2e512d6d5862fd9dfed0d561e9e3c081b3d50daca026410afea052eef85eaf97f59fe00f6abd4b972ed6a3c9236d0f82635eb69301b7a75cc224c999c48c2c943412db2d830c08ce3ad8feeb6aa5e17c9749a97831e2c228de784954f39cdf374982ca1c5b33d2a09ac8ab0b23fd6b41bcea76f2241bc716bfa24d7f931528a389b10ccd2de8818b922d488c843e2b46eab2160c6ec33e006deebef96f95e3bee3366ee3366edb36df42f6a14a894bb5a9f82e03794b7b060282c15b5ae851afa6f839046206620602ea959cf2ddb89402db615cea983b8da2db2265777787f17578a9855e75eceed857474a29638c1ddee895571c1c375675e60b6328ab9854b424f1c590c1e5e3228b1426263e98293e3f40383849f1bd64610ae2e3ae9c78f8ea1413966ffbc12485a90ab903a5d4082ee8c6751db751d38b0baea30d67e70824a5d409a53407a69479666c64d516ade0b46e29d8d166fec6d9a9c5d5941b6c86fae4c8ccc3978242e3cdec2092074a2916520687ae255dbf729e724e09c5276d24c2c106225f0c1946be182990f868f8381b896ec80187cff4c487aae1d3b60cf1bd6461c2e29bd144cf77c37702e2a33612fdfcc86923110e3910e9ba291f3f09a6180620bd1e800d84d784605f2282fce9b257e17b28b324c67be83822b86d051c638cb14da70eefb5558bb74d6b3612e180e4c341a2902c8d59dec248144fecc015ffa5e76318d89d1e644044ca1231c8e84188eb4e2523150e9ec08a00a3090f1517438939240856b6873f3f7d35cb3f45bf53a167a8b30ae7928753696ee9157fd7836f1a4d2b31966717a82bb30b141614159415dba823259490126a085564fb512f6f6933d82286962351c490c53584bd281806b428d173830e8e2c8185eba25e602f0aacc28822234090c24a13d74509612f2ac8f65fd40c0525ca1851b8d83e2d0d7931b4c5e8746484649464fb4f40de7200106632a8e2055f1cc132b3f73404d00db00002133fd052e4baa720ec3de9c8c1480ea018c387480a5cf7e4027b4f45b6ff9e8c4e5666915392596489ed93cc76ece463fbf433814c4b13e8f49a402798ed370d790b034b32213cf0f0021b7a9ed86b2a321d518124890637d8c118ae6b7262af29c9f65f939269cb1553942b262903b0d724846472826482a26492299980944c4148dea26162840e96d05086901bb4c05e2f49470d3c80e227072d6334e1ba9e9205c25e6fe947281646d26c0a1610b9aee9f583bd2698146dfaf9c1bbf203161b7bbda319959915db1faf2764bd21e915dd40a282091851802146135790bd1ecc7bc1073bd020081e1f6465b8aed7c45e2fc8f65f6f66db83228f8c218f70f1628bedbfdd92947558b408122a72626f6704e3ba9d1530ba2460744b3a9feec776275be29696ba5707f356ecc11640b0e28a1d845062818b05f6724539969cf8810ec8486a22735dce6805f6724709d8e1a70788285a4c29c37539241fece59238256ecb152eca154e0ae78483f2f6724248dc1224ce878d0ba322208e7eac0c89c275b71eecdd543288a28913a4259eb8e2badc8b077b39580ef67231ce75b91fa2ed0ad18665a3b259b1bdf1df4d8aed4d09dbbfbdbcb553c3102ed062c30d7e800312137b37181099c111413082079e185c778b59207b37590ca6c8020528e8628726565c7703dac1de2d689bb53ad7dda030198309172fb688606f5d0aaa4741484140deda71b204831b40e145164764a8c0de0a43810f5690455010163dae5b75b0b70a2d01450d4f7ac0c209315cb70e2de9bfd5a85a712135890ba94baa8fed1fdb55e6b025dbb0fa7218ace8a8ff6a4ada962c5a942c9a14cd496b42491a5052d29206b35accb6f64344af10512c940ab5629b1e7511151ab2dd30b3295c8270a08288904b89bd74053374e9c2031d62a0822eae4b73b097ca70c4a0062330f0c1c91049b82e4d622fbd54b3973693d94ce6f4c276dfb914348f82909244a0a5c850161d6850c30c2e99bd33e8861b6421a30761041d99c275e72c05f6ce2a488c906038f4c0881bb8ee1cc2c1de59348d5eec9d565a485cb510cbd3c736cc6db861f3058b5978d0a2858a229cd0c007d70df6caa2230468e1c2ca961ad40053e2ba12e9a7af54e2ec955ba29051a2b07ca5d091747224a12449599204e24e5a6212f3a2082117844184eb3a127b7d49c29ab057c666d82b7f98f8152696fbc8761095202b422ed44231084664898152161624e1c485027b1d4601289e687142e8074478e0ba1e3b62afcba8e0011238d032c48b2958e0ba0e6463f67ad0148762bb488fc145da8b2db66dcc6d9282848a8cacbca0ed6d2c1ae824bd42fbd89f7eb5e036ac05c415ffe5287601526cef3059c10c4882e842890f535c36d8cb4547493ff096cb4ea05c16b21c143510596def6619652fc76ab097613b4fbcc0c10f428431c4165db82ebf68b0372e49208b94241b7818a38c305c372ab1cfce1143422cb842e449112870f504baf515bd08443811c61625b2173861e4051e64e15203911bac1cb966d819e224083e3e57b63801c5d5456c3f4eff8d2e06df9d4a7368e9bbd328079fc7e5f3c6f8bc2dbe2b6796dfd382df53fa52f05da7e2bb0eb3fcb58b6f93e1bb5d64f9abec9b5309fbd9f9aa55b770666acc05d8eb59bfa92543d8042cd9c2ba0e3b588f9115ad94f3ca771b66f99dc9e7401ff7828f13e29b924bb073fe3791b0f36fe09853e94e25cb5a585f6a96d266e4bbdb0cdfb6c367c5d755f1350ac9521286bc78ce39e79c33acf1a6a50c810471f5d079414c2021e820cbcb052448cd0cae27352874cd0e7eed6539ac69027b977f130f83f8cbe5fdd106612ca62c61e4f2f009fde1e970066f8d1d908b0392f0353bfab7dfc2bb63757873b15d01d5650b02cf76787f581d8f5a3941b61bc8802a2f187275ec351037c2451425179020d18a2e416688813453317794f1a6873069c1cbd520db9d2ac5c7b684307480b96a5270200de0197279b8e3ea2277c7ad46ca663b10727515d80c40b18a0dac0cb96c8002042457d79e83ad2a9c0cb980e0508248c81544220172c556409ca7bbd33a428725d796e5e39fc14ce8e58a5f9901ed2d221c802c8fe0c26039fce156c610838dbf09f1cd2eca15ee870d08f22707140e364832a488e1e28629303f3ff48621db4f171b5f461876f7ee6ee98ad1dd9dc9603066ac59fcb1bb590be699af1f11122eb685f40d1a4df3ebfccf08bb841053ec14c2355fd5ade89a32ac51121de1e208964f027551d5fd35b8193eff7a35011ede56c5f7fe35b135c3953b8d6f8caf8496df47a8d84874448a45808d444788d81376c41cfdfe5d83d156dba6c61192b9a009253090628bcb9f840d3499400aa0e40a520025578c29f0ebf57abd5c33040ba0e4ea09f4ca7bf541f1bd8d4b9021b32da168b5f6685bfd6dab5a8f1594baffa04fa8508c7c371ecd78c858c6b213e8f3d3b066c77c3a43e6e1170cbdeaa209b08c67b359429be67d77df0f2404e29966f5427ed50c441755f746ccd6ef50b2afdf45b0dbbb7ff4ef5e474f19e3739e17398e790de0653bf7c4f83853ce26c657b9a05ea99cf4aa7ed04c25532de955fd18638c9b189fb451b39b181f8ddf45f9ae2a49d5456cd1248d00b3750421b6bea8c0b0f569bef881ad345d6ca53488d8fa9b938f3efd4b937424146dea572fbcdf91b754416e53ff8f6cb591c51cdb7fb256f205adea53f0ab41b6aa829ae57d7d9513cff3549c1231b82e5764bde78cacf7623d95974aa552a919335057e62cb65245fef348e49f423e87388e431d4d596cbdfc8c35e680798e79d98e82eceaa4cc39411dd5507d37359969e2bb2a215b554252aa11118b65ee8a659b23cb1c123630cb5700572c0be0c8b23774c5328d598b26c96d82aa883f968f681ca1216499c690e5b731147398ae8d2296e9af0d2a96ffaa8e2cffa511b34c4366990690e59822969f1b8a3938cb1ff38a39602cc7c02c7f61ebc7962a29dad4e78f2a1dc4960d992a0c5b9f067d57251bc0cbd224794b95a43a6a960d99aa8ac8b2a164d2a892bc952ab2216b56eaa86d2889ac54157155297d1d3d53a492d9fa2fa0084b6e535345deb201739ba3668900464c55515345b626c51b3b2314416989cf862ce6f06c7d1b4b9a35e3eb03a028e608c1d6b7518a3960bece207b554936640380191157f555495625640386ad364bb64ed9ec6cc884ba88ca88f9f5a34f1b40b6bec0ccb006e6a39dcf30210af34dd2071f0af4e10277b14dbeff516d93171b5b2bbf50686b0a9bc0def5c1aa8be709ea37530ed378d136f539530e871d4d922987c68b5ea5b2ccf44095c5d6242f9a85fafa2aa566f1d7576d31a5b230cac409350b654464a13824ba23b67ee7798f42b93b777ee266c7dcf9cd6bb621c51cded7e784882c6f889bc51ca6af1f23cb4b22aeeadfc963e7cc864c489dc51cdcfb60db7160f7de507f7d2f89fe6a4366eace2689cc56017461eb5f1b320124c59baa4a655125c59bfaed8536334e3eee7d304d52ccc15fbd50c6b66d636bdd421164f1a6fea549b2f5aa926cfda8aa618f1bb1ea3686a091a156a9556a955aa556a9556a955aa556a9556a951166d669771cd7cc4829259552fac46e52ad8339e2630b8608a67f45b0f16930ebeb95d3c41cd25f226ba657fe1cccac577e8af5ca59272471e53050d800a95aab56359899b74e416ee3bfc57a8524b236247175b255820bc0116ffc3970e5368ec4ccfa77923bae65779ce974e2f68e32af7883ba340c907f6958aefbab3a6d627c5b4a4606999794936a496228d5ead6bd36e336bc799b189f67afcc6be3ba6db60135ebc6cc4e41272727a11828cd7ad97e4e411d83effeab6ada94c34e7cce2de99c9286703a799e46356d6a937a267a42790d6e96594a29a58c10991840191e199fb669666f13e3ab8980669279c51c9c1dc1b6a6e4ab1152f38a39ba86c7c2cce8d3bc628ef952d62bff9392b872984fdd2d3685c415bfff49c9e47121b1f9f23631be8e86103442685ef146e625f3a2318b37de5dec94effc0dfa2bde70b784a473d26ec67b49f2dd1182ac3f8d530b4e41feda7b0f906549a9467332f56b54d3a63629a51d839ceda27c57e6250333cde0a37f4f41f23f2d3e96c1b6dd12df95794d4d9bb27bf128189be8420b069b7839d8c69b7580b8421259317a2c222222d181edeaa27c324262eb14146d5e3240d6659258973962658a58ff5310b1957ab98d7f2dc27e53ac87a726bcb02685eebdf742202b0e64178e065330b7f1ef529eab599a603cf9303274a99494deeda7d4cbb539f9b8e7fe9e82bc1533abfce91059dbeb246bd669890c71e52fbb3484b0fe34663147bfcc260387efade8f2c7c1619965c0cc51f9e4cfb832633a27e56a6262644298c9614362fd6f8d2d1657fe97c6c6ef244bd9b2393237f76242175a84e069359a26f39281356b04eb2f339391d2ad8a24e32333ba3241acc8041220192024244bd82b03452666afcc12ebafcd104c3bdace78345e4723086ac29c9399d9fb447d4e50dac69f034f4efa04041bc0bfc1d30ff1c65f9a22c0590eb5241ff7edd25e72ddb6d8fc61de6cb326fbb829a5b4a3f1645e5d0d6f13e3e3a058622fcc4ce63504c50fbb22511298f53ffd34ebc5fac3d0c007eb0f038568079fd34906336bd6fd846232b1665d9899f5bfa7201828cdaa695624528116d65f86a75bedf2bf323f0ab017468a75ef680e2759ccd1bd341cbe4894c4c8de2d967acd388a39b43f05c51cf5fdb7986928e6e88f990961ef16f3c1bec9acfff6da78628e939378d31359a72771e52feb7f0a3a059d82a4c8bce6cbbc2615a9978cc2fa574fa3e9e94c393c6082e2017c46ccccba0fb6f74f41f1c6bfc31947ddc4774f41d6bf2656f3d3ac19b3fd917b31418bcccbfacbbc9a5586d5c217251f8c8d4ca02068c67afe44ee34c24c6b1f4332cb776312ac799c00a2c8c5cf42ccd1ac85fc9aeda39d9c8434c50793b246f1b1e0ad6620be6861ecd29e67c803be7e74c1d85513c39a0c38161d20e9e2d23e03f46b78822729d0e8ca800604480f3f5ebc5c5a5813431ef5b5fbe5e8f48a1906724c067d2d842cf4aaab7c38de626658b46119b27fd7a060c3c697618ff86fa3add9f1d6ffad873980c1910b965012c51546ae1b43153ae4216d0a348b2357ecc0c61823c7385d1323d6ceab95e8b2a3a366f07d7ce93424dfaf8cb417510a66405b74434429588a9d0d3674de834e6ef1c58f9abbbb3b4b2c52f4a355e05ce1eeeeeedc48eeeeceefce8de41c45f595bbbb3bcf5ce1eeeefceedc5578d3c3c2d99bdddd9ddf9ddbaf80626646f27044e98aea587e509e3fe8156ea87243564a902560a13320a31fe8ec8a58135cf0c4aed0514d1d15961f9377fcbcb2922b2c4b32864411cbafb4a1c0872794ae707eb915b2b1e098e1831338b0a49af543c62169c58f9e6dc59ccd194e0c0e169f7510509058fea8048a48123850221ed52e8a18c8e2d115cca321718931cf15a999c222b768db35f0de8a2e36cbdbe4dfb9f71d701f5899e21c9ef1a8a4ad3595490dd5108d040001c314000020140c8985429150349c077ae40714800c85964a78509a89c328075218328618430021040000000c0181a91251100050a0572069dc4f7d9725b0ef3f1d601dd6d4a87e8156b0a12c7a39082d264fa946e46caabf0cb6cb59dd061d0da17daf0f3f77715a2cd25a090804b7fcc8c7d8753bb8fab661b1f76baf35612f2b53456bd3ccb5d883eeaba397656a05fa0913781d6e0ab3dcf088a43305619ec38d2c0623044f7cc3aca43affd0358fa3531654954d916f53561d6cded927abf70e9c97fe9da186deb79273835073150f354662cd5c007ce962751ba4d3e3ffd92be86a6c8c17d2f3a963c2fbd30497cdc65bb19e8286468306d07c5dd23d74b6c00f85265a05dd94091f7474adcb4c0215a7f74f9b3841b660b3e39d22975f56ea698a2d5a4b5d85bb4a3e6dca60a50e65ceef02b94299d4c8252fe2eeac47729c622cbd313ecb9ebcf0ff84b1a2d171eea4113001a7d713e72600d41177c6af30ea9479cf835214f4ad3110d3d691bb4a457c569887a48368465e5aa281439a4c79c4c8840de9b7e7c0d86ef9cdf513eda9bb5098602ba661d5b82a4d7702071a7bc564e3fa5bafd966f6df45f0f797bd4256b86b66450c776591a07578617e5fe50390d6d90a09207e66817c9b231767a2d4836635f434d7a80f00b5d1261494fa4949ca1c02da9eb305a015c26e0ef9a0f06cc2daa4b95bcb85f842c92e5ba844dca5ce77d0be8f6804fa44313ffe5ae5d403a9ca5e8a374319cb2dec4dfb4c7ac3e0a2c7d2d39d178e17b6e95379dce00a7091a5be2d17ae3d298318e7825d4b3ec9f7c7b6d6c3dafa70a4220108e50591837a537041c77671dd6d5298974a969c378e9864099fa9c2ddbb3138e979e88a0c51922f3eeb13460362514427a4ab64e7aa53b21c39e99d3fa820c842c1ca1e26afacedf42a245b9a42d9ba13816dd4e819dd27db587835c56980068f8e87a24cc4cf0a2ce15f5818b5d9e41e3978af576e4ded11778c176ddb7272868c40f8485c06988f68a78775bc9201f8a8df442837c59af1f8bd8651c810225d612a9f36bbd31dbf41e475ccce050511197742d1ab5dc33e3a46f375922617e81370b691b3491ae689e3ea553ac734f95494b1b3c1c9d4f3c38b59031bbd5f1ef20ee727c335ec6a508479cbf111973e2bc4210e8113bc0c9f36528395cc77ad4d4b6ee0097534b097b573b1393fa15d79cc85899e9d94d7f893cf1f76c4051d19ffbf6f57e2fb1f5352cd2bc92919ba321ab5d60704557d361da636af1f7a32a8283491e79b171cf952e82a8a7c40957040eb8d6fb9aa329cb823645007ac6ecf86c8f4cf3b2f640e0d6761dd96782294ab7d680a79a1955f4cfed2b131cc43dbe8111be17be77dcadc61e82339838f16133d4ed7a92441e91ec06015072073c997bedea65e2ed232f0191969f0d70515c6456c3b70b7411f388b88b740608dc780101342c2a4e732a910e55ce7abeb279c02d728e917562175b35a29330eeb268b9c55d2ca1c0db7e6f20a709893d5129baba82c9975c5934f6b26044d9ab51baaeb1bc118e68e343e71c7e3cae3d5391a3d64f5a64ab5cf54e7e08bf3f51974800c9c8c49e27bfd26519f10be2122ddd11cd5393f650f19e3e0c2879b55160397c94f924a4956a423f6720b0d23fa30a8e59d281df4447413d6900858007e8cce43ddb986fd81872496767ee14e191d6bd1c5e125fbcbe3f553af498da25e2e9465f13f2b425b0ca4b02f28e5a8a3f6cb14ffc225afc11deb3757e808c596ebe075e7141b6685dd79cd59249de65275cb56db19c67bbf899fb3f67ba39c636e9fa795cca550723e4d1ecc7dbff6c81fa36d031f0e6a8074a458b71f6b2f8302a719a05aaa6765c442ca914ae8741b378b2b51f6b305862853d61f9df32d1f8b141ca1cf26568ebc828966908728e4e3abf390e2474d00124f711410b1674d3fc5d11ed3c18edf3b7d6a3e561eb1f42cd589c0f9a01494eded89a25edeee59d29380f9264a4785a8bb6763f1740766d7d12e34730621a61f25fbb3c1e4d14c53a75d7fe24efb9f95ca7bd8d957e3229fba005ef13440bb5b9fbdacea4201eda6ce1ee3998e11257c0439c505738ed5f9debca6317eb71032db51c93d3c122547bdf2421e699248a98e0c6b37942c45b914ea5f51a51001c8aab3ba0ac25f075894d82aa754b82b4354a776bc703c79f43771946934471a7b99d0408d973bceadaa9366e666c6050fed70b2fdb5b13837df524a0384419ee2c96a4f70874de42c1a7b0d6b7bc2b70ccbd52df2283d2adfafc8628185515d1a46b4ddb9e56ea8f1cc68533a020533bd4f02383a6140c0b6510bef4d032fd3b84cf32f3b9983471549fa4a07488a60b086b688008e1d75f1bc00cedada21434ed1d098c5f30af42f2d84dc0511303c40a55d99080dcf5921b3142af59545ebcffc3e53e1076e0649b64fdd08439b8e714cc29efbc079cb5fd7203f7b3c74983a5ff305cb0916114b06075ebf3c5ce2830f99e5b1694887d2d0d20798e51d677a36764c1ec80050676426726fc352dca6da73dc4677ffb9fa1afab089e6c7795299dca53b43c23baa91b979979530646538ef14589824137a90f6cea9fdb5a1c9a7022a01cb06d816cca66735fd2413b850c5565570ad191ba1a570ff4fd45bf47884d046d4bec68c63107b723da959cf0bfdfbd642641f3465801e9cd55157c4a693c483917c7ca4e9923c47e1b142745bcafb691f04cc19a90635894103f38cde9dc1c76108c608b607dfeb37ac8b18e1439bd8c41268598489e7a641159d4d958b2829b0b3baaaecfdd12f3218cbf8109eb2c39519d9a1c021c472494821e5e507d6e3352c1e63ca6bd5edffa1e6316657443740fb233a15af120cd94ceb9b6f11fa7f0ba18f77ace3325bed57c6844af2d7fc923ff2131938723e6039ec1787f70e87b5a04de4eeeb2a9e8982546f744d3bcb2adb8a839dc737dfb4d17bfc1a217cad9e9be9187504a92cc0051dce02e3c9e7d3dfedd6e96f5543f41ec6e6c46ca434734c580bf7bd967bd5d37c29a3da6162db02378acd8d76b9919a8defda78656e840bf43616ed5c6d3ee788ce6075a7ebb0ac05da46dd651cb8b4ccbb89e3c5dbde2acc9b083d9c178c50376928184478302c0cf61b11f3ce0723b88b4df0b6e7727d04cd1725a06caf2b12abb4e0119956f5dc15e4a1cc4528aa022cc88c4d572adc9a211f4ced5f824c12d0d28e2afeda42aedda96308c6df04a6c221c767508c665edefb75b6b4bf25610150f5c95132537e3d9bb840f509ec64877e6217f0f3c509ea9a490209efa73cf9ac2525fe8778702deff66e0cceac3e84a07c06c9ac03734956ef64ae9157b50157aafa05d05e3314c8137f651736649dc9ba5e7aaa5088e84f101686b5f1e29371070d8557dd0b71ce9d5e6bbe19f609e279692f87423298280e2aee3eed6ac0dcde3b1644da1bb329e230083a71e0ddcc0226002f5f3b87896b0ae674e218067453f7e1d4410e2389c265d0f6a2b1909cf73ee8f87ed752133a8701dd866cbbedab6d1292abcb0d0895272c3b365f2c8191e6fd782cd9e8c1dcad5c10e6820874c86bfd7cbdd937151d0596a78c4bb5fae0d85cfc173567e3d96d36c7256d94a1441a7266d040217b1526326f07b2b9223b70b9f924507486d0e57dc12893b39f08452f0ed1cce4cee5bccda48484de03ac109e3020c490e1d5afa2803231237f39486f452549e6abc4fc8cc5239e7cd0afb0fe40132e1898f753ae37232b78cc64b23a339d3677d8eafd00fdd6657f16e7906645c0e3c553b8dbf350a38ea5b71798d6cb92126a6f35039dd56f298b52598ea7d112393208bda962093a35c7d38486d72d244de91ea0a08e036a3a97df1639237fff0ac4586c0cf3007c6136ba255848d65a6ec17ffa2c46e390d8b198a660ca822522c8c44e53e7d17fc9862795473ee8e62ee728f491d4c6b9e13d8f057d69ba55a383d7566b0dd322cfee67dd180d0492189f361b462f0ad76fb3708ea3a5080b5633e84aded623aaa99a60c802a4e14542a65ce95e4e2f2f19835a6cbae25d9137e3e98b3829b3a976c15e3fc5cdf81a56113537449b28f211095e434e7c64055bea8f89a3d4dbf08f20ef92274720b3202e179adee540d439b16fea17ec817bd0ee7513da1746e996d99f9b1187a4191b71cb1130a7ac4246081c893dc64d391062747de20a5fb9e5fcfb9d08f1ab602b99c3ad301bac69fccd3f88f2a0bf081719974a91f58e17fa1ded400dabcad8ab65df227e0922511b3a2436d0bff3c9fe965bfa5584caf359a595dfdc07a2ff9c19c435b4be288d95b975ae089b29fca30e01cec0c256b958fe23f7c5390234fd7e168d80c793306e9081e604173800d6683f58111fcaf5e495e5980395eecf32f45ab2297225d39f3d404322a1138a4a8bd91402c8c015937f582d431a43ab535d683ff15d0363de94ecf8f48a234e03a06bc936110fbfef6b0830071a2c19dafeb355509bcd12640de70e1ffe0b6595ac12125c722dd12d4d9ed07df0f7d6c8999cde8a6dd7c382778ef9445fa3d33a0c9a1b8944463d2c2c16d850f027b6408de8516c90f7f251ade105964fe4aac2fd32e70085249f40b70196d2647e789a44fe002420b6f5b4efde6678e697a5725d2a09b40821f6aba657bef24c82570cea21f9a615dd6196831058441a6d39940f2f8cb4570a33e86ac24016bb20b858bf25ff904159259a216297154e5ecc727671638d96d5c798c9278d1854d67dcb81c4b84343958d8caa74bb869a925280c73941bbe042e8ee778156e50694d8d973fd448a5d64d3bac98fb5d67adcf890172e8212cc7ba9e0b51ace11ef9734f5137483f8fd29f2576938b86e24827ac33ed6fa61c75fe5037689651106151b34496117c11aa6f78701d8292ee056ba555b4b57089e6993db4a0abd8abf6bc54f6424a5df789a3d563ae071f307f617248d6970f10281499c7ff1afc81a9a5692f1c2084fc39954dd70c326f333cff4892eea490f343b5022a9f5a6b5a06d6103c0bff359a1e10738957a58a0383e2499ab9e8c9be8be36fe2981d5b44f7a365896ba6085c5ca78f28b5748b594a623c93d69c226ec5c29a18ba759add1bfba327c2a8517ae7059fd0fbd29d5b6d232a2ea0d0b1753cf4f41882a9c9883785cb2e64c2b10ff027524403d7294659a69b49b680cb33e408016b8d15bbc0a50076056a5b252385c31aa0b7e9aa6a78615574d939558c341e46fe39b53263eb8b84fc850918f0ddceb60a1a93595eb61be3084508b77283e2b004a2abfa0c930749b9c21dc49ad62b75d3826cd93ebf7fb45a5895850addcd9a9176292ea2901c25153ef81c7b719b44a7f5fad71ca12f819daa0c8f05e3cd1a5e8400c82603fa75917ddd0e3b6f711ed130c66e718c37ea0a207372c2aadd4ccb5c3aa937293b26ba4bebdb8b15eadeecf90fec5025914224ef7f95e5bc59cb5c15a168cd482cf8a6591e4e91152e99675eb9236cb0e52ddfcae09aabf31a92472a5371c137f4dd867fdacbcfaf2e75d38ac3da5bc72e119529f828b11737c4ae4c6ee13af8d8aa673494cb2f9a7fc411450d247e64081bdc9e495253ac281398264e416e3ddfda0795ee8fc2089e216083a4e876ae162f952414f5e2cc912e64272c33aa31413f8187d322a0b1fb991927ed5f4fc96decf10f813eb68d49a70d86d0ab11914caad2f99fcc3880f6c813e658dce0a5176f9758402022b019d64e590b5458fbb870289f094382a6b349e6ec174bf59ee70ddef4c54a0e4e55775872fe2d65affad68c05d1f55c7a4b1e58101e686eaafab181f2f289648d09e4803e222cc6a58c9d8dc4db40a68e0c0c87001a91985884ba23791a326916e1a1517e49ad714f6581c9fbd22fa0e94914172fc4b264092be11f77b127e21cdeee7f7ad88c9c9b128ceee58f08c8551315e2f3d12953f4ae64d29922fd48284ad4e204c9bb5896024ea662d3e66e9c4cccc092004fa3135fff36737064981ee96f96c8390844152889f873ee74240d7e1702a669114c801ac8946aa301689156d3d1e42d269fd9eeac6fc80fc66957ce3bc91473b3048b6ad069def7dc77b937f6ef4856f662349d4c7132e0fdf8490eddd56498e8809bcc2b6046536dd2cc110fe3d2ae526fda0fd2523703558fb181d9eea848196fb80dff40eefb2de93d74bc58c80583b2aaa38ecbc6e08b7c75f9d383b94b1a6a738c5a87572ebf490c3d534a1def59e2803cfd548cdc92aa8353857b41bc31650233d7ac22d1c44348ec789c301035f13694a976d09827198b4a34d21a471e30696718f71cb23dc9f07b68273f3467856ea6aa1d5a669080b613a6720f63bd468a08fcca093c1735f76ddd4840f211ff636e1bf6b64a925098e89dda8a4ef8320f338d48c80d88fe607e3755d3537ba92173079c70cba371c0beba7eb1cb0aed17f08ca31f8f2b2114d8228cc1fdcf606c623fc25a1a76675317a220530e3bbe2ae4c7784a58de502a75f834592772096fceac580a1941c836f2a342d13d5100ca9b8cade53e504c7d0879ce36afd845bc5296586abb857865db98404ce31a6c0fe1c08775d004e0528422d269002382d003408d5b49390e8a308c28ab7c0942cdeb651a0fb1e60e0a1684565db104a8b860676d34cda0394fdb19ef42f904c1168b05c43ee3e170eaf7fe3f56d1aa5af9957eba2a5b0aaa777a45ac82674bf08f08ee5a991e480a297180a9450fc197d64feee7d86d45d751eb920b80dd2016b38d5083dfcb361ba921ea24f44ebe908cb6dfcd0adcb0f090930cd5db5ee319778a04c7ad5942839a0205e00e5782e967a103b9015e4010ac7cdd9b43f0e2738eeb2eaeb069588108fe260e5b8c5f6ec65cd4219cd012a962d53bbfb84926986b83ffcfff49efc5c602f92319334f73d586934dcd660190be5e6e45f08707f03e25c48007f6af275f2a29955cb5dbb29cea9d2cb3ec74afbd8f862ae887e4076560ed0018cdb5d210103f25a591dc034bc591d9deebae674d663989bbd14d3dcd147c967f70a4a6dc17b107edaf6ae04bd64f221651bd5122f6a98864fcffbd89d694441032b58eae7cdc43577e620c95469545a7db127892b6747f21449cbf8fa00a38bc3fb93583940f427dc2c6d06e22a409a178c1ddcc8cbba5212b07d14edfaaac9a7f71ec4b0f12836d822b2c1b49b724a88c456d7b54dad36873922a597243a4e7c6f2f417e0421c513cc4ca83044a86b76b8273440e69c10aef7a14be0b4bfb4082b431f9bd84413b27730c2c70cd3fd35e92509a8e03c8bc088a3ec4e2f8c5bb4f4a72bee97dd031bad2fb9142d526649522b5c5d97946dab3c01309a61eb7a5ffb9c5f780f78ae5d6c2e2f88e06c626ca616501e5b55884829224003f11022ccf2d8d359fdf7cb2aa7d10b823d56a0f0e5bbb4366dc3325139bd6dd935716489232e4ebf7ce8eb57d55d0a2fa5911ef9d1126fee3cd4e4afcaf5082dc0eeaa98e7d42223d3e5eb1f77fee55066ec51b573bac3b846c1be58dcede8afc88a0970ad507befed3dd10e3954d394e565730dc6768759b4fb9f05850bc1e19d3ec68267dbfe10f210d6870ab04e91ccf74b5a91be62ab98be8eaeb12c5a8aa0dd10b3bd4f570e8d04a7ad5cfcc5bb71513736b2baf1225b66eab355bb0040473f4a6cd3be3d877f0d1f83f654262be687c21ebceb8887535f0c1b9ba064d36357ea26d2b2d10b203e0ad5b9fd0cd86dc915d5b55c5734e1009549fac94267ca4647f983b95577bca85f919f843c8cd146b0cc1c01d6688e7f3bbe42a6efc5bcb511f730fb2b4d1c0b4d37aeea09284f99d9ea6686dbbce5ecbbfa3234d0b6e16d331ae8f3bdf69d54c7731e8ca36c0d6815a22e7d30d26d43e9d5f3a9ceddc64b04b550615b5c33c85981af1c25d37b96939a093b6a6ac4972a911afd9be59ea41a5a032efb8bf92ba6344ae2de470bdc25423fe8809f46a406cf52842d65aaed34463d422bf310ad4b82e348ae0a09a1eb16d46f6eb591262fd9887923bccd9bd383bd65ddd8221213425f206ee40f09f59500124675d586418fbc464dce2b133d20614b906522a195656288061fa9bc5729e56ce2bfc128930ad6f2625f7963790b3dc434bf0dea7682edd725544f541b472a9663253fc42195b33882c2edb2eb9b4126ee9f7a89d22db067a11c632df0636de12497e8262751ae72bb1bc406917966d964a889d1f82f665825281a077458ec4f94c66adb67440a9ec6557fad10cdd18bdda12973cd7474b5564260a301ab1fce48c8f01ffc99b9b9e80addef01c4e57f4f980e0a024dfb681a8e1dacd1103171e08ac0ed340977cc5a564482e7d5a66354c0be2130fda9429bdeff566f0fd53865f48e6e8f9a5642c2d72a035f0d9f2d321c0a3e19b2e54eacaaedc719e2fea78ab3873ef560145d0706fcd40264153937f8a99e67507d741a047b1728b043261289e379a8969650cea768ec0e614728824c312e2c116ecebc9906e0ef18cc8e5ab7613a71837a6c1ef830f78c51d2ba3aa0a4cefbbbba93c2fe755d1b449d93c6c6cc8ba8eed44b4f6341841fdb431d32c819dd26220f3105432c29cd48eb7135d355d52cfbc34d172ffc1c6e5833a4531f76f54038868ea4ccc3b5ff01b42120e3007bc1ad1b6ff3500ed88471a9499c174716a2c71ab22158104ccf025460f5797d854524acfc125e455611fa54f9cbae95b7e41bd374831a4bcd78aa0ccf83c1205970d3b4e73345fddbc1a725cc2c73066644e786f7e180b6737f413a3a4183251145496779caa6b243757612b96fe2e84bc6994f2ef458ea59ff91f73a6b5f4c642a0aff8599a303b77a3cb1e502d6576274174b1a0ae66c658b9f508b5246e4134787d3ee7cc24c2be887825cb9db9e9f932b0f5f6f60777a2567d445e788f8fa106d91cd036a125c04e24941e4fdff1ea5b02c9cdeb4e3fedf6b6781f45f8caffadbe4c52e1e09fb565970e5991d1f7e040d4c2ee430a4a780bd5217a8cf2b5dc8671a64f7a6a657e452b381714f951eca32530abd0d8337a3d1cd740e88e3e09730522625b6d175448246deba0106542d59e0f0bdef7b9596a7057906f72ac5e3366a81f725af7cbda4746837ae2cd87af06cba177ea3a6094012cf37f15730c4649f4170a8cbe074edef876e24c50d531a659c35e0dd80c66adc0f73de7803ffffdccb7b1ee8f3208c36c975b89b35ba4dd736942a183a05646cc18443b5dadd16d833163870b65214a9149ab02305f7c7f0400028942598da05a4110cbd8a2d2a1790f3b21ebe626e2562786972704fd45f4f5987c53e170c8df9932e1778eb21c1cce53ef3ccf98ead104ea5460cb803ce45d7e3e3accfaa038bdfd6d31b67b58453457cb55a5c76f7a43dc1a112d38448997f6864481bb466718252cbc3bc7071c44a95d6e5930b22daccd40d2216cfa0cac28a53ad74bafc751b1c3b794b119bafb6b13e2c020632e16581a5911d7002432f539c4c949e2cd530004c020a6e449e05fd5938e3632ff2912b9d5d916f728bf1e1c40812609b6be36932e955d5a4349c22583ec6fa8769efde7afbac31e4bda1ab2f31fc0f160736901c0b5cc0bcf9303653410d140b0f5e094861cc41bdd31051bbfddc58d91be3f64e77a00d5b2acb3900a2ee3b6f16c5f7c2aee0741366893fe4a067b9ef68ab5a22feb9b58f9746aae74331d5efcf8c8927ce6e83c8fb4192aeea1ac220241f598b619ff72f4656a5193f9524949f31562ff22ed51944a8b0fdd42b8e70be9c386e9099d740bd62ed07fbe7844a33011e3b9e8b2159238122cb2661cc247e7c5a945aaa17f6e545f24ffd79746dadf7602615e72cd2e629ea6908ae26cbf043668f0aed9dbc496bcbdf3450c4fb5478211b7c0121aaf69c30bc61628582d82aa64fc45900fcf1224ae1e96b18131246371cc4ba19ffc707eda73575dcc937afda36aab89239eb13d386780eac45c6f5cd0122176b8d4dce84c8f61cf3762b5c323acf1e5634640abd099b1537b19d748be2067b088e680762d49096db1dcf9bad3584712193d7fe0f03f29abd4c8f7f3760725526e6b11eaab8b66b5f1febcdfad34f5a5901ab18acecc96392074cd56a56d002f04b8a376a42c9f125de92020beb0a687cc8cb508cfe1c18b39bcfbd9ef2d08e1ab60162e743661f34c9dc5cbae64eaf153ea6731992c59ee5a0cdd084276fb533f3bcd4491f5f8822e2f1b06f0ea6d3c0001e4ec13292900831701eba23fabd09fcd0869af4b380c1bf0f4bb2aaa0efcb1319c98c9a7e62b22c083721340d1a51136a4eaa1ab2b3effdc30cc74532815f540e4f5ebf1da1d5a2761474c9c0cb8646b98eca575b1611ea29340d5eca9208df3d2df869fee2089a7233da3291d91fb9d88b81154e1535c2a5973dee9c380d5766cc025a32582b655abb4a500132cd4de01b3b9fbff821ba07b0f5a8d8baeff3a15216223f247e22a489ec44f0882fe2caa4e3a432d78310c9e4859b92de2f2641edaafdbc28731bb3df5dfe3e34f869f417833236b29a734730d8184fd9760147b87f9cedc4ef05fa420df376460249fda113823ba11f974c7c152a03b9cb82fdd3e6d6e49d39ccb8e06ce471391c8b25ba24a68f0a7de5723ac293a197f4b50217db39c63730b48495a6f3a725d498ca0c4b6062e9eb3a84d04f06e69fa87301e933414e5a242c68fa88c055fc6daae6310d2e08ba26f646a8e120262606ac707f6204de0a888a8309c26be1844725c6e60996e28e4e8262a06960fe6221277b0574a5d687726f41cb00a30ad84e70a072c797aa6beebbc2dd09aa6cda4eccb102052ba37bc8df76babae21928d2b8e72bd10cb6c36a5da297d522b57c50ccd20722ff91b2f274c22538e4836bbf38764809a0cb0da05617b59d0095f5012d773e65b283bcd5a44e451b6e132fa014134d509b0e65d3ec13a2deea1d622e9f97ed494f29f7819642871417044285d84df877bb0a4877065593910e145a4474f69ca936d727ca38bee15b5e1cc73850671b12f1d846080bd231850dfdaf17a2650ff3e0627098e3f2e134e35203bc38e8d807a74dab008aad758c07c70b162a930815899ea51443a61b7b6c8a90c589a88a82799a014c30ae5b8be837d057466844076895be57dd71b021fc01aaf4d132bf2c4ac12f3415d73e68907cd1fc1fde0c5d09cdf775702d90ea871de94436d1fffe8121d056522776a2ed6d0822f06beec28f16e3dda60c6b2ba5dc76f2b7d56f834146b8b81044defec6c1b7daa684ecbab72583df0e4428edef708975ddd0b2d6b79d80a48c3498348aa63f4d57360d13f6e4b18a540a86f9952cae5f9a356a0647f658517a531095f698809b8028fb2b9229d3da16b380549d2f1e4b955c051192d8ea91bbd7a1f9f468e71a68e8d37e8fc56b3e093c75df0d6233559695a23188a3e1aa591ed5bda1869ff34f36ff9f480c2a182b9332da5b8ffd11a09cf015613556a95d8fb7fac7587dd04aae5b76a957c1d2240ab95999922c850921066e39fda024e8eea17767d57d3e0547b327b31ec8e4fc9f61d17821209fa304687e6d7a503e539c338af38647482023c4abab6a5a77e7f3878a1af8303f131faec56e59c168a8035b2d1e2c3ec6c4b1500b656ce493d8396c0410cd40e3b3498913b79d85b0677e25cd6f420d8010d33fd5cf081de12d65b5b135f7f34e5fe30f1cd02f8f0926a49f3998d534b00c6ad40271d7acc66b89d1dbd8db06bd3aa2dcd594d91f34af869bac855be635d110fa600e51b8b127c92db40a9ed128816ea0af36e2015a67ace0ffdaf9481d20099045fa5b0b219c6567b006c414fe132160a53e808c03525454a632a7dfca9808acf0c189d9208d7bc90692a4a47c83752a3400f0a16123f5ddcd80138ab7803de1c6c0769f8325ecd107ca3d00661f8a1d80adbb02540a8ac73302c4419d4988151e174359b6068f12df5dc1bffb11f9176e38eeda0bdd1789c574c06856d0217cbfb83f8fb46c149785f43e92801a7e9695ca2001a6841dd4918c745977025ccf545e26c38c6465292a3d0470784801264d67d6ce0af9880ac4aaf5a5ba2a37d1173f7a22448aa0309b8380a10519c2111f5cfa3140896e398d914d31d6d902894005422872741418bb4618793b4126fdc86915b4e959e6276636f25bfc754ea84c8bcfc16ab91081dd4aee3696a187aeca3e40811bb1aa39e1d9a6886b383c4cacb0b365acbb20521707ca1b7405a48658812e899237c24043adae4f8b8a9c70b7089c279df7d0c629a5cc36d32b123c8815724e3698f3e535e194b7b79c603cf9a5702dd8b80badef76f4b18844deebf95854b9e5d9f2d698e04afe0300bd02c065d1667312f29d4d36c1cbcc4e7150f78291354980add5a02b2fa98f028f275967333d320dc3427d804626170975765ff4ab9069251fcdef7f7317a8dde8a044b38b8a710f04d51f37560120ad2511cb573d1493510fde11816b10b2a34f85900f480b2ea563cdd909511c9853747b74f69d7a46550a5465f776249f832dbb4ae1b2c509d8fa4596aa675bcc46bef2e4d119de9994a69d05265a1fb315d49e4915fdcfd17958064dc4a7cc1d0cbe27043e34127dee9931ec56462c6937ee7178836378f147f1aefd41710f3cbd9c270758e916ca4c78d32eeebed854db0ed81f7c11ad06237513f451a53e41da9186ea6f34f99ea4a013efdf9caa7aad18aa3769a404982f0f5b82692e792f741584f20d2d8545b7b2cd40f3136f6fb3d08634301753103495ade273b88f6c04f9ecfa19e4baa4090cee229ec57b1c6d66147dd39f49e31db3514a95133a4d1fbd758f952061d1fdd6e396403cd62ac45aa40c5dc991321fc004e1819285e8ef466d64f63fa1057a7b97ec64bd19c0a56449aff7256096db42e91f26a9b49478202803a7ea93dd38c3257206e4a39113dd80bc81f0bb2759c1ef0c4030c62c83dcfee6db13a1b710f02efab73fe56e6fe488c0f1d48c90deae253871b0b30b5ba5440250ee83a81184f4dbc36d4da44d68556ed3d923563185e7bb6d22d7b2fb02bdeb6ee79255660d4bcf604ef97295318863db2a012427ba35c07539e7e7880be6410a236c7515530228caed8b8a8eb42799dcd492918ff6c6b21d14b8729c9cb3d4e4c22c038090b7f5a06746f403c336bdb5d564b0a8a095fe5de0adc99db3237ec540debc26d60bd69cfd12cbd217009ddfb8d5e46f1307294cb0a777f6c7145f73e3b6491181148e3f92949474321ab366b6fceb0f869bf9fba6da71181b0b6f0a77a5259da8868c605abf917697029893adc0933c331f59b5d02c10e3b2307766c03cd001afb6ea8d679ae8854ae871c7f0c117ec2fb5c13ba57d7ee4515cd1789da4fa2edef24cdff559574e6e2a05515d37eed793b0b9cc2a30ffba9d68f8d907e536ba560726e97d60de499b00e0677a6fcce38e524d596911faaf05f3b822813acc0f1c31fcd0eb1c7f49d951c4c2cb0e7813d63e54023e05c14ac6cbbd3248d33ef002c17459439e830a0346f831e1df73481e0720a3e4b721155df62ac87cf40b9bf4cc32caa0aa13cd1d2fd931053d6829d834b91a80eb922af49fadab96c3bed74c572bdfe6f4a318266746f0949dbc6e70b581d165558bf007e4b4cb6100d552be105fa6697cb867574623601394f7a6373347b0df8e74afd9896cdb9de25bedad1ba40fc4707d62de0137e89a76895c1b1855d10c8a2c46a83a768f8b2cb7f10f1a0226f56edf810bad04f7df1615b36f1fcdc9ae689a494f1ff4351064da241081520557b4aeb358a3cf8ace418b50d5bfa0bea99d57b4b53420b8261059e985027645eef32b1b2ab8da866dca26adeeb58056cdbcd73bc8c182c5c0c8310c35041ef8aa1d9e209a0f37e2d1ea5fb8e12381b2503914e8c53311a13ca8c310addda00d9abb3dbfca937e04a9cf508c53f6f6154c858359f724470330bf6768658244aa4ff6f154823a2a5a411594cfb7832c3d6d175c6ba5cedd98d785e5b361e635f89d74edda80ebc9164274631ffdc4a5cd0d6717c382677f16ebeb2132426b4ba4b2d5cb3afd791d171f59dcee488f553d7ad9e02a03b391b0e069ffc9394b9290a32d5708684f18b854ca286c543df329fe24a081b1be76d99bcad809f8c320342f86369ca098db82e5ffa58a9e43114f7c68028922e36e309343f833457f8a8e181d56284410bce656291fa17bdefa4158b2493cd3be20bd145cd8848041251aaa14d05f24bbab82ea9b9163aa75eb4b878c86935f6b29769f6e26cfea2cc4526e143075ba6cf5bf08e75b440910dfce978e6b63029f662e1253b8a19ca4c8dbaf3ece09fba849113a6700effb954ab5b26e655219f534f87d0822d7f26a5208e003fb4be11b132b26b3a288f5d8db3ec7c4c3f906d8e616666462136052899193734263d3033831b1af45317bbfa8d1c1bf18901e242b2eda4119927ec4c34f6ff58440faefa4da500b1aad1371c3cea0d7f57325b6568a60cb0dc787e8c3842670925a532115061dd90809cef9290f0a02729bf81fd0f4290c54afd59b8933aeb9448c8c84e2d02859dac61ec5f0c8240bb03ca639048a6780ccb08e8374404bf420672351b5fdc09e8ac3929038732516b14438c55857ab848df2e8909ad114e4879b654cdb99c8631025dfe68f5d20e7302bdee7d1add58c37631552821913cf71c4b7724e8bb127721ce0ba05e2dd8070b8d0bef4150f929a0735cb02a379a550f8246920ac9560d11c4c1106a980ef4f3a1f56bc067d78b2c784dbc919df48551094a91352297d01e8498940091f156f652326887c8e6029f07eb8de55f3a48208bbd1b06659586a910ba70d524ba730ff0dd3ccf19abe98d1979fe81ba03354a1155645d215519ef62644f24bef9603ea61cde5d2b34a7f3e79938485bfcdb0736995d104002de67b274cecbae46e3ad06d1d74b0f18fc608ff1aa08ff4ecb9fd013aa10e3ee898ed305feb56b7fa7cada755cee098f158c6d524c3e958ed511f761f894c8b8276f87afc7aff73ded42f740be346048dcd34c655cdf81109c3de542bcb8045d1b241a8596eb031ea1bf19cfc48db719c48d0adbac1c44718353fc22e5dab003e6076e281eae87b82a77ea4bb2ffbad8e6a7a71108e3e2b3483d189f830ded4bbbccb2d30aa8c47486f2c3284ff4008750cd869aa711aaa0f23f839c404331c7c232edb84a02b7c3d4cb5448a930203ab09b123eb6f0a4a63aa78aeb0ed9218d602014a336409ea629a06b5176fdbeda0540cfaac6d70589a82b39ff7700928044df254c6f301a3c15c97f2554b0b582af08114182d030ec911073cd4eed81bfcc5b11f25e45d9aad9cfe21bfa70182373a0ff8b62589aa5d64759b031becf3e247b66bc89f452a49b2bb2c4d153bd0a355fdba443a4fdabb54a84e71020084036f571352ba0e5d8c04ac33a9094490a6366ebf6659177661486921bf53e310459889549f9b417c296393e9a0d8f2c7cc1d97ca13712aa9137f85cc3d9114c990c121775d20adb3e0a3b0668a21711143133d8a7cfa8dd571e68b21f48c273bfe79a91df843a67148cba24875991e6af4097048d6ffbeb9269f916fc3b59aea984993a18e26c582b42530ce5474fcbda039e2f8c113b9c2ee0aece07436bebb27954df78b9a56c0dce872dc7ffeba927790ff18627da03dc6f9038e34a1321ecbd183fc6cdf67c26b1d9dc9024397e4912e84cdf7b5d6d3ac44ee20242539a56943d918c5c544c92ccca2d555422b45eda29d60bfb12a11cbdcbf2d3f7b6df41e28bb490d2a39f04528c4d3be5f1d69a63fa7050ee9b89a3a8709b40ab90efeafa0a11e8c769333f7dc8848ab78577c3685d4ce4d00de753f8a95e5971ba2a228a30d6a6e0555e7b2dabdf43ec59004f0bb20f8cf41f7f42efb1b31a4b65769da2177a727a61d18185f550a165534bdd83e68e4840ae8defbe239fb83b4fa64c07bc7d023820bedf8cc2556ec07a8923de99465a7f1961516cb205f85899a10aaf225ee2fe0446f73fc06b67e50d8d23a3f5f1e55c93b3cf7ad30722b95bf1e8c94c90304e5dfa0b095c8cfe6b5626c71d22ab1c27022ed48a6e4d5576cba988ecd557f9cb1e531bcfb40874b10523053418704f714aef0a66a9458889f797d1462780fd246e0f45cee0bcba109acc401961afe910514ba4199e8aa8ac33fa8ca1ec16f98766197d568a9bcc9abd0829ed640998b8a94af77c6ab4dc5a4c1b3c605621be29c812ae16e6abb99a75341bd7aa71893fd640e91cbb0cc159eaaa9f86192bbb5773c4c797988c6fd57028e9e842be3757d1ecc16ec1a116b603283456bb9b9fb6f2642076116b525cc0a7cddffa60a62232f2aee9fa899ca9f9087cab677aaf513038c9896f95649229a4ae736ca9642823a9a0b821e6f791858db44e2e4b1683ccd9e426d5833d3606a01f6e6a9376adc30d3d11b1b972fe9740a0d0455a6f0333ea98e6948235ce4046d00a8a863c5c36dc1e9eb845ed0cb164704067f99784a928daa3317b23c60e06f637cebdfb6ed9786416ac88a09ff636d2060da53540897c850d73eb9353b45e33e2055cce5d261634a076e914da9e59f1d78ab42bf5066beea7190d78297cbae8880a554c1c552ccde155e1222fb88d24dab2f8a180400ce58d3b37c1388e4b6c661126a91d1df966e07cb36b147925b17ae3882da923bfe745270f5f00886c282aeddcf097aba28adb8fa6ad586cac982e0d9484f6dd59beefc1969850edddb5a477a8f2841bcf7054c300787cc73b49dc635c6831d13f16236a152f68e6dcc15c31f3d4acbb9360c555793b307c703fb2656ad064ca85711ab90d78af5b70ba121f5cac6781b88ab800b996308d2a865348a139fffa7ccaeadf0022db3e3c7454010428476c22729a8c3e97925375c639fba9024f883a371c27b88dac91809fa3e3dd44c1c2a201ea7c4a655da4de43edc6114acfab9d503ebf2c2ad1880daa8d0f0e54be8b496100839ead693be880b01a6a12ace7afd06d2672430b0134b323d5dcf02c1c3458ba15650ede4cad77c0cd14a7ae364ab369a9b13410f5dce320af5ddaf0893ac57686259456ebf684d4cb78957d30ec007840ae8160fc3ec2a82faf769957d294687497fbe9eec46b6a99605c850b8ad207b82c3c56bd3150f12b8f77822d2b9ed0e1f6b402e7fb2d144e18175f18483839d14f8f8861706e5f05c4dbaa352e712503081e992a3016ea391709615603868a559ade6dd508abd79517559f2c43dca21b4da6a354091823fac33e09cd4fc0b22cb0287dfa728d509e2e05baa25a3fed32a08b070f9371ffd7240777abc0f694f541b080ccad628bfa59f05039b8d523d8c1f3edc6a75ced16e25a819bedb841a14806d62ec00b754d137d8fbd57dff0e7bbda60680024cd0a081b280fa43f44caefadec44f1bd8cabb60a86b9837e2231e43de5a46d80e14d41894fd43669d988432a4a959ea0c33dd8fe00762d31ac8ea5f9449f638dfab341c6a8cc439dc5f003d3a9820035a81b4b63bbe84b8474a9d3e17c4321e9b43491cc383234f0f2dbd6a5ed9febc24b59f0a6e01eca29514c61d13f26e867bf8a370c8b65ba98a70f49a335a3142d99c253b278c8fb3759ebf6a38e2a8dc2cef2a952be94f68630f80910cbf1ba04d2a3c13d04fd34d1ee43ce5e46c34115b58cf4099ab7182e28d89b900a82efacb1c3bcf7a7c36840dc69dd74e0f1aa2f7b010f3ba1a20b0e3fb7102247c08a900627eb10107c3e48fd7a355f8467a05f4808d12384cd8144e155f32a5b3326d6d996dfaa1198463e928eaa0e40ba8237b883bc16c6915fccda993d2d0b9795682b5f3cf7e4557b55fc50f7c09d00b39a54d08b277b8c4a4fa2c45d08200b4a73608d1addc361f49c00ab9c229fbd914e9a0d98bc3af0ed7905d6666bd78dfb345037c0099770624a06ded38bc19542b647c3f79748051a601ce416da49423c38bec9e380320ae16155d8064d456b209044eb7ab43c80faa34297de5da4e693c3cf23371b80b717ac5805551082b97aa056dced93c2f594ffad03df54644437e06b4234a08432b4dfd0be71d6f6310b847b025e80ed35b83b62b0fd758cdfdf81bb841d534ee16a44b2e99968a895d5b9550bfe23c24a180bede87b8a4741a687477b323ecf1c507a28de7223fa14e6eb01009d82a5dc685abe1d65857c7f1ad8cd500e80577b8c340fb8c208e273c07a4662788b6a6ab3dcbbbc5f87723f23630bec0b47e9e5ccf4917fa810a7f5f99c9c20656cacc519d27ffcee57864377db457ead9261c653679c09ce09984e96cdf2ec85385f9c5491c712e848dc529f12fc8228d7dc96957f05c739407d7e7772b8452b97d0f3e5a2befb764b3e32bab10b1ef2ea0b4d5d4222ffc6e69615638cedf1d735981f99e71420ad7df22fcd9de8270f401e61038675ef7247afe3558c0a991892560752e83e149b2f2ebbb0dbed1bc1e0e35febe5b175dffc0cd1d45fb2b430585b53b485104eee0a939e4a92b8d817774d1dd9a4d01e792ef6d68733c6626c0d16b7cbccaa235a65844256bf28ef140399abb18831c526881536352092738da265b36bbaccf21158a2abbcc0c90c99e00283d0063231730053fe6783330e27b10e9492ef4f046a6c186eab92c858ec61707b37b5f6986a5285fd3b9c7daf70b058c125c0afefc6c84e3d334a3292d6aee62e9569c1dbae6de590453303d28894d08eb3f34a890e39e25352fd0f6bedcd8c3c9346b307390fc40c8d5503093782e2aaac3f7add60be6c69e8f0c9d40f459a4b6e73d8c893b091e84d3b1a693e94d63d3a9ace9e3c4a14f4011a223eb4311f738bbb239f1ede163d44cae21492cdc7c2882e3d34c22aa5a122c712d46143a4e7c474fce4e8c3d931295d91c3a9899aa6a3e96a50427eca8e7f342da41f34abc4c04c280f90f6b2c3a4ea182bbc55322a23228a3a184100728324782e1f80d19e23070b01cbe0cc4c61297cad7d46f88670edd9c0062984b1bcc11e1bee3f9b49e718566d8c7a2fa920d5b25c2b9b9eeca9338a30e326081594ba5b81331452966100b551209fc52917068270a4227fc66df5e6ff4f75a0846165b5f2c31a620e24283ef1d4d3b44c002300c26dac51562f4e9800b8a8e863326c5ba4ef9a6c7bf140c6ce1cf6980902206518d0018dc343949d03c9794da0dc98096dbcc040a49117010542bf1048927871de3ea23a2df9b54f7df0a51e39148b856af9e1a74340b9c941429edddd4bcca475492f70f63b824bd3378611e82fb3c88959bb1810724899a07f66654cf4660eeb4edbf180194304540c8aad1a4bf3e82b7aa2b4219d6604f1d27326ab1504ea2af00bf6c05a510f2459e09fbf714590fc8b28bfd652c094088c0aaf65332358de8a8acbe08058fb9bb791dfd70684b0ee5fab6641bec6e5ac376718bc887e089d72be006cd97701e6d6057759279068346f79ec12eb31d913ebbd441cb3d2679741235144790ee6e5cd6cd04784bdb2e7f01f1f0b73354d8d8b07b5734b91851b49f88fc5651e9e4d3672e9c18d07a0f18be2c91bb3d3cf3aa2e06f90bb2dcbd0cf9eefcbc509aaea80206797d072cf3ff76cf65d80c5c666063407ceeed21273db9f69320f483683805f2fd90d7f6289ef6e53ad3cd080f927d7dffaa33aa9273ab540fb4b0147cfbd3a9a60c8ef79dd597348e6c896e70239b8618052281521dee749ecf45337117b3d3ac9941186f8464ae96952262da9fc5685697ea46fe20eb5697c64a733dfb2c233e81c0952ec80aa7502ac6005017d4dc0425a2c502b632e7e7f15f495e417c2ebd81dd10eec3dd03f826a98babe2f6a3405a063e2b0cb61d12b195a60aec98362492811ba0180bdb8686f3bc4335ecf60d409eeae9c03901d54771ecc5f225051aa9e0ab1fdf9f26425ec11c2fb44befc0622747f29b8857430f0f9586aa2b8ef0002f86aaaa96fe3e0182910701c90d6b1081700a2df07fee8ea010472575afb508807fe22aa1feafd43e1673300ff4830457926b6ec552e8416165ea3f4f2925bbd3e43556be05f602d3fe2f155c16513cac37f99fab5ddacc2b5b722506abfb11b8f4f2c0423b65245ce97f5d9ecb2154a4a6dbe8089d7e58086ccf1b81ed4cc29c0eee34d541924fc8fb80f3beb30f34a389de3d3e1ce75755d59f01b189bab497519f2e005e3277a2fd8a4fbfd21c1fc4476d46760b9be285d0b1d20e0a7fd55ada5a19603f4810b56ce3af6a69982567d4766e3893ec2cbce19be921af9afb9edbe724d8c096f620cce3745aea6f554569a3a8f4b8ae6551b25e763a9020774fe8d2e1c8440c51aeaece52e6f6855e6dae4f8ab199d6565ef878c8f17aac09f443b1d7bd4582c4fd4f9e74301418b1ca34414d79fd712fef6f8e4a2ab2279b4a8e6c7c1138b10d12938fef0f1435c86cc2b4f79acf861b676fbbe0057b631fe9d6d5efaa0aa5bc30321b5bd1682f619f506eff7d55e00c6e187a0dfcc53f52d787d51646fb07a45f19e9cd49f3eb6b987e9cf4a9be86607e142f3ea67c9db4597eaaea54872594c340728bbddd853510aa95af19b3d95c8e30c366de4718c373ec930d2c212c892b1650a0e48c0c50bc07298b063a5d4c97a998cf13ca60fb733e374555130680e23e6561b1d127f5cf0ac8979c3562bcd5b2d28693c3c01d61288c89a7bf40369f5032b749734ed35fd83f41585bd8c87d5a925e8a49b66962e460238b234acfb52c9f16d4df3410a432dfe240619a782b44d00ba56459c7053e48528508258f4005133f12b2b64e745f954752dd3dcdfd4c2c84d01892c841393300912952e339c5e8e4a1065e8f4b3e4d69acb2ebd957fd915875df7c463557b7b03a0cffb16531813390dc289d2dbba97542cedff929e08bbb7a6e2855769244c1698f6091c536c7d6c178097baf9671dde41c18c1632c5f654c3bfb6daca1032c84f22e8d4f9f69f000cbe7e0aa164740017748790f0f21e977e389986361379d43b7a7404e0d067ee75e7d1a27c52b1cd94f37df486b8fd8964b3ebf518efd8a3ba23c27f5a312113904de8c85caaa301c271c44e853abdd172076173393a89ca275176dccab40ffbe31e133af2a37d156ad9c695c200af7889d487a8dba091cfe474ab33941f1921d9060462f1aadf4763018b25b83cc75365a3b8399ec35202ec6b1e5d8a8532e19448e6dba316163f7b44d0ed7b87019c9bdd114bcba96e05c0eb02ab8946e8915cd9165ba1f8b95ddbd1c9cced53366a81164dd79a6da7d35c3a8c131c72ff4e3cf292aa4b36a81594fe900859ed1221203e4203ee37f62ba1ce9cc724bf3e38d9dae3d5caaaa1981a909fa3503a57fa58bb7e340f61e1815d4a0999ddf61c7113cd7244a18831df4942aad2949ec3081a4589155e271aa225dcc8fed86bce6da5827faf2921c8ceb65621511bd042515954fb0971f5802ba121bd7eff50af1b159771bcdd01e8caa6987cfa77f61533e015b178bbcaaaa76863868de92c00eb06b8e44a5c9a5eaaf0a078f17cf518f2a5e4eb5d6dc71cbf27d7d6f8f80c77ce62189d3f59801be976f09f8117ddad5a48228613d0c812f8d08def88a03fe3e03f306be0e10a513dda0cefc5368a7f8940d6ae808844d408aacdc2cba4175b3ec2a19dd9d20de35e70d8d0cbab44280c570ceaf9c0c58f988e22b6a4de2e4d56a80532fc36a564ef1277d486edbc7b4f3c0103b686b8e19e827ede7a5f6fa59d5a85059f222861259c410d19340c80291c5277683275c4becebe2eead3aac86aae3588d202399299e1be192f2a97777088b6b9161d77a8a894a8b22bcf390f7273c031f86fec3f65b2feafe4c1ed1a36beb32e53c3d20435fbdb354396c99a8a169903263ce5e034ddbded0ffdddc214d4acd7224609a6c56fa7a8910aeac920906006a8d52a3fefb661f5afb14a553d48f64769e7622793ffa248037d9442020ec14692e6d1b2980f48588b059a80965cccd3b98d13e96c4f9ab8042c73ff2c9dde5803a2decba8a4415676402617a4a899c5f928733cd945e3be7ffcf032a508184b0e83f528fd0228b921c1377f298367acf26c48193c3d08a6528c177e5d5aa435fa91fa7c029496ec0bbb0840387af0422f75a340610791b528af5fe0abba6ad87e52643827279310c51894bd771d94dcbaa66534ff044e5ffac4dfd0f859245e383def16d59ed6fe2eacab562cdf2bca3959482087c15219c632b27e678f6c36611cdec02a52a4118fbb3ec26a0eba813705eba8ae8c88765591e7090a0863857a589846b7fecfac4bd98b53cc47b556fe52308ef63322892435f6f9509248f90ac9a072da95dd2f53100d179266237bfcaad2324e6b02b03c6e61d808650b3d8acb8f73bfafb3d3b20b0036ab2481068483bca0f0578908507e9ebc910e97a50c22cbff3377d378956defe7bc0e564f00388d9704079d7169dab1c5c336b19641cf031d97be7fd90892b846a00c846000fa0dfe8a65a4860519681d0e81fcfe7c3e89f0fe3a49506d076b12df610257580bbd78bb0b9baa5a142822a0b135ae9da204c54e558d1e4de0c102baf77d0a61792120bf51bff1f5e595275c05fbcb34dacd017f34674c8959d14cff04183ba05a479ae02d56cb316307a630f61d890e801e7861a140746d8fa6c483a98db70f0714f19a89721c3024ae9c6f8f10c59c53d74b955c206e157d0d7d3942abadf08bc8dc8ab445398fd3bae5550cab979fbf68f71828d5828417c344ce7be199cefd25197f03154831e305a106e54c6549b36534f6f2cd26c287efb720a86d588e028cdbd4a973883a04db1bffdd34f9fe64013d04be9dca9ee30394ecfe396e782de78f54d809d76466357e8f0ac580f026d838dea83a416a690d99f901879098ac5d207aa1ce6c351129a6efaeb34a3cbd318a4b1b2417c70f8e101494a2c8ff625ff3efa4d032d81eac30a0bed9c45abc6eb068c4e469a899e1d58e79ff5b05808b67d46bfcebe208fa0e2614849e874aa3fe1d191f7b6f2cdbfcf21031e1cea1d5fc9061fcdaf6a5c7ea8fa640ea2b2fc998939157962618c035de1f733a4589853fcc8ab6ec950d9fb3eaa7506ca8f38d3d37b02a371390cfb687aa745d6ec40fd597fdf868bf0dd810d22ffa389fd6ac1a379ec60557139e75e1266440b27c3b3a80e5859134980d38753b3f0279efdd19d26048239cef6d6728392796660f1852859c2ee87f1c91574eb07113cf1786cb7b0f3abc9c5ef3f220539e9acc789afb4f9198e08490c477e92199c890333cd43a0d88383b6d8f39b85d4d5937b0afcee40232f42daae264332143dd762fbfa747c608acbb46328a2651c63ea0ca691a7db13a290441c55f9beee7b034971818feeaa3e493ff6f938579487c67d5aa75fc58785fb8dfaef3f565eaf4b43c4bde4d9196de194299328b1a89fad5d0fd2d76e7bc569a97712f8700358226808c3760010891e6ea7ad89d41596acfbebebc4f94b1b6fc389dd585d809f4ba57a4ef46ad3db434c81c24843a0d377345648a3be486ada1cfbe283dd0f74b7507fb1907e86f46d306912614bdb1890280ff881eb6531e5d65449c383286a2baff768eb0c3d7dbbf23ae66453b86b09219f0b9f185255ddd7d971fd8575152cb1866dabe842058562dd3a9206203acaff7cf87cd680454c05e91d496355cc19183272f1e0ae4e61f93ce09c316c9aa933e958a9832564fa9125e39f58be29bff1f4f6ce2147c348cf03717c20e813c43a20d70f4b3c88d057492ba0dcead8fa1427803046f4dfb24fb50205af1d3e7298ac378223f96b2534943bf7c7def096ff46df5c172cdcc9823a150cbe5f04b468c2dfeb13ca87ec49bc29094a61702e3dc4f467c0f536713f031495798b118110d13015116368d2f1ba57c4bd2824abbebfb54ed08970edea2782a70124075d2199974677399c8686f148fbd9512c992a8110f848acf6fc2242e52cfd4fbc4106227ead6a465ea735b71e5997efa0c1f38f20d5871de70264f3d629ed8f953039253ea2359a9167b8ef33c71e23ec39089b02e984bcdd7b5ba192aa09a91adb611599fd83a64dcabe5195c721a865ffc8d846a1899a26cae4e147553cf88fe0605f930598ac7d7a9dbbdb66e531712e01b3d83c581ebcad76e95cb40b475dfc6e6cfa63085c2af59401056f79cc0bc3a6a739e27e4aeb8dacdc4a999e7aaf989cedef03a8c195ebadbe25069d08594ae265570b1de449b86c2a7119655595af8be1e977d5c4fc3f2e80e5eee9d99f879ea0c1519bf594d922d51133a2f61160407d9c5f718f5c59842ff201b2e74d1f9bbe701ca09ef3c55ab6b7f9ac49644a65232cd36156246aca6dc672650637d90e7a1b7ab7e152387d7aa2954087b8c214ee07c8c89a8bf45f8543cad7d1bf893e9111ce93830ecff05858e906cec06864d0793d2a01595792488e08d9aceba3aa0c4a75f6d3b630788a80a5dc43946a00138aa9469d148b2c59d8a945cc0dd9cb37d27217f852bac3a3f48619c56b3476a86b6b371664f3ee0cfca2d1d353242ca5cbe4c16b6d846042d59b06b461dd21cc3f06162c43e545e29154a864fa20d8d587706183378e7e53be9d736ce904c6a387fff8e6669d92745c35ca470fe850d07d308f438331696091bce44049ceaf38b66a0062f3ed1a0ef72575da1057c9bf74b093f430035dfe75d0d334f03f93f43aeee341039b474c8b78b13a935a447f1d4552779524f6bc3d6aae4a23cd1d0c42b54f1abac61abc98415b2942fee70fdfd42b81406260a9a3b6a1ae2416d3805e4a690f6273c556e173ef5633565dc947be62d26b8f8635adaa8642cabeb87dac9331cb637bd1d2a12bf5f447de1ca27d08536270b0af49122274e75aa34015c7b18318537a22e4a36446ee47332df2878390357cf0f05e041c33b99d5aaf218f362857fac3e1e7cafedfdf5c2de26eff28c570efdccc808ed1ca94ca14edafca67b00942de760687e41c52ab13e98035fec13eabdef12394bac30fbf025b7e0d53f50dc5a235aeca5baf97713ba340d5f6ca2fc1f78ff228cdb369e38f6603af50bbfca93c8dd5c705bd2b8e61b6e3dfbc61441d7a1822321e88526ad0887315e4af7cae1aad49e6dd6268299415429c42d69ed99a5ed1814401a061eaa3f210188336eead95aeeed364fb2e4996ef66c146ee9a1463932598e544809352715cd0f38e63e8f6b871110ab6a3c5b7c7fe9c6ee2a8f6688cd708ea8bd53504263d8b8f02cf5218384c115b8c053a090fc08859fc7b6674f4169ee256af88d9f011b2c8d6f61b4fa825db575050a1f78d654c9a0b7d9963a28859a76af13873e27bbe95ff39a9d99da8dde88c3716e7270cedf5eb0b2d14d33594849b652e668a65825d04d5b0205eac26be60e44b739c7bd1ec3826934318912e50443f1ace79351dab8c7842f204a738aafd682b90acf592438ba62d6b57a1b963a8f8cf6d2c633f3fc23869a46c739d22e9bf669347615da4ffe2c85614b2dc0d854b3fa3873fe2b28faeef44578009ac5e1b871e641327293435936d84940549bcb1216e8822f236f0e95fc7881398e788fe75af8a99065383eb206e4cf8f5771c432bbc51e9e2cfa16ddedb28bb203270075638c281d5b0bed55e8f36007e31d6ac5289da03aca8da0df4c573a4876609524e1473f7a83028548b23f46b6259598a27c8e3124595abe8320a0d43a8e8d1a5ecef51547bb0332948c1a7653514c6f5838ac4186eab660e9974fb035aecc026d68db9052aa9816ef55ad873712c9d6015b3d0630c785d48423747636c7255a227044e381540f196bf782dcc56e22c6e04ac77bcead34d4a381d5ef66b7ef48d68b9e46a4647df0c7b9260ff41f46e30248173cbd21375916b3c56a501d467679464aa4885dee20510860a72e56d1c597280303512857410400e393f8285fa5293f9030ce2fdb742da6ad996e66477b88d3431408e4b0e41110030ee1d52a395d3169aed651f04e22fdce79e7b5a2106be578fe4867157697dc3c87df6b0526e04827d0d8a4529d7b6629ca9f88ec772a06fd1b663475c5baffa55d2deb82fadda56bb64c87cc81094e88460948fabf404587071b7a89d65e4bf81181556a13a384e3c72510ffa21965bf4db1c21821bd6dc5a84a01a4cefcfe1f3e25a414842ea661f3d644a080f4bddfae69c17843c22b673af955ef691b072a773d8feeb02a6f909bdf90c55ecedff36e63f8ffd9f2fa8a33557694d10a4a9afca37856075da0cef5dae559d2641ac824f4fec7c749434c518758ffabeb874d558ac1ef5ad1910f60a12a5fe5d8f3a7ec3768074d12ec5838514be58be3b68fc3d4dcc887e7616c586c06b258d8e62218f47a1a190a0e2a26f44f25408693a73cfcf28f4ff988ef681777e228fea8c5485224852fbaa6f80e2770c9d6bc53516c91bd0287561ee5aea989cd5e4eaf99e73094fbbf4a506acae519a458f230a7381049c29d34e3f17562a119ab4b4e5ff4a16510d0ceed9a5296a05d7e26f4dbc991f6bae01a0f5df0eb60c5d5c21a1f9d6423eb6bf164ae20610e37a6d7c3c26df7d708f766b9de717a05cb1d04c35dda5149f70bd1a8d4a0c75847522ec7f8208536be9aa2aea80ad9733d448f34b36cb769821fad0c603582c404ccd2901340ee5a43d2165edc69574466b2aa2fffbba5492c339d0a36b52d4b1a1871fdfd241aba32c364e7dfa9c1bfec858b6141f6260d9d1562b658f5010ad869335fc2f84341ff0d69cf9342a5c620ab830889081063c2d6a2f7fc1b27cb7e41bc7ce280c5ba195b2652786bfecf8bb8a37ff98a02e232b1e616e7a431fd7f17a241c61e15943f7d557b876b3ec822f83c565c1e2374c29238ec14353d25d58874acd1df864c8b3bd301b3e98da03d241906c11d5e35095139f06fea149dd2a6a450aa48aa4d953c4ce79b454d61b52a8cc7d51ff1794564e8be2d5ad47f2a3aa6494c864f9a4bc683ba43647b7e7322801516a778024b69efd97182af68c2b1e9ed17766b50f9720d734c77db80bd8449753b37974b3701b90cd9b39118c482e9c140635cd6e0ed968c26b4dede60e38ef3c1ede1ed6187e7acf9df84c08d44ccc385885e45f061e0cdeb855ba67e4efdfae914d6f4548f2261d6e9147bffb8c2d6cf7777c22923a95c9df4f03c68d40d15eecdef2d187a207f48d2ef249e2df8dfbdfea8941149e9b400a4726aef7e08b543d0d5f48cf471e1221e6a333333d29ab51492735857252a151f107e596bde55f5219432a4fc7baa4ea875f5291b933f7b2237101f3d730f680b8a4a56b2ce096b316850d560aa6e0bfd2b677c943d0255406df3ef5b75bea6fa9e16e937d3035c804704d17b7313c4bc3913354366122e77235b591457ecf3c6ad9096ab479754e5cba9a73a30c307c26589a1f324ac4a9b08cd29db5b631bdcd6fe245ae1b8586726c22922fc62cbf8ecb4376507efb8355083e188cb4500a460be1ee7c3f35e0e6b42d9d8d091b43a1b54c8df00af805cec2a9a7a241b851fa2c25e440ac340de93ae3533ae31ff382e29d828a7f2353b5f49142d6d4ea950bea90c929f73a913b8bc13f65301f610047a7e016df013a477d4fd5e021e9720b3a0bd23958fcc478201aeeb5a9971dd8c43903aa47de735a305616a70379046b04f348e22250465a2b2adbe309134971085291caf7cf4330932b30f7f161a1fb84302dd023abbf36350665e63e8d61b8acd760135dc5b2f017e00de435a6fdbe997d3a3759c447b2dbde7b6fb9a59432a51453073407f906916a2a3d3bb32a7525c6151b542381bc6cb0c81ec2d4589a8b43c07c5690527e3c7d88b2a5b2d242b4d102cb76396157f0e4ec99b302db72bed458f09c933dbb05e9a45008f59737a40fb1347572fa947304087811c4713e22eace9ccfc1b2326c7452f4542e4e8610c235cad9c685320fd3ecf5b18461374e1947a18d2be0a6fa71146c3f9d5565a5ae6c362e962b9b7eb33a29caf931c6cf381ff135ce874c6259903ce77c27c5f7c8f988fed919da8a8d156ec4cb0927b10b3e7daac0f6c46eb8d05ceb30717ce220c176117b23d332c6b1394772724253b0dee59e1c9b8e08cb82143834cc386243977b90f82441424515486af08565b9dc8384051c14dbc21a2128d81590d099c072627446588e25c60ca2b8c10c68f033831610610644381c587bb9e7481a438e48094561b9cb3d478a10852341c0e24893908ec52ef71ce139b263648584a5b314cff7dea6e2c83d4b39692ab99ee96446668d8ffc8b541c7b399626cab3b3c64b2aaccfdea8b0547cdc5dee2693e0243ac6ee5261a8c9f50fad78667e07a4c4f5dfa4784905874bf14ccb4952655584a7668723a29a725d25674b4385957538d7e34aca4e4e53a982134bfec447fe61e4445149d9b9ce737d8a8afc9c159d7960d326c64ed9f992a3c12515e220102f0f3dd4eb0c431fa34060220e026bf750cf89ed30cc7ccc8a79fd04baf3a54a8575e7ec704e3cc3ea5677ce0e278d1d29aad9e5dcf9a5a2308285ba5669ece434b6737538b17445c991a24a63e79a72dd895c9f59eafae573b251b2f48e2c382b6554fbd6b2783858a8b4d69c12d8e059c1899a152e522c511ca12206cb06152ab03eac9525baa8e9bc085259828c23422d5b979052c38191d32d1184820d4ac88824342d6cb8162091b1785da0bab0d96c1347605b683a26a84f70b2284c709d301c2b92a05ff0549c272598b2861174712c592829d900282dda8383460a6203c0f89943a0bc014f58120924ba6e71b385112b040b9058f9881b1006cb477cc37aa9f58cf048cb22832b6e689fba3021fda2d745892003555763755cee91010a4257d8bfdcd3041a37d6e5724f135be034c183501956e5724f1334b8e1db274d34d1d9d8162ef734814490ed2ef73031c50b1bf4e06502a88789232d30830926a6dc81470a65aeccfc04fc98f3bdf938e6ac756bf18ccbedf6ef4e40f7f8641c9cfc32b963f750807fbe1e2ee3a0f4907f2d53ab9bcaca7cde85ed21bfca0844c04159e7f4e7efe11e691c940e90328bc283bcd10ccb9f8c83f2631c9452be94ee2cdef559afc65607fbca7780bf54b9f5ebadd78f1e0ec8b12ea1b8bbbb4ff7c8b1d080e36acd1de7f4798538aa342d6a3df8603d2fda32ae9457573e6365ff1d77b39a36a30b92f3413fbff293481cbcd24b4c73ac8c3e9b69c82221034e36552168c48660646fd99d2d71ce47c67369c6e4d2250ed26cc741faa6cbb179c9c05e4dd8599ffe0e9b5892df82447e7dfb1ab5a217561cd17f5183673629f08cf62f2c3f007df1b9f4e5f7b28a23fa12b4ba34d4a16269d12058552cb5893e7335c8afdf4b501cd10f595a5e86ed9f960b8b61e8bb123674c171c171c9e199ede9bbd08067b4a7efa272c1b9345bf1ac1cfb6ce725283e26d27aec0e542ce950c551d08e930e158edd91d2a1f22e266c98eddc485d17255c6e224b2bf42191775018ecc2e88ce0a060a20e13f93b7f9c0fc3d0940d339f0ec530f4352f626872e9c7b08221a5c22697db05e0e119975b5db6aac5185994038861d5476b3384bdc92377e9bf4cc1c2fac7ab7911fa5f829a870fd9710a1a804d6d9714c3502fe887399a5cfa39563894b8f471dc486951d801a062a9ff049787be2f413c6375ec60f3c285e53ffce14b1077b1c0eed06b5cec7df8b38cc1a49715cb0fcf604fb160907ec812e5bab05070e9b3d8f04cfc70658c4b7fe58767424f5fc344ddcbca9f78c12253828d519fe844aa9b63e592e299eba97ce252150d2e17933e97fe25cab934d37ca3c1a51fbab02e55e3d20f595a02086298f025283583cb8ddf288761e87b3a36f4a10ed5a5f1b1aabaf443f4376e061d377e3ff4b7efd2d700706998f95cfad94edcb99eb9ecdb6186bed7f70c437f8678b10f49fd182d7d99229360779c5e821ca4ceb2fde14bd0a52f030e0e17686caac96a7aeb11b93583a61427499e424fbcbab9f1a9115c9a5cf0195c02e3a8c533da8d330a4f2e280ae8901ba71a17f4e192174e2e6e6471304660b69cd4521d8c9bcace8fa60bb0ed6cb51ccca26006d0fc26504bf2cfd60462b9d15b591ee48f9e41369cadd99a52aeebe3cf29f444514e6ab99e698d93a64d1127dd3869e238a97e7c6a44e72b0f938565059b35fae7abcbebbb9ae95ffc0ec6e79fe66996cffdd11d396535e03f3f5275c1e02564163f0c6c7777b7e636b6cb95c4b9e7a00d26b5b4bf308cfc162631ef60506e97df2e6263ec7e9634461ebc9f258dfd2ca947772751393fc9496c7cff907f6ef78b93988a2446aefc1eb1c42629597192d1f15439299caadb4bfae7aa7ff6cf54ffbce9b983aab96c72f952248f60e712c6546838430acf3777a6b0fdb1bfc972315ed4a8c171a3b7b74eeadf9e65dbe40d5f6edc5f4c4b0df0c8bf7e3cf4edfe6c70e94668e1848e22e9e0d67fc3ab1eea129bfc860170b9fedc3ce287f2bb50b418e4dbdf85420ac3c0b7391afa4aba04cda34b32aac042e7f6e94ba58e0a8b037f9f6ec8efef97abc062c9ed3b811e22e472a3aa493e1501f193493e8bc2206ed8f280c6b974c62f0c028835c9907ecb03b24bbf1dba3b3f24f2d98b1b734321b187f7a94f577664d8febedc0e7f23bb7c7ed19abb3d89111b5712229227c90e42a72460b4b3acd67bc0940cc3df1c0d2c221c0e3abb4b17543790e2924370d56709847e7174f9702f1b17db810123e0b95708786e0e198f3dddebd67a3a15717bc0be2f7310f02ef62f0e562055abbf8a9cab217bfa01412fed12c920c25a30cc0627cd3bff862797bbf31b55a2270e38897b68a0e44e99907d88386b8c1bf689c8864303285cac5e324e0a71f4983f411912e0a4962b6ec84ceeb40287bc44f2a9283e8e8ff2e34fc7899c0f347e0955706e7c6dfe088243eb60fe701c82a874c081492050d23044272c1a1030c902ad9a4e3331ecd53a784c0cdd45538fe9682c904413c1a3e8ffc20248187dd20ba0ff0f7f81491090e125068100937060568c4f51a77008a2d98e8950438f22edd37c9e4207757d8af19e02936200d23044223ea8bca4acb25ed775b17a0bd4162c4e318c8b6133da37f49c18b9d967ef54c3bc4d53d44a20e9beb7e8be1fba045d3871320581bdc047de295a37adf4679cc2314ea1d5e8a027304e02f3fa6b2eba8185b233c66a52aab5cdbd5ad44a20b18f431054276a539fb8daef877ee274fa3105b11d0164ea3eb43e844b1ded87ae040a6eb4221d80f801e26b5dc7514a3b8ebe15b1893e909b1bc1f833166098f861bf8007051886f8612b71e3f58f26890d3b286c954e07f909b41f75e8be28d85a051b76ab3a0b2ef5139ea782356beb5326868d3d657e73d737833fed2007e3fbbc7e4ac3861d248196f99c0f2b55ee75907f9d45fc336e7ce79ac530f1352b6cd8419352ad058f9916a039312839efee7694fdeea3b6b2e2bec2f9f49ecdf9703a3b91e6b50e7c63d0213e8dc7c0240bf49386217e7f2b5cbfe64edae14a9c88e378ca751ce545e89c73bac8351696f99d884d4e9ad34da71c5c559f9a15bf6f7c27f241a42072fd72cf0fa6382a3ef3ec8f5e574fdbdddd9d88adc84d166052911b3fb60b7a143fa2a0739a08ddc48d51d80ee219176e7c3f5d28af995fe87fbd8ac88a44f231ce47cddeafce5efad7c3bcd9530e023ad658638d9b659f8c10acc7f9e82086892dc5da40d35fd8b059cde299fa31f4dc3333f398af7df67c89fc678eeb56d489bc86d60b715c972b9b15f630af7c1a53e21911fc0198877f22a832c8acc0020b9c8f59c695ef713ee6778a0c1b76d0455d354eba3aa8836a9d45e49c2a389b2f60881564d4dc1b97ad1803e876d9e93a4074dd0d70bd6ff0c8bfbbdb63180fa5d81f3d1cd4de1382ad1f921620a127024208f9f37fa03fbf961a2610f4fb9c68a99bc9391af9d5386a1f34ec21789dc5b3d7a31ca2e3324f95ec5b36eca713d13783dfe9a364aae68e935860cd1f27459952bcc44e60cd253577c3b2a0eb46a68e93ba8adba3e373b7cfea5f9c8ff95c55bcfa3864bf7d98fdf6db4fff162c9b0fc61211ff71e2c6899beb6de24b64e3b1011a8cc68f13615c2c9baaa9c4491207c3f46fdbfced43a27d967926a78a2241bd49fbf9db257de435fbac5e779fe220b3dc3ef5550529e3f3c5eccf5cafee9f165fddf463f9717f3f5f17333365422911349e676d8c89898989449052ca8f730a8d5fa494d36bd91f7e1a2ed1c491bfb78a8991f918ae292b69e27b5eecf131c6af1cd9f24cd2610e21cbad2c0e7a7fdeeef90a4fb02cb77d7e5a34ee314ae9bd8ac1a2d02fac964ae1eb1062ac00075aa431c409aba6e2044378a3cb3daa9a1cd5698a31aa20458d2c7800051448b62a94c8b92e2354104f5218b122c51a9865e9b1014b8a267842a21802095e3ce18a1aac8ee8d1f9b9748e50c4755dd75584eb2c2189cb7afff2c50d3f667e512f7a5d9f65e1d15942e74811d37a96c5da4b47c77592a0c0f23cb1ba7248c20e8a30650d132cd1c424a57862072a1348038a2c8624210935a6b02c4fe43c01834642cc47610cd4095e2085227e4cd70f86144187988088ebbaae6b880abacf8c0ca8862f269cbea0a71f22b6d314ed0454d33eb85cc60d41a9f5e41481dea008aa1616501002105a26d972b9278749ce0f2a8fc63cf30280cf5df43a5c72e06871e186d7020bcc2bb8b3d85851b15d88dbb40cab179d327a3333ef08d91842bb72e039707777776707cbe015bc59a2dbd8b032cab91204db2f27b31485194e8c2ad376f2ca418839a2b818b35806f6c998da32edbdfaa1c5a62894095f2e57f9c5589d24b56cc3641062ac95af8eda389253c519a6a5bb20c618638da368ebb489239631c6187d05f61c162ab3d49593c6687379852d461642cc30330ac049198d955159c638eb46b7b0fd714a5294329a613bce2f7eb224a5c4cb2b3347b1fd5b9604db5fcdb01ca36f2c9594524ac9655eed9c18543b2cd4c92965e704daf38c6f4dd8f81a336749d8f0afa3625b8b8adde2199ef9ae7fa3c1a5ebfd1ba8d56a5c5fabbb61a3443ab46269071ef9db1047d9a53ab028f6b1e10c1ef9a3a146a77c488b0a2bb85d587f9946758b526fa9d810c18525e3db743b1d93ae48a7ea9674a9cec8f597b12f1bdd14456d04b5a143f8071bcf8272d06b38ae483f574543d4bfea73fd396bfd16c91aa3ac1c3922d79fb99aebdcc9c11b5428c422c26a60b7220eca8d1551180decc613630684d02ab41281bdf2e5d7745a77e324fbfe9d7f67c449a1f7ef72b49bf08b4421542eaa0e155a791f23134bf5596e1842d123f60ba1e2289413470f823131322befafad88422c076568a51272b225e1a2bafe2e2a279db6b81d72c2f2ec097645c437b47252b8a142a86aadca177ade7f870aa13a540815ba827580cb3d57a46e87e2196a04f340228af8ae8a5cf17343ab58ea8a7867e3a0bf5d598d7bb815b1b0c7607f4884839f2d6fcc92c1063b8afa6acd4dc0de30077b637ce618bfcfa6ab71d03b9483ae46d00db79f2d6ca809c35c81fa2ef75811e5865616083cdb8fcddd7e62a943a17eac7862851324f5fdbb224eda8ad4ef9f97a750811557aba290131fd5fe422b2aac8ffec22e753d0a14cfb470fd3b1b246ed8a5aa00badc53854ac7e59e2a58b743c5526815571d4aba879a308c6d625de8503af60adb53c5a987896a8ad5ce7de1f2144c9e5ccdb790132fb1c9ab5842d00d3b54154bc829b5a90a293937338d296502cdd68c32c3983f390e4e9d1863ac22365d223645ac65436dcaf5d7d2905146d93bb006e46097d3a1ba54877212d7d83829a545e96e3a1c4da529b9ae9d42db8d8ffc8d541dab040a76fbb9fe4eea2f22811b977b84c0b9bdb950a960c3ed8ad6cddd58b1a44df18d084b0bbafe5a1429576b5dff0d154bda4ee5b12b8fa5f9a87e76aeff9c4287ea504c6cd8a13ad4f54f75a80e15e30472616541b890290590848a9927cff6bc9eb3bf67c87ccb81656f4acf6714664b8cdc5f13b07594a159767665df702ed13f97e019ae4211a49452baec8f7d1ec1dd5c6394b2f2a7f2021b4b2dfd6c5200111ebffc9ca9704b7f6e6a13bf8c5ec44189c6a976ad5c5b413c2d13f16df75a6bad38586e30e8cf2fdd59d9d783611c04fe41fa0ba712b73fc024c95f388d4ce1fa6bceb163885e1ef937c942b3c286dcaa17d617eb14c3dce02c71b0eb1938b245ca6a651cc0b2f9cbf59757ede6e681b5dfecca2f26f7807e974fb061ad3775e3245a6bad1527e84463c3a9e606200c44847f284d33339077bcae2b7eb3e5e035e7a4cd021405ca27e57e9c449bc86d75fde594524a29e54f27d737263c1d224008204c294c319bd023ff70e6caf04cbf7359f4ac9a2040de50e61fc8fbc8c44195e541de077b47e7c6eba4507b7a7252c8fddc284e6a205cefd9b9a144c4b02137816414dbfe3e819460ebcf9683cead9260438e5b5d7fee27962aa7e27e3815f783029a52449c0a0836e454974763f9d09e38e85c14aef5713f9a0ff793868ffcb7ca328126502c693a3ef2f7adb2684e6cae6b395268394cb7b0fe2c954cbca14c8c310d8aa3e3251baa293bf809698d0d4d5123d74fd729a99b5c4ae78c33ce38e7fc24a5ddddd7c5d177a7f3a70b9bdd50c6390848542c4d201fb9fc163701e6f1c9d2fd59bbf14ca09346c4497302c54feac0fedc909e70cc2deccf8c428a2c1aef5448c47379d7e5bd93765c202f389c24e304bcb83d16dde012c7ee8ea51b0ece80cdc0865335e5dffe3c867962af0f49fce95df7eba9e779b5ced40d4e1cf15c36e880564cda71fd7d20022402dce2293e1c857bf5a3bb329b9a8afc6adeed5cb79b5a8bc286bcba2cdce919617b08ab71dd3dbc71556ec8abe791fffb5b617dc258ad7e74970c347e09a9eafaec1df5c773706f22d77f366bce1a5befc16b574050e5df7a5ac68565a04bc51054085185abc58d86679ce7e4caf1b310af796097bb570141fb963f2db6c418240726b785eb38707a7012f7e860c9f507e386e0ad3c3a088ab37b75ddb7229c78c6639858031b6e345ea2a7182f55d0529cd0f7531d1bca3b43343c9a1fc3a358b23c9acf7da1b77d277a9af102ba5354efbb16d6bfb96d07768a0b1ec47ba61c6cce4356685fbb2dba70849d8fd1d09ff451b45e6a53639b7fac224fb238383fe9a39f8cc3878b82e829dd0639d03896a88da52e88961ada34bf4df3436da6eb51d244643e11d5d46c00f91488345d1f114374a6f96e92bed1f9311b407eece8bd3363ea33470fe3c83c8408f313917962b81c0da0f261b42a92af2cdbac28949989b57bf6ee27e94c18af13f9c441198541f9a38a25d9924e9a871c836190aa2804fa7ca38c14fbbf98fcf9a40f4b3e91acde3e96645c963f5a8ded316258f52f6c6f2de15e22665df9d33c2e1467300c3c73a14802c3f00d2519d73fda2bb1af17abdf152392c1c1fe198568511b72b5ae662ddc360076e3d7c3951fc7707883670c406cd0a14f4da46bbcc44e60ddd0a6feee922b364e0aadcf0a6aa566a5486b2a6960a0be7f2d409c471070c0d3c3b5d6c25e27ef081b6f98bd68c533f23dd4fd701c6cad73a450b79f061d577ea215c3344539d838c819f44d95d8327328e98f73c6cfc1739303c7492cdcfe1c3bfd399cf4e778e22546238791fe1c3932f6c7cd395a93c388834f7f8a3e24a19752cb719323154ba18fe60607fb43a1279d1cec1c373eea38e583fd39701ccc71e360cfa01c2922531cec5fa1565b85e54c9b289c53c21ca92b6a72fbb52a9b63a88a4e99c98638ca91bafd2e2c979c58f2502eab76d9b9dd9f23154b2bac164439e1e4a862897956fd3958b1d43e3f395a39824ea8e862134b318b275a303999fa5d52b1d4ef82133d542ce5487939b73d9cdba9db36a6f8b149eed87e02f004e158cc0c3436b1e491223b72e9705104a594d22695270b79a48821503534e612973a4646caa794beac5c9a56ce4f06e1ee67fcc7af3c72931c0dd98d5f8fecd6356cdb9bdb2f63906b48143de089315ef1f23c8f7952403ca91ff80d3d1ff2358ed715af8f318bd70ef1bae663a23087d9c58ecadbbc56307e61439b5561e30d3d09643732b9a8c7f9a03f8976055d970fa71c25447ee894cef5cb070c73f717801747cc98174b1480f23f02c03e1a1fcc30d7d3cf3ee4a011f8d030e82148c0429814e7cc115551cc8e5866988b7e1843011ef98fc173460c4d10adf105d0f72b28c8875b470c63c4e6320064e501e683613668005584033770500d0e2f6d1ffa1bcc03fb161611d4ab7d37444f63b0ba0a83516c8043cfbde7b5cd0ae7c72602620b7d47310cc330ecb1ee435bf7a12df4ddf6dcd7b0d510faeefb21c900a27b4ee5edfb6bd7d34b9a01841340ecab3c10dc770f847d151f2dfd40bae71e88cadb6faea53f0a74cf7d0dddf735a87c1af84240413624d9deff87b689d824130a89b6e75a6842ff437743df0fdcb761746258cd38eb18ba9e7db0eae363839332ac5e37380e2ee1a14232e79850c2cc9327015e34ba43bc927e3b4429295d6349dbcb72c14518469082841e70a105175c68a145ed5ab97277c76dd24a6bf67e2bcd686dd556062ec0376880d170295fbd58ebd56ab5300c67498849adb506c95a5b23461ace08c341be4fa910ed0b88ef6f04a06b755d2b5690137902e4a435c5c7ffe2d5eaffffff63add6fe4b1064fd775d7f5daff1f51793e25fd7334f26514a6790c85ddba49c8ff9d5ba5a35e19a8585e28d719d015d5fb031f0681c2aa414fb8d73b1f42a088d06648c31344f4d7ef5d7e785dee87be5e3a42b0c27d1d5ea0607a88b814629a5dc3e31461feb37649694e981bd10d3fce8d7c2de3f08cfe8b8d5e4ef430e3a0471508738a20912473c5687200e7a0e12c85a302c8dcc0f6ed119e4ba3220fc30b37c129300f91f63dd7860678a47fd3855c56a19ffe7cf7ca2835a670c015edae39e1fdfd45ae911bef8e28b2f7cb8887d031533d7ea79b5664e58cf054f89161d31f585eb2297f5f27096a8281613e70a2b08f6d0b79f8836394183af6c085e5f0282208f14545ac7e218e618190de490001998c551789cc4c474cb09850f142c9673f3639fabebff77154bf5fbdbaefa4301804cd75725404a50d75f06efe19f17472e9dd8ef1922072fd83f0f15a2b821f3104042b121c8c486313c4e9a4c9c2463626228c59c00d25ed31ea4b981002f3c4f6234eeff6eedf935774dd334ed3528738c96f64d9ffe9def85f7010058004c92ab8f51b26c6d52a161b3074c928249973677341b379c3b7fb5ecffb76ddaf82ce2337583b384617898fc00614ee85431c6a8c2d547ae42bffeba9b6bbf9c8f54a79a4d87307b4e9edcb51b0c1bf28cbcfe977b5450dcfee6284d9d0105b8e37240865518b219646e90c811379401251845046018181ef927a0f6c80032f1037180588e8d0f8273325012c761e6ca35d65aadadb1241347fe20f8ef79d6665996653d64bab34cbb6ec8ac8bf2d76f51edbeae2bbbbefe5cce870d615dbbbb767315c3388cc3388ce3b82af3038aedcf6e07c5bb81a1f2e160e35be5fd2d8ee7c9bed61e3621b7e6b05d16517f2d0fe8df7eeb2abad8f5dd2cb5b3cc86a87e5d79242b2a9f13f1855c217706edbb8bb4cd37c54f415cd8ba7dadd817b91d7a06a9031b3f4d9fd86ee9607570c3e1385d64418c1337b3ece666d9ddccd99552ca1a4b1e68b5701d3bd9f02ff3c838275569fcc25c2f5b2b15b1bc3e944fa58c38e857a58d600031f3e4393fbba0d8e9c48993aab5dec7c82636ce39e79c72529d2e88b0c58e55d1164abbbbbbbbbb9b1b85eab6d6b31ee36d0a712fc444633814cfe0b804b8de35618baa9c93eb9f37e4d58ecb452cc7517c1e2eec4dc03ca3913ac0a0fff65a07b49f3cf28fe901868d37e48bd2eba94808cb83bc3d38e85de3a8edfad7a89645850cf483975a871e7c01a837655f23c13046300cfe97a8e712ac43b13f62620f540ad57d5da91beb925b020a13159b6ecd6e3590a7dc48a7e1368ef29b0e9acdcca846759e58a8444dd7453deb10d1000000000315000028100c08c462915838cf7349f20114000d768a4472523619c88324c761100319440c22c6000300318480216668c606018c54366a21e387d42368f0f5e04dbf425ab1238424b09b66b1a091249f8c17b7753dae116a81f10861636e36d1ee2709f6c8a009bb7e4619c9250f380dff6a1b8400ae9bc26ec4f24dfba108c9cb8a3b16af8b93ea89b7ed8fd077768b9e784ef0260cf6010edb5a777d4a05ddd02c8a2c0079ea9260d4e4567f31fe15ab1d2e0e5c07c126d2349bf5e2806c989d88a1df35e25e08dcc70469e20ae32df279946e08053a5a6344f901dea0445a7fc8cf1e2c410ff5c53042089423fe9230d0d61f8f77b801a624c839c3a4c066943d99944810777fc0ce1cfd9a3c2541b248eeb47061a164f1f42c2e71e73fa40ea047afeb87784d2bbe4174f3662f975b9bf72832b6d1d3e7291032134a13ec540259b1584c33b0fcae687787d2d6470092357f0479a3510f459761770f8e13b57e422572cc88d27242ddd7d33d5fecbc2834f40b7b8703957d25073ab3b5266fb29445d7aabaae1d3fc35313123e18c096242a614691066a8b4060993bdcc45d428f902fac4f5c52058cefd05cde4fa5cbdd5367d54cdd5de9ca4b394b32d6fc1a5d9ffdd0acbb5f1505241700795ee4c9de9e37884d8325b47ed470288a0da6d8b680b26227351c9a3bb678e909689d40592b43d2f942b434c56beede68c5842cf161825c90fb65034e2ac9a4750629233fcd3cb10e97d019cc46221b0b5ec2677b300beab6ae1bde62d632130a72e6ee678cc77ccb123421f398c42ac8cf33c5cb996ce5644c8107e5e3eefbd080297363cb7f02c7e9126324d3e96d3485e2e75433b02e28686022cf22be0747e55fc220af6988f65207df33a64826584d2661e1e8b88382756571452b1eb2afb869e7a57c893c1339157fcf42ae5b828d339c8b3c5d1e9a9b4836f1e35e9c382f372f9522b84546a353d9967d1274d19c6fef91cbeb9c3ecaaed33df078c705bcb1a25ca2facd19d7707fa90bcf66355c1c42d29d0991b11dcb069039e1b3fbd9b5ab6818fffc3753e41b9c7ca987ae2abcee62649cabf96f3dc63b8a37049e67948e8be66af73b95146864bd58e73e71644f4253378a52948eb5ba89bf5e3bdb6e88e3de7668df4a2547d8f10460449980a59fe46f5a82680addb7bd60f5a5081e4e395d27a9dc5948176b551c5c880f48c245c8d2d39e98dc9b53b8732d4ef8108cd5159165520f188313b30bdee538ae5da2af4fd359187219f8f9b72815b0de5a4d828488b1818ad357efaa2511519bc63731eb82e70592adabbc81e55da0da9f35c744040ccdbd218d65e800fe13a11a56b4949680415c48c8174e20949ce318b26bcf1fb7e70e688fa16f43896b5dd8db77a51d7395c05ea1391b468b306376fb3245a6e041b4e1f87d9621c17d1304fded4e05652fc1db518ab9df72ab1fc6319beefffb21bba84fb77cbd682911d0e840bb4651c1148ec2076a9727a516382d997f687fd4ce0dcfb4008ea24f72a2dbc30f6a98ebda0f959a34e8a1f0bbd980a24e0e33cc7d9c9a5c7e3c4c1b63a543e307a7feaf9b3806a6a2995dc30d310438bb36ef3f4255f6dff507fbf56e0cd9b7de4a9012640eb1bafdc7f1289cc7d72a89d1f20e48e0243d6cc5a9d70b088844107f11f2191bdf1201d2cfde3afa183999c3993831c314afb30585e9a16cc5d842c8700d158547919de060248d1ad62872dd6d22dad932e992fdd54e9e06e8045c250aa3759809617e89e21a4c9a017be77e7e8c124af35a8daf3e0c6dc1576abf5b31cf945e7c159cd6fc64728752acdfa9872bd8699559d10baa2dade5462dfd81785481cd729cbea861da52978b06d04c03709e0078b6078bbfb71d393e780b115a19b160435ec0449914ec0f3ec73c26e6dbe267cb32aa5bd12a28961437b05e0687e2b70b3c87d119079806c892abd1baac55d64411c1a4e11bd7764cd98ba01cd21b65ff9fe05063223c828de0a868488825c7ef84602336d439f148bd45ad6d0501059a7c20388e2a7c5a3724517fcc84a2b721542db0208d52a0a810ad60ff75506754a740133471a24d85e551f042993a6b70782ba1cf06ae3571254b5865e87c0db50b6c14c089e738c14f3dfbba92b14a9c6ae4f8720e2a8b89769940c5e00141848dd8e3b90d84c9a543d5ee58522901c4e594bda265a24bed2e8b5fc416e3f1b2573989cbd01498be8534b8f1be71b1d751df9c101e19b34498d183d1b8e8cc22ba6081b83968c987517e72d29328eefc27bf3897252056b222118e34c7121218fa4d19231bb9762378055cc831098046cc6da78feab68e5c006c53f8d44a70ffad2e6eeab0b01a700c09a8c9cff345f0a1a449bcec7131da76fda69b838aef20c2f94d97234b0c25e6dc6b13280361032c355b51ecea444080688048370affc064400fd52a1c0bc516879ef616ee062aef1db393aa5ea01047f65cfe18fc06489dfe3b08e3d8dccbd1f707e8be24f494d0c1771d9c5809e84ed28b590995bdd8e82474bb95bf5a85b8ae4def453719861dcc8e0bcda6184b9412b2980d3894b619e344bcb4c7fef2be17a7b4bf655fa235d6ba54c733714c5957e747e462dd326788eb4fb78cb28b7d45d8d826f4b03320e05e7f6fdce562dfc9393bd72f2bf62dd122ee86c6187b39cbaa3ccb991c0131b9b188337776a4a52d2bc71561ec5c9b224197771122bc6250c8ebccd8cc21cc58f3719c23c4bb5caa628de2aecff1778416ae610d2b0200b22771a3968abf995a920fa9145c4d681d4b0b754c8feb1fa623ace44e023fce6c8452327195b78d1548a765a4078b5e47f120d11f94d6821ffd01dfd318c57703fbbf5550f732c2026948ba790fa70bac2f41ee0338d2d0d485a6c9f110de7a7fbffadb20b2ca4b806b253751235b7a5d0833a4e07a08132a7afe4bc61d2ef4ce508ae81f4e5b18146d4161a9c4e1225d851c173c329fc00b2922d35cc3125da8338e99c1cd4ecade61f7af6a58d7365f3df3230608bdb61187b2788874157016ebad6083c636845aca21520bef60432af4042ee2d988df1e59760fb93efaf2fd096019921bf7baea206825d65a00beaabbb209505a7dfbb20160896995e22070c25aaf51eb94b2e2b03a37455f75178f4d448a581fdf8accba5bc9e54e18ed4865209156dec2edb2b6c412b83158d9791fdac03828bab1acfbd04a3f605de737ed8100e61ef9825617b298557604644b354145d8474a471e31120b2afa86c699ba913816ea507d5e18aab326298fa03ceb01dc175230b0726dfa5f35fa0d00e609f5c1af8a06de1d45021a7c5236d6be8cc29527f1180464d1129c8c42523d6ec456c071019256dc2d9e0cd34ac39f4f133648a3ca0024c83488c7415f86b137b5d35ff5cbfba30bc45b1908e0a3ca258b63664973c9e299927e95e33d88dbee3082ccdcc34822885173c10de385052ce8d5ae16316205e6f7626726e8f1c181d82c1b665ad3efc36b157c890c185d13041df59ca971298751f08a2c1aa2640688bd152ec63ed758ea0c863d1310c08a4d3dbf0a5c657f9770c9c48ba783c0ffcf123e7ef9b7fed30109c7dea352dc7311af9563f9b81afe8fd250a38f3c24fd79e7a323611b4a330d542d6a994bfbe97c83947de8b5b3f89082a6703a656056168d8a32cb6da8d8b95303e3499b3fa4c7859cb0eea8aad4305aa81e55f27ccdb12ff79194bc781e2342e65ab27d01a080c073e7c68fd6ccc9baa8504396f66acc0aa18bd23d7eccd544f9f986a39840b5d6842147176d47a5d967cd46c8af5195aaa0df9e92f1d7dd2dbe21cc24b4a4cb146a0be5ca743e5f4b3ce1a3a9bb40d8b669c290194956ce0020f9bd9f984eb40af3ef4d5c39f164d490d4181c1bfaced8973174c2032684ccde4287ac668261816c6c64b8214aeb445b14e12bd436edbec8b8bc64727cba6b18185443bd04613bf55c985adfbad00fabd34b8a4b5f3e1bf4d73a7ae58bd95b70ff11788dd8a721c0a5c5083f25b1a69c8a41110e9a0599830e4424240d6a1da2179e772d6bcd0f4377a253d5f7bb403f13022bc4a624fba85f39746c5d3f5097a19f477d333b86e1418be9f769860b502d6b4076de85e57d9f922e6e8bf65c3062216858d08c7604ab1500a208a5febc63f8a1e7a619aada69df74d4bf81de2cf5cec129f7cb3d7d66a2f5cdcf405d6f7bc8de234d6bf2935e50227a33007c3e7448d4bcb86c50665a8a3e7c5f4e682ff52b2d9e1288f7b29da0d5f22cf897f9e82fd48ae606a7976232ea571f44627ff2ba646c68cf0337619faf17ad1d7dfb55e77011b258471a6808ee87e29661225e08b610412bec9000e91aeabed6e7c5fafee86423885a710860fa0f14c5919ea35843d68196d8fa1ce2b5a949baaaadaae57aaa0b7865020ee02af82ba46b1bc7bc2d51d8e4edd5142cf961776dea67e5cc330eb1c56d8fc00882981d204cea1db7d1a9c3be416b76c6a2813c2c56244aad9014f9a4427e34f7badfd53d558c22f0969451d89c441481e9028b9c08723f10900e2e7060ff0279cb609728a68ad61d15fb7c0ed520aa444a2e37e0ddc4667141b02b8f41fe52691dd5f56eafa4c0f508806c96e46fab4521ce6d5879dee2e5769a955c2617dcd0af92d661bf0bbaf35fbbe537d28fa92409a81bc176b5e96d3caae37a881d42939f604edc811f1d2bcc8e28195650421c9be33a9e0ee2d4aff8976eac28101adc72b75f3b0b34ce2c61dc378f762c42adfc0076125c56f20aafb7034dbb4f24858f98061d5b3b5e19af6d60db17867fbb8c4717e1daab912d403bb93fe521afb37a002428491faf587284785425d82f9ae5aad44e9a5e1de78e30409ddc2ba1205adde9e684387eeb7d4373912aefa435cde97ee4a355699e2e3427fc2da06b4e153050da6c95b776405a50158dfb58076d752f11996c9ac0d297b113d364182ae92fef8ad5f1ad8849d17f6e7468cf73e887294d04f8636de7b59ec8beb724ce0a4ffa385ad4cd65918205accefaa9a7831ba3081312b65db9f3e89b9d37bfce590e10aa1fa0c92cd752f53c53e1c7671650915515c0f0e261d04358b11eae7573dff44685e77f3b6c6fe2d1e311948991bf00cab740088b543fa15ab1d2dfc0a94b040610944edc0dd6364fe2e25954b82fb973c333a27bf3ba4c8fb1d9128040e53a87fa65d9525f222bbe37ddb9d36e063b5663b3e58f6be97d4f76ed83bb89d059f6082cfdc5bc80bcf2944524084211accb60f58bcb49c10746823a941fabe9eaf8bd2cf637e343395a95941d834bfbeb0b1c1af75562b5089818266a9d731236980d5a6aaeace790d714fad068ef2aeb7cc4ee869465de9e7190db58afe3f24c6438859499b83dfa98908005409da4c2b50e37ab0b1e2b5861e61523983c2337a22d2f48d33ea471965f569eaac9fe6c88b174fa511ff148bf2b978b607e25468a634763496db0a3641b5d960dc2c19edd5a2f2365a04e460e286e4603d22a38745e82d4b383de89fa53350166bc35adf261cec3fb6eccf65d11ccfe423665307ae3a265c631f9ac01b1334b4d0b6f06cd6ed69edf07f11fa7d8e34c4253a2521a20fb71f9aedb623848d073d5c22122597f77b0ba9d3883d81ee2179dc1d9ee112951f5aa6d507a145b81a12e22033105f092635d2320e135def40a726e75f598158a83c9cb3c9b126b788d6b68a769c28beee8290cfebd9c270a66953622bda9c3b0feb1109e61350fa880414b8fd85d073760c75f48911195afd6a90ee23b75c72aab370d803bdcf046f607d72e3e89de788873b83d1b6632148aa2aa50ab9b7236768db38bc7643ca11e31ce60b8285dde727d5383205e84284308d0df5e6593e4522372b392f44c2e49ef287b3532e63f77b8f4d982574eb28b27d98b8b508ac025b0921da9404888fc3a5999ce6ef0ce09d98bbdb3e86b5b40ced6164ac5bd7632b957261c6dd558b933c1cb23620c6ac461e186206f9eb1ede941b027f0b1924b389a03534f5d88039ade865d7779872be2656b9e6156a3c5dd3a805c806f6222299282bf66a8cca53e8340fe2d30732530691886a2987679d40e31a25ae5cfb6165c8b52e30e8468dd25e44d2255c82ef19a3b91486b1040acba04e6fd8d4408b4ee7d994b628fbea533aa6d45160de345db241a3911908f18919412f90b94f26c143dd8baa938b3dee731d53333f01d5bcae0e07ad22610a5f6e50155ee9bf3e6bf387b2576909054a583465ed98e3c908691896878840b4b24fe659b81c2a1701c75b9143326e7f264c3efd3d99d3992ec16a21fa25ebdb196aefe5a367bafb2515e8f0d4ecf8ace60888f4673aa7e836d976f401cd52ea37095856596efb2a31f567f6ce79c7faef1a87894066589085ab47f70eac35e285fa621a41ab94c39d1241a063d69d65ac0233bee9ed35c6ba4ccf459fec4453d25d5cab636320db036bf46eb8700e4c975a9b4b143ed545137334f8fc214331f07968de2483554c08918dc1b53f606abe22c26942f42abba14e45b18e1a6324efa2f6afdc273ac68bb803810ce3102a538036b7b242d98669d2b92744024137a26c0de28a305c1a0159f085945a665fb8b78694ad4419bd4fa491444bc88e5554c7ae4337b197078f4be1ca4df7c6f008a1cc53f8823165f384c41dbe24d9956666467f572dee7c2b53852ea0d66b44349c6426a684220e0e91da619586ca80d685e586dbb6dcf77f1a3d0c3d232ea99415a656b53dd9fbc63d8f6ddc7e876eb1282732e0e174eb1fae365a16392c5d4107ea27d9a62ca8498538ae68146861752e0da51b6a4cb9de584e05ca555319cc8fe7fdeaf676e5b3d849109d7b3bb1161641067d17da02f7e7a49d27ea558a66bd3ec20275e48d0f49caf7457c2b866f056497fef1c85ed7423a806c8cb1aa437e59525a80b5b4980840a6a44cb7ef61253f9d78b516c607d0dfe44eefc7daf1daef8da8a2e37005d3d32677c3a3e1d75a825403010fe923f75535eb962e9851b8c02d16f69d81369dbc191209fb51a7be37180f80f12b8e7bd48911297a1f1bb9c49b71df59487dddad74aaa26858027f935b97e7f94250ffda47449403947f1f828b345cece9c5c9fd9e2504cd58f07946713071fb2a49ddcd4bf61c1ac043623f0d8e1a37a4618a30c3d4d8c21cc225694a01441e6a2606520f0489cf6192384be1e8ef89601404f71d207c49d6278901956836aab7c03b1d0fafeb68a2fc15891508629a786d7391e73e46a41a780e2b3655679ee6acf0781dd08a5d43bf84488d2241398f6f19ce59163dedae9a410db8dce4e3796d1c28eae0cc812aca28a2691bfd6541bbd681c31594a325421a2511e1348742f55bae789593109bdaa6f683ff7e028f97227e3ec77cffb8e37a07a63998b350e170c82932fe258dce45cbd80ff31770a195bed24e63c795873895794e509a0535fee5413450330cfc518344fef709492538040ceef32099a1f9bea14aabc8d0b6d95379390b5cd8f6c236f3c17b5fb0abbe02711ca03dee6276dd5a0f8afa08a55c28f2b4702206d0fc07f6ec15b3883536e8a6dc3338734124017694016624011d547ca20ae30ecca27193ff6559af077478a9a2e345c05b46a60fd91bcfc3fcee3580b33df90c860ed01daf01dcc07e524c0ca3df6dc55fe7e36819c031fd2c0a2e87447ed289d8ea268f6fd4ac0a00b6806f994ee3989654c7cbdd972ce5cde3643bed923b141b3287c0e0e9698afbb2fc84d1e69d92bed5b07c70d1be9c12155075936cb581e320847399c612c6eca434f1af32b31ed86b07a2b00a48a07e940aa44916404831c1a3f03650e905ad9c12b2f2f1a6f992aad570f52f698f018aa4c5e1af918c20ce338ee5d3e2e86f00d38c4558b879c50ec90716b8c676e0e4576832a2cec4210a5b4c47a8812991c808c95d90191948a51391c54ec7d1c639ac60661320ea1c6edf13b0d84c2c10c1642bc484eea9e70f4cf5658a3a0461910ee038e0f0e36f14871c7380060b4627b6f6b6b9f964aabbff06f4422298fc11a347bb54b421d2ee0e7a412454c9f05c043321e99a2a676c54764427979826c4c07aaf6fe87950e888c912ed0c6d3777542921eb411c1222b25cdb3af79fc99f99a872acfebfa5a18ab4e148c8b74e2607db484184e52019b5598690f043b8525ec0973a5dedf2eb9aaf844d79b12c22059897bc01c57216f427bd5b9619a516a58c01a8dd5a56f26444e96192acccb9e43c32bd3068fc33b39650d33a5479d480689fcdae48cb1e6d0b8bd13888b1c509ff6cf81f2a7626c7a0a167583e84560cb683700623a4f1094eb37c0912b0224e88d10387a4abd182c83d025f0b33126c940fda05f6ecfe14a6371c3a7ee877eac3218224e50087ec4854f7568ccb13d7d015bcb40128c09d6e556d019f50d0fbd2c985c7cfac6580f0e0276ad932d36e8f2607cd96b271d1f5dc6a09dccb8ccb2d87a7b244435fe48cf3043aef6fd2fadb08dd04f403148f9e8e9b7524474966bdb47af90db9604fb4f20874e475b12d7b97190a5231f52ad1dd6f00e078370f942748f698ef5c184920fc9faaae1a30720760b2c731c1edff0a22938d8b1ba8285387fc6d7cc827d147bf67c6b50f770ea1a4c96b2ed61bed0f1fd9039f875e798836b99085b049f2a2edf3b78791530ced18a6eb2a67260e8d4b80f5c9af0c1f5fafc42e0500c1bed72272bf3a195acae573c431fa7fa059748ff7d8c0ca13f636fb4cc1fe1f48e755efef38d0b24b3f4c5553ca86f68e6d7263f1223ec89170e43bfe5af6765970774386c8ba2b21070f411aa472b96eb150fce65bba89b9621860fcb7089f7dfb76bac2ccbabffd8821b25fc1b1b5ef8869e222b08ae129982477a5081c10063b98d19a1b9d1fda8b21ff87cc319d54b5255847095daef22b9fbd047485d888351fa55ea8224e324894288cf78d391199fc995d45d7e6893f81601be95722fc0576d3dbcf8edad4b416633563798913159e63a9dd9c3af36379aa5845abee73df8e7833327f95155e3385cb81815e0e42d440829b722e60995489dc65f97c4bcd81df8d5b33a9ba4114e59eddb4138e6e91a788388185e87e38d384b5c8e01501946ef5923365104022940fe02483284aaeaa57030bdc30440ab63b6196471553737e2fd5f88fb6ada1e5b63a6ed3fed66a1c7874c5a2fa721f9a25d04f1980726f4d46617f799d0474b454d49b331716dcfd8ae594a8eed1c5cf0ecf7823908ef8f62376ac51458def579da5a4a65ef64bcf43e751dfd6032fd33fa55644119c1a3881b1693631bc49660dc132d2f9323df0a613934920838b9ce2b9b59e5bffc265f6cb0c365b437550f1888a0b4c12b36b15e449191700d2305142eb799fa4d425eead2131209a9507a91be5e914f209b688f8a7c47bb9b394add49da78dec88a68d96c23ed6f3a170975d5ce26a5a3aaf7988b732a2d4c54e27297529ce2368fa30e19117a825571161d0f0a51d6a9fcae01a9ee255deecfa0de0f31a00f75a1aca0c25050649933907e8eeca62d107d6d25a5be77303628663980bb6d21875a7b00b7d98b280d14d003b2fb602714a123362c3521480387d1546cc0dfe86d4597620c8d0a68d94cf78f3224ed4d25f532c28014c00a39b36a2e5adffcd0039469cd23794f1102417877719fac5e4dea22d24e23546ae2786fad8ced4cb63fa2a4bf26239a8516c203ee70b9157e4ed9700af344f5042ef1738275b682ad31d063850ad35b83d5f00dc9301ea8bd2019e7818bafd77c32ccfe5e467011286e684336626e081ba3b68001ec86466872bdd6884a172515bd7b2abc388bb329ec40daebe65cf64de274f672e96af8823619989d3229901b093710225cc4a6f1c33eabd8b4273ea1bd8a6467182c00ab22be6e1986abf9b2034a2ba5aeca2a4e83d4818b78d8dc2846304230db225918610974f1a31afa9744505365897f7169d058edeb46835b64fbb83616edeb20bc46ae07beef96ab1490f556b5be58f4720e35e886924044e427248122baf22062a4c2955d59217a13fd63026e39ecf68bbb7a68c7825d748b1675ab18521727db119eb96020e1cdc87ee6fda5b6f93d49f25d6d5c041d0a0f914715a5d58298807fb9db081ef62b5b1bae7d779c9545b5bfc2ded27daf7cc84ac5a448a70a5831b20f591dcbf6a330de372371cc9a9e173237f6f3a802a231a795805c01b9c627dcc4bae234a4da36c8303327bd9493ad09c423f47273bc2fd868f2a3dd0e3f50755266c368ae168676cd1129f44e65779d1563141864b0dfd936ff2d0f56cdf7169699fbdf9166ba2054acb2da2c767fce08ae6b0b04810573c1e6e72ae1dec4206c9140bafd43477a0c3d83b97743f6d5611869139937f3ef81d04ec2920d14adf5df0992e19f8046c30366983d6a7f16a60059b8dcb45023297f07e1d48da81906fc080c21a6c5414b9205838a8b1cad42cf662799672666b0165adb909aaca45243cb75c2cf1da22e704f876c1c30b8b7eb3baa4fffc2f6a77d6b42852f84097deed43b9da02ce315a9612fcc2ca6c99d23db7e79ceb28157aad8fb856328e32dac20402d20eab633ea273317dbc7cc686150920294c001b53f7dd06420f9acdff66931e7a99311724d9c7de8517d2a488e7a78bd748cee6f9138f8183aafbc42f1ca35a61778757423232dac9093229672f9c2b851e4d7ee93ad1114977a2ced8a482b11685f0599426ad6a26b5a8ec20571ee617965e75d3202d9edd44e330edf4583b8c6c2334a41c626f97dd1b9fafe6ed18fffc9d0eabe4539b79c4d75ea2d952891f818f62338054d15b21939786d91d39267fff612d4a123bf945a58c705c40e33db1906c2679fbbdc823b031af09a2cf1a0e87c58d182063efd5a661357fb82d44061789b341f6cb42fd5892ab9369aba30ae692a3cf8e9a72b9c57eac1f4ec5f22d117e92bec90b5875946fd160b4ed0a6d5ee982f093d7a5e2832a023fb0fca305bac92bdf2f40299dc43b0f5ff89bc732e7d2837c4af54dffc02da84ebb7e0cf9998a2f9941d2d4b2a629607555060c9833c6633a31ef262ff531c115bd56515c821b03e7b948b16f3182b8e0458f885583ef5394388cec27a3e52f71a85dd01110c9150fff81887fc5196bb12181c566fb9189b06baeda18884e77055d413fc22375d82807d1e0f0cdf66127e8615f9e569229f8250187d5536b4845d9639813448eb92d1aedf7cca00c5a602de42132a292024a979415f77f1d2130a5096fb56cd1f541538eaae05024e5b95dc4e0a78edd93d1e0b8310a2863d14b09dd12f763c5f0faf5d11720ddcf9df890ca075c2b44b6ef41f0f79989113cc4cb8bd2d9417edeecc49436cd598ec02e9099b58847b9a8b0134267f4893102b6f0ed68a7d2d8913c922c1b685ebfdac222d404acd69c531e684c426585e5d168f7dd5a24e81b7c7cc3e6652c6fa0f04380e4bcb2e5402dc6bdbabb4893a139d424e75f0ded10d9bc78ddefd5df6f4d70420641bfe53a71d26af3226ff456706a0993caf7e3fd84a40ae42b32e00f025cd7250219d57aa809212132ad712d9169c777b8e139771aa3cb1afa01357c79a5fdfded3ca41196b7da0aa35cab171530dced840043d5c3ca906ecba2cb0ef5c361c2ca2be10825b25a7d3391800d91df2a4a6bc8aed63091bfa247a26b1efac74316030b1a6dc17e677d2519765b5d9ce8e19eded28397b17805b47de16daa1d0f6798fc4af51ed19d309d531c7c54b83d9ac05019c995c1323bdb1a39e1278bea1a072e896b12ff321b54236b2015c1a4530c8ac0d4a92484f867cef13426157be4751c52fa955339b76806571d4049f11afa1195e7598410fa28fe79e6cc8a57ba33231958e1e9c5728290ea046ca4760d69be01f1b7498c0142bd41fb4de8186f4a03bdbf0baad3aba7012847de9f1def7292c43998f189df46d1b54f6307a53791ab69179fc8c7506eacd3c43209dbf752b1a2f9d37c983f33533e28c0c215922a275a606e0374a9bd0908e35d2e95cf422628b7f1189031d4380f6597723390940e21907dd138de0a60e525e4448ea3200ac6d48d465f0c6d2203a0272e822ff2f1fc6c5f6facb77fdce86e69f9155119d5d4c030834758866389ec93f27e3fb353e7bcd18d4e630fcad789e5da3e6e898dd3d3c579016ee2ed7baf1b662b7a13274522adca9ab848c1254c9d13b0e7f12b7c96b001ffa5362693e33ed9309b26f5553f31dbd8a4be9a2d4ddd1a3310bcd18371a22ee9d1745b7eed14483d503f78494788fc84b6b8a379af9f6bbdd50015bfbc91c2af8bcacbc920fd442fed16b007e30d2b2feb18ae3d8d495c5323798ffb2bb00142eb124f2bf739d11dc0af65921eab11bb460f4dd3fa18ab72d3cd86b1d0ffd17d2f2a5c0f66965689d374047d87cd2915368eb2b9369dc2b4629a002a8e658fb01b3442813ce43864c67fca1ff15133c73e4e050368cd89f673805af140839c30c1356e78b6bacf76d86cd148acfdfcedadb4e1fab960ef32120f33b32bc2b8f5e1a9bfe032944c8852fba2e345c06149ef8d574f4971f068e5745912be54e6e43041f1a69d4676d501feec6a2da1b46b16c70b86316d6e05a7fd865cd754b3a145ff3fd0214bb46fd9af22616e650567491e847f6ce457d2e84445216beb8400901e07fdcbca6aa03068fbdafecaaa0cbcb8a937f07d0e5e7e4d80ef38bdca3ffcd6fa18859cae84f5b88f511509f5327f371f3a25293f247cfceb1fc9be3585a9661c1aa7922c0bce3cac9f4fb9221b0d94a7770fb472d125aabed031b0b212f9b597dfcbb482c2cf79db5a8fb3be1d9b2fa1ef8a24d8758a4fefe226306a01ada0ecd1168ef1d7221c47658fc8b741a9c926bbf9bac48ed8253e0467b1c736d5b2aec8d32301778f1fbd246c7b495dd7a7860d359d555225d8015eb8a23f27253385866f690b8b2eac9fd167562faa5579092ecaf4709a216ea412304d21ad9e6136a229efd097981fde403a60c4aee94fe7405d79511c4e73d57b34c278c6c46292164f9458e0317c2fea1e90e639e4de4004237a84b5e412b89f191e6ca2370816aabe3c05aaf2f6738e5a5ad9593f84d58be882a135e65f0c0494271e0bced7d654406ffd39f317b2ae656e1c1e15bf42e8745636bd75bafca231c5d9f9e9ea9a2e50a11c8e0ca483bc85cd3395ac8fa0a5dbeadba478668cf0ae60465c2d2a2e0b301993b3e8e7bf9aa12c19a1b447c2a322c7a760b7be7a5ccbbc9f32bb7e72220ad7856ea3f0293d15800fcae3117360fb8b4fffa5dc88625caf29e5705eca9d1efd0aa1cac24266aea4cedc84f19b1e71e81f3d48c8bc5ff7044cd6700d09124de375cc4d452d06c2af1a92985d560ddb56f21841ee9740b1e316801e5a2d763045693138a3a5ac197131d796c2ae337c7a957d79d14a4f301b63eb03b3f5e09885badf067ce718c8c3d7d95decfaa9ff0ba7ae4d80eb4aaf62d93121eb796b060aeb540b2d7e59955715fcd39b6cc108f88a9d0c45c76a01959fd23a8c7573b7a35513c624101bbbd3277e643e71751f0f16e63cac799fcac3b068b8801b49a27fbec48ee8d1de2319b99c88488d22046278cc60d1112c957b968b6d4d2f0ee99785058396eceebbad709800fd8bfaf4991cc87e5b2396fa28ad31c24436cbc8ccc8b42ec10fd809709eaf2da91035d5a745d08f1884a59a0ac231b3687e2d1a03392bdac458c1a34bf3eef0e79790b46f4e0cf82c9d575804ac78cd88a667ee61e99afc6b142329dc7399a9b0afe8ac8624a5fae60bc33eb8ed601d25a750de323d5e590d72a68f6932860b66478bfa1dd452300416bb1671145f16323226b5235248cf0d9074b8d1e9a1ea7093c8bf01d3f51238a00cac0ac732749b61f8b2c6a601926ada48428760a16b83aa478f22a5f36913178e3952b6318aa8c59904866982dad624bd6a95da5f322797346bbfc2e3e8c3d33ee86b9833202b12812e4965bdcefe3819feb71cc9330bfd3bd4488c4dac10c0b4bb6dc9085b621d2d4c5f556247d09f60c652b7194bc3e6fe1281ed7d8942bb4903bca16c1076215aff135c5f02d10dd9409bb93c3a1305bcc56fd107f7217b335f293e39326e5bf6137fab1472165c93646971093ed8425b99c015624d4d091e01d61e537a3b5d975d38a119ea97aefa3ead70c3cbd32250718668fa29b874d44299ca585600d37ce96f4d9d6f26c51584a612bf2432d5a8eba28a496b5e67a12ec3ac400c491e6d26c067615356a74ab8b13c46e9ecfcd72def60723eddfb4cac2929b6573ae3efd377c0808ea1cec89ce6628dcd40a637988a0bdb8f1b06d9a89b84af27bdf9068149fcabbb33f4b77e368cc0aacd771a3eeb6f303c324999ece3fa11007d3511ee673552d9fa3816fffd5767e5aa6170b47adfe01e3d8295b7c30edbbe26f561ebbdcc1ee3c3ee36bfa3c3a05b2792d641725a9632251fccad18ae2661e99e966a89535e47c4a919fe850c9a407e26abf9493cad0bb817c05a371a592d4b69015aaf1889ea57cb01cde10fb1d26edc0e26aff546ebb8e366a73700f51e72df2a0c3ce6ea02e361ed7571dd71bbde4f02384008c0b276929ed0584d63c97dd1a40ff67688928b84d067cfd6439e271cc8e1db12c3c871ad0e3987cced8c8de6ddab64648330eb6887c213dfa09b307eca70ab3592b47071e936276e2cd167695349db7c6f66eababd9a1a73732d702f29ca35d25d667354e2f7f111f0ad76b511e29dadf60e7c1bc4cc5160dad872dae4a2ad4f082a789f4dc213ccafd96ce269f2af1f970a019ef740b6c6e92f0a35da1837708092092581a8f7e27f86152ea39a53bf57750007aed4cd490a5cfb5e5cbe9c880d994a2949baaaaaa1053ad7bb88563aa0c2e0092e2b58b89ad012c0afacd13e708e1c13799ad142519ef2afe21b18d04c9e920bc2967bdbbcec7c22dee0c3ef6140a1c77050fdb8978930c9a05014ea4ed542710202ce84f166221e1b360738325b495853b23276875b7064267fc140347a7e865ba7a5247c63d2681814596e02cfa0d08ebf793196f0ed34e66b849a1202c4bc5c07df793c7fd4012726a74522739ff5600451d2f196372f3c604fa90043d2acf63d90b6ae54e6739789b88ee62ac564b962a5cb6aaf37e52cbb784f536c909f4a559f78e4c534363d5da267928e795ad2bbb4312d6b7d2ece7e48b7df9f945cd5179879ecfb35e080c7b9bcf4ab4187e3cc864e7a2abae244364dff50f7556ee5ca1e361ada770d5587cd0943a17e321160a1d6a7801c34985f55f6cf1e0dcc3a932e6d4b70ea598fb6af3ddc5968677efb560e63013250d540903c80468c41236720cde8e34fa342df5ea65ff0590f771df533747ecae255f17e2c0c10f7e7094769132aef8dda4378713ff2bba1e26c330e73dc0083ec9e0b118901c2f0d88233251de99724da97d26fcd332fb2798ab6d18bb8adb1ee99753b1c33dee5c3f4f0b141296714525b55c7de42549276fba5aed04780bf302daf4510b65262cd9562fbec3d294f0101417c020858880985d0288133568990947cad488d9817f05f8cf00e234f62082c89210044238d824dcf0b79a56edbab259cad7f0fcf412fca6ce993a5476702da107cd407d3b487ac7dff3f7a60bb21c49698dba70b68b62228f74a4517196868e7a7fbad82031cb02f6a9a21afc99d0031e014c572721c986a0fdb5fb3c2f92e8d19bcfd6aa3782687203915078af8fd4b513e30d585668799c3d7bf0b3afacb53c5839f92e056e51d34c6feb204e47982c22cb14e1fd9325a41839ce38ceeceda0b98d3d0fbbd4159648bad94f1fa171d236505df91c908826bdd127fe221413e35db3277d768e7efb56dc1f638c5e6f8e472639ab4a0dd0ffd702eaf9b20c9ed0303537b3cffc600c1d8db00a39ed3e37e3989ee0ac39ce922ac7e72e5534104e5460b987982bb186e67dbd33d49888f4958f671550f725f39b9861450762f5e73fde1c2a91de4cc38b3be3ab598cd4078b1e636b2230fbfdefc50c3088f7001faff14a908f6bac2d64587afe0697f5c2ab201b2f425012ec1e87910305782f51b2fd78a59dd97eb1aa229ee0d7324bb277047d8f9e24587398a39dbb70cbaf4f8e28b73748c7d9d333c72bf5e72d8fa036f82606d9709e04a82e02fe5fc36726996ae4c2d41f16d923550b6fe6f033243a8b3634b38c5b9719c38867d779c9787e4016a0b07c059a43921ee63331fb32ee1d70b9eb979d894f6228845bf2b06b12855c32a6a42e4014d7360119ef98802295f4508c85bd927569a3810352ccae0241046a98cecc813c51d6667893c7bfc9b6c890d49099acb2ef84221704045f2bacd2e3fa0dba1f2cbaa917922538989a3600e0f54140d1a13dc73da94fb8c655f215514c7308361cd06e1a55a2c4daca589296ec84ec07c690c3e16ce323cca0b3ee3ef3c0887f49ec5740df9dc1a91135b81d935811a9265845e0721b0394c419fe45b9228d88f22061c401f85b02a6a00b31f5453945d22f611186e76af3e8cb41c4f96bc5ebc228ec45a5b6476c01c24ad984695c277579fbae56b80bf4d84d441b583bacc70847a902c26ac8404dc1ae71c4930477b088f708d9fe4115d49dd6050e1e678419a7509fb487e9f3ff2c698aca274b0cd18c2f981b4a2c32caff438e48949430201f81bfebed03e0f4dbf3b995cd500330cc3e04a6596e2e5cc5d7769c44684071a6357711fff77d0a6fe95d4ae85ab17d0a66278b67c25e3f05029ca857fc29e5cdcc684f483ccfea54b54ee0765ca382434d802b00683ce089746f25e20655bc96a9ace791184cb62d023dfde9b7c7c47aa6063c1561156563ad475e6951c11767ae34c6b2104ec5da430682b1214debfe9f57b2105b92fd73de35806088780ff8420b639e41cb6715b4921476dee8a3afd4790aac02129981d8124cad1eadbc083433171f193da0e5dddc484581d1e46da282dc105e5b81494d13a92b331c13bf20de8f8d1a75985245b63d38cd91f0460711036058d60a3391a4574d695db2d76b702290778e4cf78962f0acef7b51be742660df27e5785e1a9d9d819f42fa69c3e22db230cdc2c9e20bdb2c468d6587c1b8328b14bc192a6311ce4489e5e4aa045da7170c77284ea423dff0df986482ad8d8a158d4150ab527bcb6d81248b3e3fe1ce8d8e6332972e51cbffad85b017f1b4f181b1cebce36477aae89a7e3598f8aeb6fb3bc3e76192468074ce4b2ebe2d232bd9d38da14cf662490467778ea4de7fd97c63e90117084b9373d075831e2c7892b90afbdd8c98ec863d0bf2e466266e77086a5179f0157862175c4e0558c266797d6a76e8c7e7871993e4520e4375770fcc3e1732b01029a25df824a582cd3d2a68420613de7e8660ea0c04b895407928eb1da8ee33fe5864146b708e71eb3912020b82522f9b949a8358e7276c986e91582606bd7b7cff7401ea15028e18e2de92305f8356bc39935aecc00b65d61e9a0bd8e7844696de14a254d39ed8068ff09fa15bdfe167b0b71624b255ad5d9cdcc96a6e1fd2316e87cae20599bf1b027ad4f68286d1a400df2794da43702091e47571fd4adf8815130896e4859e1fe9e1d40817ad03b6853a79041244070ae3ff174ee860849e054c2e132a883abbd2110dbbccaf8e037598e84648b9d1d3ac7cc23efb2bf3cd3f0712477efb66dd0cc0339c1d60bfc0a3afdf866e55d709465c990f17d6b75f737a49a98884d442899ea328143a45f9a14f8e16b781f84245a1184dab8cb5a1d70aabb5e987ed275c3fcf0e37d64936b3500b11e035d2ed7f3b883a68e0d0155d2657e2f995b43d8ccae714888895b6523b58914b5027d6078f0159a54fa500b987bcfc12b294e7c7b633ec29ef05667b243219fb536c3da3c12583c9987e8ccacd642eb76578baf0e3d17c300fd583fb797a6efb8384fdf6e1f65aa48fc34fd5f7874650392e5e86a20a418b28b1421f0be3c34faaf4252d5ea2e637d527a8ebe525eede490d9aff085910c67884a81cf682d8004bd5b60b7b3c8aab65f7c087ed757e29f5b984db7776ccda1cdb71fcedaf9f92d1eab6c362948593b4f198c8fa6a03ed7512e61a4c3a702833833a5bdad9f1495157c15ba5493115082f3f4cf4e37223360945676eac219affd028bc9ab2d013350901dc70e0b8e3878030e4b411c4cafd30d4e2cc27b88b32257e29c1d5de237ecd52dbff9417811b5c8290a81e4ca807627c7312aa40d966d343aa011612ec4309b509273a156d3dff6f50f762048112bc040496f63e7c6736cc982e6e79e631a2996950fdcc7a58d6cba70c5aa81cfca02f041ed05af69c48d5e2e2f1cd0db2d7b90c8fb10cfae380dc75d78502f2bd740abbe8bd9245490d5492b9be1b95cb4150588606be97de5e090d0495d5d1b18f4c54c9e5e5f3c027bb713df2332f4d2c61e67abc3903dc1439b14fab9ddec9cab735d6de84f24fe553dc133a07cfc4962980ac8f01399fa8caee1cfa46f968b76bfe9e00ce6c345b9657c669ea6b4124d629f829c47d995ec4fdc6d858d8973ebda50e29aaa011c82f4205aa82a49a0cf20a2b7d09e5907a1bee8f6071ac2ba5cfdc9d3c68fe7e7e455659d69a7660b5382b7cbced79bdb84f7d8fcb93a9aba8519f142110033c5ce6005d0c4f4ba33e9f419b9b8eb592b2d679bcabd14da795e7c049c8c2c691d8403bb58fb52569f679907dff77015466adb130ecfb42524a20fe798eb2d00378554e08ccb7fe7a73c714fdf4d9b17d025c61f6273997402b1888d174b206347133a07a390dd4ddb7cf5dfe0ff45de82783157936834db63e91780ad52ee8820349ee74f0d3bcf4be3eca135253f37c7516c1b18dcb88fb52db97a0c1f4e6960fcf06cf7bc0a976ed9c99e0763b0704ef962159ccf0fc4646598aac37ac723859a8896a8659f1d0a764474d87bba4195a04dd7a7aa1b2417144c4ba56840422c8021b187f5cf7580405e2919b9d2ffc92f97aa661d509bc9bf700ea43c0dba0f317094dad0c7df7a2b86439cfd1ea558229d16b4b4b82a059e9149fb73d2668e2a4f716057e95d5072ab3db79060603cbca668efdf65d07bf7e5813101fa10fc86e9031f4e38052999799b84a6a7f54f210f8fd19121a39c8301fda4824415f634e2b565b507ea193388f96a3f2822b61714252a8826946278b46fac403eb0ad539882cf51d5ee96f7c07affc1bdfd12bfd86eff0514a231bceb524afd4de4b5e191fe013bfc32bfb8deff0957fe3777ca5bff11dbdf26ff88eafe437bec307253d8d535d8e11fda074c997677931b3b2533a1b47a53ebb3be9bcbf0585cb503bfab7829a466b064054ac306e463d5627a07890fc75efae75540d7148962f386bca17c8bd8d9c30222b472acc42caf11c961f30622aed4741d842d9b2ca970c076225539a0e4c59a14d66b0f681b7a8ee8828abd19e8abce4952eaddd57db00c2e5af09d5dc70a4ad1b5684ffbab86df0b78db0b57b6d65fff9e9dae50925e9a216acbf401f9d146ff975357932ce6084e647a5aaee43b9a28ed1b9ee725348224ecbd9a855ab7f7708da5171d42d02b2ad1e757387cd246cab3fd0b2d26e0cb4c026110fa075f882ed07a1f6cbf08deb34cef06bbe3847167fc422eed8de2af69a4734e0fa0fb04a3428fdfd933af283f1caac1653099f48735d108b6d74ab63788c368440edd48da6cabd9b9c9fc7cec91c017ac13163e18f19ab6c9f5aed416e9cf89eae41ae3404bae8806be8fd32bda5426081fdec403d29f72dce39de152aa637a9a03d8b26dc385f7370a9adf8dd4b2ac4221c4fab03eaa0125485cf9b4cd45bf8fedf92a0078411c32a47cef0996ec963f481df61fce10db17dda1b785e50ce218b707596203ad9ce5542e18835cd9aa2d146606d73d1cc0fb242f940b9e5cde02b5bdf0f1810504c465666fe2c3de41b5477612e8b8dd80c17840cb7a8ba68416713cba16826e9c2a0c5d0492e5e7294614bcfb4a0aedb8f22fd3b9fbac65d79098a467be1b8d03aa29284f1f831958f33b14603ff2d57ae99a4e1339f7e01424c3336d36a19bdcd0126fdbcac769cb1d57a6d8cc042f1d9392f1935985585bd1db7e403b025bee7399b9c76441f22bc397b20bed5308d778f75d41969d8f98f14a66ab277fea6db72644af1f19dc2f3fd41710d7ac560254fafa0482bf0167c989e029b169984c6d91a2660f989544c3cf98275240a858eea10cf82fc159a7a41a65cbb91ec370fd2ca33b1948f991863ba3a7ddf6906cb825d364acb7c4ed9f41ab5bfb7c3a099c0eb71f7f79a293bd6999ac59ac2d267f0c7110b55505fcc6fecd82ab1b0a55c239b300c06045895a56abca914bcfa901387a3c879080a1c0671da48ddfc7e8366de8e0b079f20e7c37f190456a162cb785a94c9dfe7c3f9c81456895d0cab307f3ad7a1e2692f3822882e4600212cc2787d91a997ff6d811f604c5e7e6f6ea00ab0c12652fcefdc5e91d7e5f233d02fafa0588bbef7e4ea46bfc8e9970ea361f3a0eab0ca34ffd1d36d607c12e55855c6cd1c67cd5f887902fe09154f5fbf389f7651695b3ccae8f31cbd67b608f2f65b8938d2b065fb0039fecb5ceb805582a26d97f7efe449f29b7f21dd10be609e355f49a1e5e45bf56ae58d1e98113cb7334f6eb5cee6d4dddf0c8822957abcea5c08d690b9eac7c28e0fd1d11a493309b588e35c01b1c170ab02a676d563ecda286a5686b9263749040503a44008c4e4150cd5c9fcc80a78a68c52a85d1025b709786fdaa75b4b12a181f73e0a8e40f1fe56ff3bcec04599ca85797d664ce101de06eec1c2cc81287a748a3dea7a28a8e371bab6215038536fdc6ea61b29c68c0ac5995291c25022423cb5c8dc56f143e286405d050e74879fadbe43b8eefcd99e52b93861e1f348a527eed063152609169a38d22694e5c61ac097187dbf9322c931379e3ffe1d27bd9caa3381680a7c5a7ee7133bf4871d39b80b645e838f95a3678224035961f74cc616203e695a9587809070beafd020484a695c90288d6b9dfa3c162302226833e7b95d188e8a267d2ac29824edc408c9f6a029cf838ed41f45cbded7fb3935afdcfa3e4962dfa31595d34bdaa12bb67fdea638d0c236b3876bff6571f1b3a466309212c715830a2a4fad95390baa6b862f4c348f57157fc617400e1ca271ff325c4e90e6926c15c8f44e8a75579449d1185273060e759817f183f7def8f312d2e0e58cfde81fd6e946de5bba0e9dc3293454484550d0350c1d3ae2249d9dab18a36b0c5d1984d948a932cf3bd2bffd1f9dfa168fe82deb866e426404803005fd9587f9e07f9873c21bcc8199bedd19cb68994d1c81f81b40ebc05a5d1e3343352fa35afdca910d53f9d1567f1c4b8be3d045ea9d6edac8f99c12e80f6c0518e52c5b371500a0a39c0717018a6ca71a466f0dfef222865c8d2047322c84b7ed50a71cbc83deba432cbc6dc7a6a5239f49f09c5ac2a682f600ef98ead5b4dd00d0729ba5c21c471af8ec1214b6d2e5a8b40a0b359342f358343c8229696543f330763d24fc22e531e4a93bf5450b45e99e7ec3c7de866c9fd086d0aa8aaf17d08b2815a53f09f4e9045e138f513b9a31b91a9145ec9fa5615eb4f5610639ef3700ba8134a0132c78ea3e55db63c452df4af4a80f5efef6edbca5c1fe3fa1bfcbf14c4fdbcb719a1fa255aedf5d6e7e392a73f0bfb67b5c5449aded6e289f518277583d9a493a45c7fdfd41d4c257ada98d1acd30aad63fc97a9c23b0fbfc3ea1e179393311001adc3e79d4012834aeb8ebe96d1b2c4eaa27a44145e21ebb00fbba11316eac520ef5062e783a930bcc5e562e655e04e98706ac87ca8d2b6f267fd5db0151ae00d3e1ef4e85fae4c29c0c4b1495fccf93fb305399da6534a3baa4fadc4872155c7a9a847ac2b6708fa6afe1889f5a088c4bc062e788fefc22640990bceb94001d10fe50d8d5877b21a90b732140e20573737f37a524eec4ae883a8588d4575941bfcbd04ddc63425ba93b040180d672c10292e1d094fe9696f92311e539b6d84cd7856d075b5eed629ce47db5055f0e5c8bdc30595ebdaa649c6a4288e58c833fb8fb07bc9ab3a09053b278cbfc54ff01054dfa23e1a1e2525fdae9b90a92e07d9ff52057783777ba472938a9c44ca0750ef27c962968e0da96f1ab2977c44881d64ea50651132f2eddf6005dd23828e64880e666f1dbab84c6d592eddbc7b0d6409bcc4ee2437ac359c7593d5aa0f106d333e6c4c68c5356686a560165a9fe49efdaea9e634a2a3fbb439641c2d6eea45bbb835ba9979a69a5a7c99d901cea06af48b62a36c918fa8856cd7a43c18e5095500a498723032d6eb8caea5b3eaddcc33d83cf8308f4133657d3198c8543bcecaec5f6618e6b5fb07f6e94373214398d6dffecf75cd37ba20ca28981382792256ddb5c6bfd8d2eca105f3e9cb263205b7ce7fdcc49ba0db3fee0800fb5b372482b4e84a5d06a530d6dc01accd34d2ec56e0f8ecd1512451d909fae89ece8a84e505c793939fb009742e524022c0203568597e32280cedc7c655aff12a95251f6eea6b51ba6813021be79024db8f4fe0c4cb45d9cfd03f69477730cfb1d10c586aa0bb8db3e088306288ad364cd764b9b342c5af58f5bf72e04ae59a9ad08a596b6b252f7985672f9c5fb28a9d614e10b5a4082c9b280fb5c6a656c8d1bd7b9db9a12ff67bff71d5135b2296f8df4217004b9afb120cad29d6f701467a6dea1aa4028d10643e1796046c86b0ee5dadc49c82466199a3de64a3df87d2dc5bba585d5d5e8736d0c4819bc988d68dd82ffe76554cdaebca15a646eca36f5ea3c374337ee40cd559c7ad5e4db99eb0b61ee9717e473b3ef5ca6a3d58fb833f8f68c440e8f1502c519482b015636934fe73c1762c63f49bf88aad5dce4484aa2af0cf22991e2edad289dca97e37ccd3b6fa0569d9ab2adbd3a20df292d8f1a0cf5913b70cbdba135ccb4c82434eef32a266d588e8be0d0aa3a3f2e3674e69e18c369497942be4d545a64ca930253a4b8c3eaf8d505c3423cafbf58fdf73d741079a5ec62497f836cfcf2972d0df03f44435ab31ff75d45b05dfeeb77ac5733d253d294c8400f7751c6c32138bb950fec1c52bfc76303b71b8e64c21f1796cbcbcff9e630fef57258048789f40ea74abdc9a58b19ef305dce66781c2d1ef36cfdd91990bfb26f3096ed866b0bf3187c8d802f4e5c039db4cb480a8b5ddc4ee533135d4dca3bfd9d9c7cdf89104467c02757de71190274f9c828da3fdb2a83c82889d0a52b894dce48b5488a338b6258f5c7c2a758355a6c4740908e5565ad78e1d5ae6cf1dc74b2cb43efd9b695744c9a043fe4f6f12f5012bb37cc2c843ed81c2866e720939d9b7847088dd0b2e53c3d5f04ea9cda66a157c64ccbcc35204dff8e10582211dc8b30bfc27fc9d78d05c27e30e0a404e77caab41764ed3f0710b96398e878a3b4cd291e60c34bf7ed48585af192862f5fbed028a5fc008661ab6635f4e2ca60a3de235676dcdc12804dad9fcacf46be92491d1c0ab2a77c5c55f412abd66c4313f75869feac926edab0adf4d9c72287857b747430146889f9702f6636338f2e4d28d4064efc66204750148d449ce29c10660e72ee2ada1c3a82963055a3dd5cd1946a260fbb940378e2caf739976c92306415988e6f9ca4f3fbc1ab10f8c97a648ed7c515cf44efebc52d3ae6353e3df043d3ee71f62a281acb43af5306c9a91c2b3e7fa1face3cc617700719280289ce70176a6b11630ae14698a89283e497669cd2130a207363988336fb6290ac69131dc5e5995f0d0a7a2535e6103720adef656e3330aa5c0f00af4ffdaecd472e9e531d46b4842ac37143fff915c8907232bcc54475c6bbff3b0c82164511ba304e69e94f5822228cb5301563fa9be4863fb6d8d50637f91664238740987441969cdbd83b8dfb9e6acbe11229bcf443e26760c39141e3be16c91425da977bf2affdeaf67054718e93235abb1197ff701d1f5d530f375bc1ae000ea741a54d664bd104ccc41a9645f10cd0dda83f4be35923c36e985994086419377e08140c8f1d7deca312f3f459cdc0ab2210a53af3b523a641d574042ae8c25271fa80d026aa0d41b82797e76cbad46af60251354f055ad4b1cf085f307af14b070b9a0aa2467b860481fb8a8867be805023cdea52aa96d00c728223b74da3d11deab4eafe53d65434d94dc7cd64041dc327a2e232fff815abfd951476c343e050c64ef40fcf4ca825a255b6253ac74383fbfc2fb0128653b003d6e92f27e85ea9c579967c40cd38b72758fa7f17e499779ae7974e66149ae2b931b6f2541b432fcc998f020e92f823baa0ce3e3ae15cf00cf0406ee6da290e9b45c6c0272875d1497f9e56aed3e738fb41c2012239f81660051a83264712c94e82602cf52da82acfa04ebb5e7316755bab082f0296c25decf3a7683f8e90575a0deae4171404fb88a63d553215b51797134426204282c5d9e93cb61147ff0d6d47c5fa95c4c9b724859f5c5b63363963cc9feb8a1a6a7d8bcba953a3c73a9773a189e1995af72d990cca43a91f5d8faa95ea8c4843263db8b454d852263d2c462f0c61ac95c792141624e119813b289f1623a055f055d2466916668c930024e283c198e7e1c122b955317955d981f5706b575c4a5094d47172ab106d36be1623fde095ab4e5f06408692de700aa11584774e012a8dae47564a569a4a10338bc4c81891f5cca47fb6d9fb10baed281bdd79a06567908ea8f37c402b3e01483a099f19989b8eccce477878e230433224514b31996c14cf38e6c5b9e5849559be05ddc6d89a6c1cc23e42c1812f10f2dec532c492cd98a08787a174673fbddfc11cd5808d7beb0a4e2bc3401e377d0d71699fec4d0532c237e1cdfd47c03c8e2e03729bcb30781e3a11c2e8007556b544a5533e4a4ad190d5b2971e593b3c64e95df80d0f4668bf108f01064da6eb5da7bf8cb6fee1257e0768a205e1764184b595125046063e26345d600196cdf5d42fa06eab714b648377c2222d05a3e90c86f89d0144e6d59d18d0864351e59b5b723a24ddc6954408fdaff7be6ea8b8154fd6cac59e2511ce4e219bc2c5b6bab2903aad12ba021f61d7aa71eabe4e3de1a0b154a04abb27f46297e47d31f0927f941c74bbe41faad206d89a92d06f4abeac748d1a86f12e8af6f0152de914b6da9e9e51492f3ccc41c78b9ecca1f518cc2df868359fe55b703c4f9ec868f449b75ac3eb06f6874337e5a2bc1bccc87a108d17947a6c238adef603bdbe9600927f6aa92c2faf57f62cb1e93e323521b5f4d8dae5fadbaa4b7e43e25f2b850a74815397f195cb6402173faa51352cb4e8837c941e450bfdf7e43e3e4e527f152e7a693116fceec3c040c010fa0b031ceac4ff66285c1ae75863fb0b8846d52a4c9c4a5286df9d390b6302ba5f59640affef0cc4bc3c0505efd699d2bac6a1f0bdd69af833ce27e0b4b4bed0f71acb17e9ff8ee45c1a7eb6b4472194278ef480c9079661bd4a203709bd89570627fe18dec8a2e80d4e42980a5b8285944540b204ea2784cdf6a4ecdc39353449597da44ca54c6f8f4708908e46c3bb3aa51ff8db6f585c384ed92b903f11376983697a558af55c54ac88a73121be1574d194053eb73a87d474763130eafa5871719a30678282af96aac8f8c79c23bf39a00ffd17a311d29a16fc11f2ddeedf444cf090241bdbc78a287263032fcb16a135ad91d156b4057a86146b0abf92013bffa59f77f1fb4deb5d9480898336db476f54120225a7b9ee0f4fce82e783b83822ddc8999aa2acf39361f0024efc7c6a50d0dc0e00b2031ed451ae3aae291902649786459c006491a910be31a6b0921082ee15b527a0c0d6d22b1e5d7ce6221722d4cf342628c93d682d3b77fb86cd7f7a85703d2a6bef0fad3f08992ae03a0ede0553c7df8698d7bdddd37acd218a3b45cb0cb1ee499b8e805c0889e8fe1d10bff0105737df85157f3c3383fcdf6f566207b212f07a7821ae0e626aafc7f78987c5ef17d3e0adef6135b09b4b47920249ff1d0b8ba3f37ecd249a7020c365907fff65941e66839dda375210c43782b96c8473e59832f9ce3b97b890367ca6e66a4de642831d05c5120d62fd24fceaec804f45b72a813b9c3c369a01b6d006698f50f45ffcc3ff3fd14704e22a16a12ed20e6fb9fa3b279fe413da805adb2da0eb95d85527e425a9cce671677a30bb7726639d6b80fbe7fae235360875c88d373f8b52e80019fd4479c0cb195c254d45a537b9e47e27d2ef9b1c2b5992adecaa066e0efcc4e376cc9e8d99e09f7af1c76016fbdbe91710483f6e363660a62dcb2a2be1c79b2d9079210969a45b3ca36fc6501caf10a21cfa360aead1aa22be0e72a32b84f44f22e517c8e70056da007be6b9a07b9200addf6b72ed67006d2e932fb2f4d0b8bc93044d2809611f51421060fc877b3cb3edb1fa1cb6456ebfe30f9a704f47f0562c0bdbcf6ae93f482973b437cf6f90be90b9c012d7138e8df2d84b518503a3a9379cfe2e625f2d8ac4b4c2decc0ca54112cb952ca3ee835e8ad4e597912accf18d15bb391974af963596720d94c25e61030d6ecfc936518530bd462188be7a91e20ca69576357481b94934ff4403f38ebcec41016292bca874944e221806365f9f9e63aba1acbf653f36704ab23bfbf4a6f626967bea0d797681d7ae66cdba2ebebde6c3964b669ef51a300b144c048a0e039d5c3016564d0779e4928aab07936a76024295dddfec6f0249cc51aa8aab99149887b7ef0dba0ff494c87e994443015a15d5877a9f1e77e73e5f83baa0d509f282ea2b5e0da3cfa69fb83d7b118f98d070cab690b6775b937b4b29934c01a109c409cb09d569dada5089ca4e3369fa9134a548ba1621ae3c9d82c0281efa41ad2c79ba1490671e00c175a2e2d6671e0001877bb5ea4e54dc7eeee9819297ce26d4b41e8397637c7e54f550b1f33685ba47b8bc91e8e6a42ad098a17291d19262595149898182ba27d3e7752592e5b64a7ba4945341274fbe4a01fd4be184d1f70525105edeccc9626e4d3003f469c8939d3986038045cbbe42ca0e4c25d44aa3562aab2ab15675d11e003df750a1f30178eef149f237c631d3e7923b96667f639c97663c236d5879f034e31820a716c7945230b8405266b3d8dfd8486970b2c207397f6c851d5290ca38264663b2269758666ece592da5cc3c67cacf18bf63ce2e28f374574132b9f2524a5242fae9f2673802d156099556ba844a89e6dc39829495526abd9bc223a53aa1d24a852a7542a594d24aa95727ccb905651b74fc32f2ecfbbeafd2187145b5fbacb5218d3432599604ba12a952c94b9d57d97a3785474a672d17b7061c3878de7783ba77796eb4d68083e68b2b3f93473f12938ea896b35c0d4aa552a934a94f52a9b4f9a43e49dbe693fa246d9b4fa793c47147a85b9342ee2d770409833ab9213a99e9e299d784bd0c371f9ddfaee0156f3c33088bdbac57bd3581616078266ba34d98598bdb50afae60715bb23581a1f8b9c33307cce25e1c2c8aef176db2661543a489c546230d91864873873477ec108934f4e5b8db901d6ab17955bdb92f07fdad09ccda3627fe2991226d9c8986a4d003dc3659eeb87aa573b958731f983367e2cc3939f43967654aef3dd119e4993583308cb5dc0cc22ba6b32b6fdc385f359cae6ed2598598734ecaa59d0a4e17c7b077739c734e2a85b8d229e55aa7cf5a7bca90dab0fc64d7a2b6a49452ca1ab6d0ca4462baa494524a49a74e5d792ab74dca0da68254f54a697345aeac4cd9eaa020254ac66b34def1f6932bc316aa41504ae90aa5f31503522ce5e219a0be4129256eb462324396afa1922dc7068ecc76b37ae24c8c5e39bdea1cfd349472ba7edc60af95b74330b872e565b33c3b07d6d03034f3355f33a7599b900db6c1662da64252ac1aa1e3172ee4cb0fdce6daedc1312eb83309c442330a6e69bfb5642148f9d0c067c88fd77b6cc1438b209de7b9fb16ae15e6c1abc0942527d9ced96bc09122a37af1c9da615aebfc3a7ed0c1a3c70f4f483eca16a9fb6af5bcebbaceeb9cc11ed81bba4d5a6232813d78fcb8ded00ed6a5e3071e7a557778c21050c11d79a84910e0194ee9d5bdd665264bc74ab68e6b650eef1af2d09173bfd7274b25287df1c30e3ab8cdd9527b4b1e0e94069a1a70c8ac5256be9a347449a0842b49f09dacfd2dc073daa2178ef23f1228815eed10bbf74a8095ec90822b01d76ae5bddae18a2bb92366665aab5c512aa5f45a6bad5cd10c9d6ef9ded30636533a27a5ccec52b2a4a62aebc4ca3c5ecb3de665496511f7db6af79353088e10836b02faa848620485ec04048a98cc153725830494e4f9c147ade5edce60383284233adf5eaded80d450226bf1edf652918326bee7eb4a1f5f7a7280f3e0d58109414580a0081a1366f2074494ba97ee09568b18a040c209ac88820f0a4010849af9e3e493f2896fa72daef06dad670486821d233230c2080f4cb063849653336284e33272f432c2e709d7103760b3274a1082278e78596b2d0a6a1386ed134b1471c388a109c3f61e21e374e3b68d6382f61c4e0dc7711c97928aedc478623eb1598ce631a1d576278bb91e2694701c67690dc34276a861f8c77b2305b59449878930be266240c4122eeab9270649dcd4161d8b9225d729a7b4bc00f6519f254b666e2694254bcaedcccccccccccc2c5972e8524ad9d3594e72b5cccc3cb9390c194cf50a89ceb628a58c0d5f715c3856904f2c25b3da69c82c954ccbeea4d0522f4545259dce4d347dc97d6ba55bb753550d6db7cf9706ea2c731cbb0d593a17d2c9a38f79398697f04821414a0acfc0dd729331ccd05f7d47a532062671cc8ce41891d269044ae9e8e3a51581f792524a79dc21f8b63989542775dec12a4d2e8954e7141acac1d25a679d938e97299d3e9da31bad94d2f9842221a517d43633b34c8295cc3c4941b89bcb3a799ebd94041b5a599d74d2721386652d275b6ad1dc5699dbea6c1ace4d360ba6e86c8b35bc1a2fbfc287cf81d5fe067eb77913f4ef386f822a7c673b9237c119df95bc09aabeebbc09ba7ce7791394f1dde74db025f5ddc99b20cb771725064a8c149515969616192e2a1a34bc09daef54f026c87de7de04b7fa5de8adf0e2b5f0c2420dd30c7feac61c6e61b5331c2995b586b56e9b9c4d2927e9132920f0b6bd9241852cc33795620447dff22c85088e3a50f2391b500a5bb306881f35f3fa3851e4e5772387e9db0eee7d140ae5a890a26edcd092bc9b60e79d0c0ae4f297fc05cf5556bc395f0157a04f989490fb3a31e455cf3d4eb8be6b4f9642204a4ed3d50361eff9078e3ffe43397f13052574c1c18fe236fc28a18c21aea3f84b70f4fbf751eed1f48fbf608f973eea9bbc5eb5e72492d5a1944329f4094372ea16745a633dd01ba78435a70c19cc00bde14469d8c3e979e429fc38416e02f40f76e07aeed1c1d1cf9e7b740083efbcf3e93226438d7081f0806441500d866a9ad8dc03120a2120d1aa696207c9376fd781a66d741d68c32379e7a3be049c9ea8643685a3f492571ba47f9d4b1ff53bf7dcf3ce9bce9f9c9dec3a972fc3c30e4c2ead1e29b4212383903b324d478b72f290711d5acca1451ea21b8803a4a96106577a72ca4a2778805a0d725eb39e18cc6cade70857a553a645172aa8eca66438f2d00bcd5f6851929ae84cce2fe30b3c55dc7106eca60914cbc99d03b5fa0ac19d27967007e7fc9d0964274874d1eaa6c78514ac91823bce8085309ed95c12c12be92ace2caab8dc417718cf4cef7c829babb81ac8132153b5f5f3f35235047d78e6ad9f7e5201c7f929e158bd6f066ce440ed7970e08ee9f2597a0477bcb419b170066c86102cee0e3af44ae66082eb39e8b043e928e5ea5c2958e9a8f4a42b936b6216670147f92c2ec37c1a68e49c132cc57c70c719b019b01933746610e98cdc5007e533603c73fa9571062c059b016349149c942b853303a6e25271a9e0987cce39e5e98867aa4b2178c5b53c3fe95cda77c4332757e1594988db6a99e2e30d626b283ede202f63f8097418c734e8e39a01537181b6c631b254695568c20c19f54aba8ccc0b3cc373597cbc2c20ff0a38a648f0d255c071024901474a830f1c2b901828d303f228d007a9e252c1b1996ef21f351d07bb23cf7468378540744d6a3c35ed1df54e952272c7d2512ac81d7916c6c07082a10cf4e1189e3baab85e25470596d32ba902933c77fea8d2a519309ec98163a4cbeb4d50ceae298c4d985214dde71f38cafffca84eea2e271e2d8e282ea7d086fed30fad2922fc5c7d107eae7070cce4daec29a969fc31250d83738ea994abb587eadcdc408e3b45e52fa015baecec4b645a466e75dbea27843b9f7bb2d8a24816410dcba208b287da13904022e765c48911d20548a8a0c24812161c79d5c808810e31d9e087ac8a22a048e2e708503842411656300008532801089a841105a0139c5a1f0f6cd0e0e70450184901d0133534877612a2c36233bdb2b0c2b3f8416bdbc291c7cb8741d9cbe2e75344b290e24797a1a55688e8a15288420e270526242b900882a5688284265434d9d101cf4cb7206d280b17d36adf91bc176478c63a7d592a13725918006651a193cb3f854c43afa41075153be3ce1a1c439da6a96cb2fab5727fc94edc6d600a3c514b6bad73ced0853affe43cd377bf7bbf6bb24faddb8ef34cdffdeefdaea94594517e3798729c3ea57fec40bf4b8ef9d5e7cbef269d3fc1fa230f992f7935acdb97b55f5ceb15c5a97bcd8ae1d469ed4bb7e49576f45205cf3d58ec7c476533a526971c93037f72eb14e5a7cf4f4db4fee4f70644acd32c3eb7426ffd5404eba72110f9fbd7bb18a81415d4c9647a79e104da5ff9e2f273de7d26ea3d9c3e3f81a3947ffa164029753e9d420b7ed6bd2fc7c9ce5e7192d3e2781dc5db1b86e299146fef2278e604f6703a790a28ff9b22063b9d9cbf1c2747f1df1b5e37390adc71fde4dd77c3e7272f7d396e780abb9417c6777e025b48afda63808d22bbf2fad82ff9946801293f812da1508b394e604a1586340c31793bd56971c7c9ad37e9bb4112a9fdb780cfad5390df7e394e6ea96c5ab75ee3addb94b7f2c5bdcef90a93cba57ee4e1fdf5d1479f40fbf5bbe1f4df8385cef392e79e211cf9cec55b697247f92a6f85c9e57c86b7d2e4eed8419da3514ddc9bc60e3c2abcd6bbdeb89a42a325d3a0a6ed4de0f4c071c68ffd7a3dedc7149ac38f54e6a9abbcfc8a9a5e051223a4908288eb598a1808417996220643dca89e7b785cdfa3f3dc63835a8ce79e9e9cefe9e9e9418223bbf9f6ae48c7d32501fad1b6a270fdb0e493d8fb331228e43c0cce2fe3d7fa761d7ad5d45e1d38887fbc9b7a49bd8ea9878379d63080d6d504684781216b21b5eb3adad18e7694b690b9b31dd974887c6f45369d5e754c488b2bac40a44334a4c5306c81e09297fb900a2a14d568d058129b31a34848a582b9b82c71c9905174d4d24234944abd58587456568a5a2a2ab3941422a118318a6028283414aa762fd1d1e91413f23645aeb729ba799a260ddd9163f2cd0d71464fede586b8f65fdc986db5f852c2cf92b33916c6711c9192ac54a474a4242b1519653ca387511da394921bd4b6037a1ddd8ff42ec977a74324d6e2587a0d291529d5ba234f7ad5ad5eb14b90dd4b552ff54c7a224b1dc8c3231da1e08e8fe76e882b25389276be7924c9be3dc8f5bc13e2d6700fe876dedddc9550af5ac9b733d801c1000a0183c70083f3b3d3740c30c000030ccc30c0d00575b30e68058e9687f4e48ea517cf975e2d8e1d2ca7c5913bf2ed54e74b2f2e08d6e228f3425ae41e2441dfad717375321ae40cfcedde8b67d8f582c574accceecc928b6798c951271bf2a427cda2357d4312d285dc495e2349c707fa487e3ee6b9078990f780fec5c74ee6ddb82c4e8b2e78bb7db5f8420de74ec1f8782da73361da6b3837abf4b2332037c673e8153b0b2e99b6c5e0b640a46bdd18b7d12b8ea1e10517c0d26b363b00c23105138e77b55ac99517bad8f0f6593aa07168011c99870570b43fdf5e031c6d916f7f015b2d29e42bac571d13d2a24cc2f3af00da100989c58886b428bb78fe10b4211a42a443a453f4a445a9e4f941d0a6e849abb504a7458984e777d06609ce92d792979216e5119e5f05d0a648c9d05091518bd208cf4f03b429322aaa15d5960869510a3dff0cd066899025b125b1a2a0162517cfaf026d8a828a848a84725a945b3cbf0b68b32407065b72d3a2d4e2f96580364b6e96b896b88a98b428839ebf05b429625274547444a4a4455984e74f8136444a88868886705a9444787e16d0a608e7f52a1ad2a2cce2f957409ba2213a3a4f5a94b4e757016d889e14b58a5a3f2d4a2c9e3f05b421fa99cd88825a944378fe18a00d511091109150514e8b5208cf8f02da14e514c18a60402d4aa0e74781364440349a518bf28ae7bfa00d9151ad46c4a4451984e73f8136444c888e888e84b428ad787e136853242416b329ba6951ce9eff036d8a6c8a5c3c23bfc8c531fc348d0406b01fb9a1adb61d7dd776a64c3da61e4871a8abea0c69e2d6706c1f57c3266b1ed5d874c126ab93753258d7753b2d4a6fd9ed74a17575456cabdbe9640b0dd568b276320a16e191b37d665b2d8ed3b5e3dd2e1cbdb0dbe9d5aae784753b2d32754d9647a4991e4d96173b9aae6ff78834cb3e6998d86459797324c74e265f0d9bb67533b720e9982c2b0ee58016c7ded857af2c6ce75bf6edd4ead896b5ad27dcb193c95a2cf2b55e681cc0f8849aa94df2f2e74bb3038c2520929c73cacf66737a5377035d483870389c5b39c771a39d71cc3870a849b04853633c43924e6733891b55e54425ac4bf758a43f6af8e33a2f81a46ddb7c6c9fae4b3fdb6c6d1b89c4d6b33f6a5cb6eec785561f191d26123bfb6694721441b4beddd2482d124eb3a88ca300726454fb9611f1a33df25a5104b1821f4937b4d280825a3cc0f707d4a2cd12517a2780953c4d8a115e4a99a4e4f3d25384d84a4f79bd1c51845eb68fd04b9273e0e63409e823485d7b1f102b8ee8661cd376d63e5f8b637a7260f591d2ce3aeb930a722517d234d7ad6edbb4244b224d93e77194524a3fefbbc17d5fadb5525a2ba5b4565a41af57305536b160fd6998f693cff5f96ed49f5ef9704cbbf5f99e01595a900f075a9afae26e4d29e8d18942a1502d1955327d4215861ea8a085041b781965a8974ad970942ee50bdfedf6bb772ca53b68f78ede7ccee0430f720adfde523af521dde5bb41761c5cb62e7df3176468b1070f7cfb267fdb7c051363a3c5ee41be7512c9871fadb750fc0f66bf79aac52de53f42bff98b8c88facdb9f59bd36d335dd3b79bbe5d857729397f07ba94c21b0ef0d67da07952e892e300df7e806fca73339370a577872d1b9072c2dce91de0a8a1579db2915aa97c655f9092b9d209ee981a398e67f8bb0aae054d4c26a152d48552f0a74b5801787c14993c84909ccae661022dca2a5ee680c30d397886924fbfdfd998ac1af69835c8194a21053886e9a470477f0fcc621d3de6ec5007c7d0e04adf668f1c2e37d01d2e9df3979cd25ee510e2e8d51577641d5bb5d3daf072a1afa87c4dce041db3a59b8b63e8b1c6cb71864a5b4b22c9d076c702e44a17b8f966c3c6dc36ef0f07f9241b5a1bcd711cc7711ce794e3a95372edba39b802c6e5bc8795f7f14ab86ccdcc410839859ae9342d438b3b4c8e3776798d2c056166b136dfdcc53adfb86c4d7512579d0482c0460de538cec5863950e7d7e1d6042d2caef17c5aec805a5c29a55cca124033b9cba55228e7c0d16e8cea482892d712365d29d1d24aa9940a5740150ea8ba71a5b0a8801c6ccec9bc412177c66d0a21311bfad2a78666813250f6f728e503ca503354a51555b34c8e504f440ec585828392c3c92356cab8f07cb6bad5adb7962deee8f978346fc8f3f12aadf4bd066591962677e46035c6842b6b076b9f24497e60bef24dd57a79cfeb6213e86537a3b49bf54a76b49793a56ab9ba4fa657d3069251a6ab5d2be5602ba8b439588b9372de583ab25356c2c1a9b55537aa9b9aa48d5232a286bee552ddf4ca066505b33818c3f00c7dee693f9d4b01b3a8e7829fde9556dcafacbc13eea86aa95c3f5b5c7906e3ae30066c683c0d6d687c87ae1a5f033c7780e73c616c68a884e7ed4f5efafc6c94f0bcabb3d6cab5d62a6598460d3c6d6a636bbd9235306053e36d6a3c0d757ae511e9d50465a00c2c42dba98db9f1fc94992cef734aa5cc694099c854ad0e5d66b4d2dad98e340b05d45b7b9b08eea86a793b9ecfce6471b01dcfbd50ca444a294c75a36aa960f47494a06628da54b53c1d938d37f3f1e28ffcf4ae97b86327d4f9c04ea8ad522e06eb95fdb92307ab615cf923070be3f28f1cac619c10efa745978af23a239e41f1983b303a29bcf440f092092f7d093c13c3eb96c033fc29bec2b2c2c292b2a14bcdb9425bc2a5a0ce4fe7118cfdc4f9110c02be1e84fd4c809475185acf8763be0ae3b8c9da6ad83ab92dc393d1963b1795c7c1e04aefee065da88a86c72971a57b5e672f3df04e98a6ce1c53bdcea041e3bb7125087e374a6eafafd078a632f3c61c13d2cc0e1c55f8d429a463a7c3c538d80baef44b6334542dd54dd7f80e55ad265c935b5388026a98e91f4ac94f6f4e48b3be1045eb218f9a793f57c6f0e91c0354b5268ccb3d6e31c6b3b77c7199834d28dc1135b320cf77dd99302a78b6c955e1bb41c331d363bd9a43bec91db71d0ffc6ed41086df8d6ea4fff2f2dde8bc060b2c7c372ef538180c4d770000c077a3736ed105cf85ef46774291f1fc2da0fb5a6eabf35a6eab24b7dc56514e4dea00e0752eb8f0dd48f12ec6f398ef46ca57d40a0ecec655aed61a6ae8ba922d953acbd5ad73706525a19c2ead2494d3557112cae9ae8063a733f250f17c5452a64802cff3145d08f9013c4f910411bc14afcf139a3001f002f0ddb8b2a593759d4c0874f10c8acf51057b699dedb69d6baf7e026541ba9173126a465a99f14c6b3a49d6abb6462da6f4aa556aa8231634b969d6cdc7132b8137e361c1ccb68ba8faf9d5695a94b5d8deb282bb91c0913bf9e6dd77e374a25eb7db09c0b34cee006470b12bfdf35165030d56b56e385e55cb86cc84b9735eef55af5e812ad754e55c6fa39baaa58aa95aaa17ff55bd54392a98ea4685a372a95eaa1c154c7eaa9693dcf4dda07efafa92406a51ba189e94c9542d5944ca68523f17d5ce42c1b85dd78e8353c72adbe8c609e9d5f452104e086a86aa4dd40c25849a01d150422825a8a10ba2805041bd9a281a4aa8574a5043a8d913577a95d1f40a07bd292ed469268b36013cfbc5754d96cb1702e1f2800eb82811e373aef1c2ce05a26280a85913607642ddd04bafcec14017ea392490ef908bf56a965a77e4601c8c8bb5c89e8fe7e3befa15af14853b82af9f5d0b4a4617d4a29441bd92deac0930dd044ea79f03bac6f3b1c11d51b351d5da761ee0f956843b6e3baa96cad562d7160753e1348b837130556bf3eeaf01b5e60b51b326dc2a65edd3371e19a397da92344bd5ea2d1cbd47d18cb088b2623bc2220a8851b32177649e9676c2b03c6ed1c5061abf425f764755cb858636b03c66d6e7ec230e98f5855c6cc24c0f06c54fef6c78b248013c2fc077a34469839deba2bc0bc11da58c3d1f1a79e44a67da8ad0647d3bbc814ef06a82dc889ab93619a1679db8a3e7536b7744cd3a2f79e724ef7c7a0dd3695a6cc7e2a7977824b35033d415ccda7626cc4e11beed923b7230d46cb2a40c3543cd2805ab8b63da3ff0f50db3ed38311d94b5ebec5ce07a472db2fde11b650658af7e700f8e3fbe6b1e13940d5ddaaff3e37c3836ccf78c7a357d5a9c1d5a2feee8f9a0c0d133fae92770f46a3f83fc36a2664d9b864a98a812df567bebda1d2ae139fff423b248af4ca1aaa56a85dea5202936639d7047cf67ca2377e6b243b8f467d622ad451a24e40db167e4d53c26de9316edd7fa6e3e1cd54b95d3aba9c251dda85caad667c33de8fae2ca1f51b3d1763378dc620d0ee6c51d519e8a83956a13c6a7c5f67c7e769eaaf553c633346c68f0a8cc000740800b2fc4c0000098550b31b02013801a0218800c04105f566085360a3033034883010e8080ad52d26cc9a5cefb4c2b2a309850b39feeb26da8190ae8f5d351b416533f1da58582d32c02a0825ae41e24b87eaa5a46b59f9287891f51475d8f116ef023ca0daa52554b75d3620bf909f290aacc8902a7d6f664070942a9d7224b6ea975770de71325a1ef1a325abcb8638df12cc1628c245894c12b9d52ac249b2cea3e3188c8d8f9b6826756be6b3ee4773cbd1a8fbc74d9cb1cb866e3429708509ffe80eaa4b0034de8404b432f3d2c09bdf4126db23a4043171da89768355687742c17a14ae48656aa472a4ff5a940b416c7d22ca8c54ec1379d4dd66bb25c74a040ccd0e501b30394fa8f1ae9fced40c3ee157625a1f943d28a838e5f4619ad9faa67aebaaa8c678a90846a1066d5228e48e29947196c58e2e198520febb490c411ed0395e1775a82b89d124b39aa3ba75a89a8458767a4773c2d4b10fd51d772aaa51cfdf453edc42425c6332c3219ad11b420f4d3dbc2d2cb323bc948e9e5fae9d2a403233f9e725e3fdda4839f4e41961a25f23ed01f639c7262f0f46a7a4b10b77d8cc1a3fab005c84df5a005570c8b31a4a57a10d25a5a7071cc74256efb3863f6d3c790f6d367cc78c6fbe93380c216cfb0fc8cc1f393460b421406af09333728ee68adf4aeca57b2061cf8b9821e68990b2477acafae5d6d532e6cd9a2760bd01d2bce4f9f3507d6626c48d56951c6b548c56101e3569e5a577d7271817e505b3ca1352e84d859164690272164caa140a58b004ce480052808028b191648c0a06b5bc3ab20a2c512b4f841ca0043461c01b9820b9f9d3e82089deaa6b4a8810b95822ab105d184f4cd7e71e7edb8c794cfa65dfa9547e8b8c50c6cbf839d59e898e5cc334a8e087794912faa54a79edc51a6fafc34cb6b980ad4f345c94f3b6b6dc63afe4cce9fc9db5f9099df6a5cbdc93f93f5dc0496fc03bb0f2c914a5e57f2ba9257bd2b311d3de6a13ae8aab4de8daf296d79844b9dfd935ff76723bf6de055e71f48720f2ccdcf86d9f9b4547ab27a092435ad3e9bc052aa30b9e34d69d14bc94eb59832aabe67a883dab66db2d65ab7e97293b29581ebb5ac9538b2d52b97bc39e2bcc5e5de07fad35b4c955cca3ec89ff4bb41793669738b6bdf07bae2991c30cc52808499ec13cf3d39483ca77031e7a4205de205115a35b28f20016bd500410653497dab5ea9e42455d244e7632fdd47b3640dbda2b4bbe7ecd960113bbceb36a794524a9b148e3d7e47fb66c71ebf853e68e8833ee7f38719f6f051c475c9e13269afc0e79e1b267ed2a03b72cd87cc0f40b4387a634a8f5a38de9b4ab9af565c9391f9c10977e49a8f9acc913d9adc916bdfb51614dfa194a522f0900e9eb93b1c736ae79711557bc9f335a06bee6cc2484711b9af09231d46254bd7d1e387967c49988c491d29933bd247ce244d0ac92159bbafc9d23161a45fd9d599e178631c8e17f6d2532a498b500166dd1dd641019650300975dc1c30ebbef815c54bb727b874087787c9fa5ce6903e3d1c3aae29533a3b68e853b58268b2a0262fa0264f276f2288bdfc9edf023611c49e0154c61036b0972838fa7712c4d12bafef9186916ec31c7a255dc2bb63848f361d5c468244ecd988cb74c933d43d965c949cbdbb7379ee912177b4b1b3832376c7bbf3f2bedca58ffaa5fbcad941023878c6869ff66050070f1d5c1d93b55ad920eed8cffe77a7c519a6f0a0dd1e38b7e9975a5c9fe1c8475da95b2ddad1ace633c423c43359c6da49337b984ff212d803c947fd4d7ee849ee98fa1bdc8d6e5b7de93c602f2557e1e3f5f2eedc5c2125e85821f45ba577a7c531e5488b9dc4cb94165d2c955d7fc97d0f93ebb90655f280fc21e90a0f6a3ce40d5b9c9bf3e23c739f14cbd81f7ae3297597ba8725472547ab6f52dc51ee1ce1e97ee4ac455af5959cdbb9fc3c26479e171e914924ace40d92564e7077e4915ef1d42970683f05ceecabb790d669b1d68e863249af2aa5e1c83c3b3493256113a63a8ff5a9d6de9b4ab9cbc8346c16c66c5a7b6f2a4561341c574c663fc373cf1729f8a15e55a3af452e8dba149a34da28776cd89091b1f6f200f2c031db0bcea5ee2f376aa61e74958936299d07927fa0109f9b3e67d3e75df77d2e41fb719c75fe1cc0359fd3f44772a15a995ccadeadaef22123599347f2498b5dab45ebe2240c8c7921f78a0624d9b0863bfa558b38a9e78b117c58ac7e54fdac757185173b4e20811392f093990543ada65db608e45d8244295d81270a464853c6ec7c7b5b4b29e9a91393524a299db4c5975a0ecd1912021078523220791ea59452cafcdde0babbbbbb093c4d90354147caef069d220a17b46a6813573afdc1b5740797a6699a3a09ec46cb71359c0d946ee0c8e391dcd15698acb5d6d06f9095bba8b3ce1e5bcc309873af53fa059a09d3ce8536e5aa2e0f4239e88acacb8921b8d2473792c14f243f831885a085b735b438fd76d3b084cb054e046d51e4484dc7bea7c0c437ca7777b709aec72dcfb29f7316b1e0e71226fc5c3121051a182184114a5c422ce091c20e381b3f7a444b54f1ed96e8e8dbb7bb821037df8c8401c2b75ba2d6500dbe9dde96131e6d91c650ceb7f794410847d820044bb8a63041af0008255a239ba8f02d3d223708028302044cf8f040043c413062895a8f12889878d57a96e0d39a1bb76db4573d560061886ddb3697b6650a57b69e93b840669a59415b02cf4de9c9026654fbf1ba11ade44ac721071cbd6a3e02e28715cf506f8761568f01f86e2ee8338f0d704c3b8ec9e280642e8608a2a33fd0b00711e46877d0f1cbd8b3e79952e9885b9f5d023e389a3efdd3497ae5d33fbd9af268c2e8dc2de46107a6b1acb3bf48960de5938611250e4b16377d7ce1a7978eb8f37da0bf03c70cb5d4b9d5db47d96ac9aacf9e3da7ab57b25611d98eabd52b996c47eec031cc923bf441be8c63e6109d58af827047d99237d3a5abe58e94316df6f421c7cd81739a9647602769293b629ec952908e9d63244b1b44b8eca3ccebd0f117e432929c2121222e23c9d1f99c98eb321213187d3fb7c8484ca0a48b1feee2486ac8c8e665f45abf2ddc3e1b2f256388bbf967f3c3f6ea4738fae8a79ea40c48a5b8239c043df78061f4a305838ab7932565473f3f4a19f71c25e149320209eb554f700aa928f9d3e23875bebd6f382746081052d2ac9bae06c613168a8c7bc2983a2db64f222db60a94b0ce427abe26acc50de7471ff3a9cb9d2243b2e9d3a86bfd64b6a6ab45971976505fc93d4e8c1070e29749e7e4c95eaf36d29ca46d9b609dd469e6046b0539ac1949590fb594750b519ea6c924df4a38a607d7c7a649591b7517d47be8fb86a5f6c1b873e38d379e737a2dfed88cf8cb3177987b6e3e993233b3fc2943edc82b65f3e474ac577348c34c9791a1c5eaf4ab4b75aeb15ed885a5d085862debd51c533c521881ed550f901f9e3bbedc14d9164f8b804d6d704719a3348aeb330a5fc4a4b8a38cb9b66ddb9ad2cdebe629295395368ffc51690909489260f28c24c991244ca47c1746a779a9d7e20faf573248ce7a05d42b285cf6818e53c814329b38cd923c3e393f69f8d14a5a0bfdece94af2234dd15a6bb77a1bc8f4bb819bc214a62085a74ee59436e8198911b4fb8cc4089f24fc3c2381b552cf488c18faae29c83f28c7853efabb21fe0099011c0d29951dc1868d970cf69af2d52d251774471b3feb8bd2e642a7c5d1c64f972d35d06063daa8e28ef2e5c95ef2255f1cd01de58b49b3e4cdd094384f7eb6361d264a3cd99c9bcfec65a0d17eb4c1b54a24e85a4eaf378f725eddb82a4bdde77db24a2947efc6e9b3a16f39c63453f743f9b6cd6badb5d6bb52a54c30051c532adf8d5e59912bd5747b25533c94182b5ecac792c2c2c21203858505755958584e2ca68f85c5eb4a24cbb16c9505930cdb4b3cedd202259edfd16129084e9c3871e2c48913274e9c3871e2c48913274e9c3871e2c48975629d5827d6c90c37267436927a9ec7794204419a33e5ce392b4d6f3877d67abd3967cf79e724d9e0ce496bdd6aad5b8a9b79ddea56b75abb296783a38176db39e99c73ce3967b595d239e79c738226ae5dea3c1c3f73ce49eded9fd2b3a99006d9b1f7ddf85a4ed3f8a3eb3cefce39676d3ae70c47cf643a9dee977a2e7247cf2315e9393b069dce36b9dcad643a7ec1c1b91c8e2f4fd965eca977ccccecdc7372af46ef53b8b0be2835f9987c4c3f6ca33acf102222927ab660b5465ef4f3821a21f13c7d350c754a7dfcbc781a6366f2094325e3181a63966af58a7a4b10774a167838867a0b903b23880b3e3c233dc8b73043e8a90b3ed3c7f0f5d4c716604f3d7cf18cf7d4c31c1678e80662a192a55a4fe35f88d92ac0d30c22eec81d3942823b72300ed6e2458b55488cd3e188c85adce18eb4787992b4f8d3e20d7dc8bf31e380b8a0165dea46ad947d626b6b95ccb1bc52a29cb6d3367d43414141f182de5a69a5fc99ac1bd270bc2173551ae14ae7983755afa63d5271f64e71e688b82ade8f0aa2218f12cfab780a0aa8533e7744cd36293d9a07e4cd7af5e3c385aa459baa629e479b1ead5741bdea57c5542dd510950e119477249477240fa785a8a4ad95463dea14d100000020089314000028140a080462b1603098c8ca621f14000b81a24a7c549d0ab320c8711452c618448821000000222083d1840178ad17f679d544cf4eb7ec0d88bdd0103f4399c3f3e15e0666235c874b0d11da86a7ea0d393d0657096b69ab36f96e088b4160dc9268a73addb7e11d24cb50bebf8acbead84ebee13c8888b0be34ec60c66b8aa3e34ff05a6f50519f45ab05add721d7ad092dea2458d31f735adf24194da022ed0eb96e4cad5a702546c261fb47ce8e9633903e36dc1fe5b357381075fad13551fa169431f6161ac9a3d7ebaa2334e1284313cb26912bc0f129f1d509c444164d27579e41d5010404a8257d352ffd588209495fcec0782b6e83c4968b00f0c8a7ba40fafcb566e65f9d205d88a0d4eaa725317715e37f8bb2625c36dbe2a9e04deb22d131b83ad88a48ab3edb553f5e37e1494b21d9c92956b7d3ccd1059933a1d6667d765d086745aa5637dfc1223d1b0c77172021b43a7832da887c1a6f18580434fb6a20efe18559dbae6bb614c254ae107cc7c4e8eff59adfd270895c48b0efdcfdd9a87500f52456f697fabdf970f625b0a4a361cd3dbc4380f597df3974c0465a018f00c9d55c62512cc3abdbe1438ad59d71e0d55a103298a0622fa8d5d7548bcdaade66e63be5edb3bc59599d520cfc882ff8709bfb30848c34d600ecdb082eefbba42c22c3956c71c20db3acc13af979ae84a48a40ebee31a3aabb67bd48efac2f7fcb6c9333ee0da954806e68cbe50c6dea74dcf2778b89b960f8d80be66c6efd7bd310757d2a2974a640a498779162e51210a66152d38b6e951f62342604ce216fd92fbb653afedebe3f7746efff870282460b7cc960a28b49443a4124d6adbfe1d2d4a4b60e4f9214819117a152b7454d227d1cff198b046b6ffd7615bcdfe136d9088c6ef99b60327f10b87e9ad6f4cd1758faeb81d9d7a7e986e8de38a726a788f1a4c5d28f40f74fd376ec58b624b005e0687d20a18dd8c34441d697aa933a4da168c9bf2a2b31dee2780d43b551b5415ee38bc79f921669e46bae860edb04d9f7b0bd32cab72f524cfcb50279cb638aec5354c2011e2ccfb8af30e8b03df7dde177c53acd8d2881d07e4cfc79c446990b99b11a82d8b7a45002b8be984b01389f8af73c6b1be88af7ee70c909381147cdb5700b13ba8fc4db1a053a0121727bebdcc68a82c28d0e7f07905a8f83155c323f8d3e777cbb5495f528e146c4a46e302de798deb6a8d8d36474e787cb80d342d5245657e01c3b414f61a7908e2561f0ce012e999621d39eab63641f24dfefbb539ab1190c34f22998e65326c98f9e23c259b48b61cd634fed6a1796cfb181167267f87e6d0b2396a83bc8774dc3dba04e74aef1f09870b737f1a266c97d0246df14580110281e1941349abcede2b078d66e50a30d657e7380b04414637d6dc0a8fcc2adaaa1f00753b8a03a88db794b0b581fd2de190b69b2781ab29ed0a9f36356be47f2c2f8f15ba595509a9b06813cbbaa5c39029f637de6ef31abf0bf5d64328c4b337e06059dbc8282dbbbfd534fd564bb9a8746e268c10870b9c3c951db9520fca2b3829ebba265cb977d39c11803edcb0a9e39267539135f5bb8a39be959fbfd8b4c1e6c22e41f3b156b5cc3fb9c8aa337206278332cbbe779e88aa432a2cd947844339b598832bff797904395eda6590922a39dd78c07e879888d97aff2b661145752b9fd795c097ac24ec01c55242b81c1389f31d05d44c9310e1ebc90cf69c7a9c396a0ecd42c787fc0203216aaa7d86e5db188c777b8689a17d3d62b8f990c9565e94b7855f7059a6c514af9bd5eac8d60d6a5eafefd252e9052ec9e5a7db7a01be3051e5881db5e4fae3b6608f59997325bd48f4566846d022b085e75f2e4a65ec5421a02b9d9de54604288e3138b99d349280ebc1441d9c9d6b09ae257f5cc9294d30a834d4987656b6df4e99884df682b2fd219e198077011df25d0ba5d2d182e549de9103059763f6a665b096a1d4d379bc35c01ba0b0c7716ab6693fb75f6dd2352aa9a8bc9c6f69fcd3ddd0af5edc4e6ed62e988b3cd99ebc638261b49af1b661c82efad058f78bd69283a073cbac4d3abff0e265419121cbe08e0ca84fa00795b6fe86418a3cd72760faeddeb1d4dbb2c899e1b7da526d82f8a2090bc436f7a662b3423f002189e889da836002f0b810f2d23880d4efdc12965cf698785d351d5abc8fec8d383eee14c5c86499b0f32f7c7bfdb35fe955973a51d9ee10179fa29fa91d2c1113cce95d08a5590e748a96b90066d82a4c5213bdc9bd1a2f55417008ed1acd282adae584683f06102bd1a72870a0ef800e2d684de205040fc9227087437eb740773d1c311b7d0ac0e4039e64ad14152709040cb07ecb27a07c4bc97cc8a119b8e6d6d8916a04dba6eace45f06d427147acf678f5357a02cd54ae69500fcd51c5ced3a3594f0d2ec92a2b227f650bd303adcad15be5f4dc733c44ccf00d572b74806c7efaabd1e2f1dca2950c1a6b40e9b2e85c115f681fbe98b931049ed95767e20f28aa267332c5097576f8525f7e16088c11b74008f92c3397b48cb87a98041d3c13eb15775670c3b42aee5ce6c119c38eea753ee07db0beed80b8e6e3bbbb1e090550c244645367aad8802068f6e456a21e0f78334115a5ec8e1de51aabb62c9c012a3117c86b6cf95689a8388b47a939785147b79b05ea66531b3ff7655a36c079ee2cfcb12785770b68265417720f15ac20cf10ba76e0db996b866679de8da3ecac89c60f3120afef5d4b196a1810981a08d52dc2608d31845130706fc6ceed21d0cf391d1c94313bd2b6b5ab57527ada8eda3db7168e303210a1aaa8364c20242e2d17d4fc678b9cf902ec40f1ad131e93cafc401a56e88b4bf81ca7555ccbe615e6d554ebac127b7aa8eea31105d4922d189c9f4129295354a512d6a1e771f83b8f2e8328ca1dda46da2b785ca617f2aa0fe51d09be3ddba90c5a7d1ca67e7d8482837efd4bee1cb820512df2c8c470496e4c51c555995b67702a037bba154467b7936f92e2722a2066b79b8f474bfe1231408291eb4a5fd9f5d0a2e4ec54f0d5aa94f9527f76e4a0aa281f456364987a0865e81cedbab62bdbf225090c0c7788ea69f45c39067df717608feb4edde0166bfa9cd974d86f8cd9496955fac29f739d381958f5f32ed9aa3771ac7760c7b27d5b90c662e236558c66fd92ffbe6340c14ecdea93058589f7c43027af6b3def9c66556de6fe07126cbe44fae4d016bfd43eea10fff1a4ba91f6bd63628a1a07f0a185cba399f8948bd1bf90830fd4fbcb734e7801c759b018fa10850c9d7a944a6e4c080c35f8e4e52e09a6ddc013fbc0d69cfc990e4536d4fa2595ffb70d24f8f9a8986ae84f9ca2cdaf77e7d89e5486cd5fb4c8a90ac68310374fa57ce81180391577777a6274acce3cc199982bb1a14e11d776dbdefdd1d1f3822dade2849c5ef12d59100375738fbc623666a76917032d4b2c8bf28a58e85fd82354853b8dc02077c9a82b2bef5c5660b8117560777967de23951f2745b9be9a1f52576d6a6e0fac205026a13970a869fad198dca37372670e7cf1288ff671cc151a224ccbddc768d60ecbf39bca43a3e767950e842626a48ee200073787c449b9ec0fcaa7de6f288e91589b7ae51ae73a43b7b7a33068ac38f810ea3df692da29a7d5f7605530d3fe4256692c8bfac412d19a7ceac8d6a320eb308fd23f177cf4e95929423f1508f73914d2ce6ed9c56e4d06e729b8d4f1b5aa81a8e35bd4540d5ea6a45d6407dc968e3bbfc7853b8dabe2426b82f896fbd429e54c55ec40613f2c15d4545d2e06fbb11012648a596e96b457d250cebeb54e000bbeb338e64e74c30c889aa054fb49bac2401eabf1c95b0838e0f00d13d5d1325a92ee4b445ad538af376f2d0447799b9d81aaca29bbfddadec208f00b4a0f386b1d077b626905a159dd540fddb5b8472fafd4d3e90c94b980772810d1bea5df0d24141f9d8477c59519a21a35156bc77a2c4b3a6cf2ff2e510e3268d8feb93c4116bca40aa8c287e05a8da01a1d710dd8e4699ab686cb1b38518af7f8bcd8c263d6827066563620804114e0679e5a5f339fed84168440382583493613ed03dd132a0ef41aed641d03f512d00132d7773e85a1e592bf69fae9fe508c54454e885f8ed047649317850e13888dcc2cd4424a7660d2fca581a3f8606f1341433c9966595235abff52ddc2c3c7344757c25c1b39f4acec57d7c4ea00b40ffe9fec52f6e635752b1eb8237f2f4625ef443f65f6c73b33a3d3937f9021a4122fe272b39ec7eda4d1698014242595d48a72fe805d3206231d80db0e0b4ffded36f3e69b6c50b04df1b383ddcc69cb1b40ead616dfb873c1ca28768fc899648da9c42ab20d508e4b66dee11d3f8a3f52f3a904ca61ce0124efd7883b386872b3bfbe8fe6721ae7afe6ea64139deb54520825f1a1dcd6f576c21be27f427145e84c658449c31161064f5c88f4f00d2e2181b09f66e8dc2c92a5299201cc9ac2a9b191bbd1202d5d1d4172c66c6111f6c186b04dd64f9a4355dc564575b4c61e026841e8adad16b4c9c6ccb40a57b09a1653420b24a93ccd3d0c8ac38eb0034aea1432a8472ae2a55e304c94cbe0e984e3c037451c406e82f7782fafb0c8b6a95b7d94af6fb8c5124a39738988899132d0154cd2aa4b3892f648378117ef8a5370a98747a0911b4d5abf6d0a38336892978464062422f817f30a8deb9454a08985bd8f29e6a72e67a5d4c21f6d03c7f5fa281db0789d45d8adce08dd2171dde6d1a12767e041b6d455c9d3cd0f1516d2b2fd2c9df8aa850d8271fdc77bcb014cfd95b169facf326634d1c746f40ff3b45a3746ba481caaa6c84c9bf3059bfefd68e6d5c2ba2bea1dbda6ccd086c81025a2881d7307f26444b642af102fc8986077bc1a70474d0bdb507b3a2623b5e8901d05d83de460b70e04ccc06206c9185094f9de55080f664a48d64e54ef2a76ce71c14df9fb430226599d8fbcb5aeb53189070d22fd6094e2611b898f632fa8881b387316856c652a6ba350799388587b57e00b9e0f04808ca9640ccb9e7b428f03394f0c3f31b0ba6f09143bc8056e9f0a7f5cc7177a31155b673ec395c016d8c19662b060801a88afd262fef43f8d7c284b3270539a5eb216ba5315a58c54874be4ad37b642d5c1c2e7bdbd1be7ad0897c46b74770a4347c6b994a111b9db116cc4a47644a8e7ae2679eafa53186f1d94624c96d5205862216546d8f841cb76a47cb52b98e2c41a5cb3b1a146de83868c0f094b56bf93da1b7a2c20af9cb5f046c90f556c5bedc34b028331db298053da54c81646e9d427d8e9d23d88436cc49b50b97f33abb0e1e3ddc3a3a97d3aa74816c14494b3ef0a9cad41d59374c83dff6e3de8c60b8bf5c2a44a8b913feb891da34aa01a2c4d3f65d141e4781a22164140238530640ba2156225e93d7f288a3fc8fbcee2943fa61f966ccf6f3bb8ef4aabe97daad714511999638a599470b839013af4d92eca17f51b9ad537ddaa0c784a07842c7b19afc25048476eb5e164ee24d876023303b2e1c298037c042b2b3ada889217756b65460e37b1f8ca26fe90e13463a111cd86fff599ef06bc02bc56e6d4290b7818e29e43144f6dc8384de04be802969c1ec5dd05fbe670de464e468edad1d2cccb0ebc2fd1e1ded936dd7d31658842b680e57ee44029ac1b20663d752909ff026f61566be126eabad968a39a338904389c492c28a3e3a78581592b608a49db0c86bc1b712337511f27f6c1243aa7737e692d9fe991254bb9c713003fa1da42bffba000bb3af547fb21847a918944bac227848b41d190f3d056f0bafd41ae66257606817f24e5e0808c83b7ef224283ea77df811c4d9352819dddd7b6d34378f2236cf69cba63f11bf32fcf05ece7fed85fa11dc97a9d3e4c1feabed160926ab4a56189ed732b481b4aad3e6c0d49ed5f822e839535dadb2438832d62d71ab8f731a2154522909e92c63992f438d354750d8509e2a8c4526b991b76a2fa14eb025695f7a924bddaa17ad92fb4b53abf5a21544db3537caac9983beac915de665108310ef09dbe8027e11a6658d35dc5764ea2d073fc12eb9c3412b18cf660f60016c2da2a50228165acc250e8e48f1c82cc206be7d5a3793550847cf1bb7995fc5ec0c7eaef3249606a8e3dde8054f40d7f5b4a73aeb94451338c0691410e4e75fa3b0c15dcead90e4397500571b0dd8132128038a800795dd4194206ce4221b9a8ec7cc0615b0e69436ce5ec456b438bda959aa3cf49f82e9aa6a90fc1e1cea70ea32cf639184eaee871be1dec984caec6a50865d11da947521d42f6d2097e8aa71e217d5950f0f5535a335083b0d7f8141c32c17dbd08a1dada192083adba4f086c7fe7217f23a5a2faa8159af0a867eacdd44b99751a1b7f09f858e55ba8492b4aa8653b1c3c94ee3704e382634c6cab00e06c2850fb0c92eee28bf47ddba79a30b9cdb1b94fd64f0391453de27fa04c901566c743086911a9b0637b1f09d12b428b4801471be7bdac348bcc2f0a45570339a842516851d76f24b3e75a458c2472c873ca820911cd314e64fe8b2752b5e114a1b4339b2697479e3f4c1a719b3feb758dc1bb9d49e475f0f8a495e229994fafa27fe407f4bf421c3b731830c0b809583b594cdfa058ad599f5846ca239832c7765c582697aacd406d8659a20f6b89298784662e1848aea7d46617da4c0902e348e429e4d4b3302fca6011e637f1cb2b7e4756e2f5cea240f8e9887720ec295f39a0f81334f566717f2d7911f76c22e48964e3c643aa7dc046a9b133785d9046d1469e4dd9ba1f25249512c4afafc4ac8cce1d163b38271534575a66a3147150e73b714816d8c6f4e177e7d5e3247d0cccdf11ce842d0240531ce2db7dc7336d7ebb7bb0a34ad7e435e260d101dccc180375640c4dc69ca5bc19b681f7f3aa55ccb20a09cd101c0f1353ecb4a407cb7e68ba41e2ef15a3f63ec460d3d0324dd37b1dd885aeb9a55becca4794f2649672b16b6f813b7ed7c8cd1c92ab9b60304d18de12a5a48f5b97808294f0861aa7544df7514c4ab1cfa9ae982e24234d7ddac234f450ab862ae460e91638a654f214effaafb51c8241303599a693b25ea363d68c2800a74c86ed0c312257eb5c506d0fc0c4038da3b34c9aff29df5d24f8efd25e9a2918b6dbeeac70e8593491c73247e55f4f428c1b34911276ee8f891c511771367c12fd1f7e1b4941feb2acf584319b2cd0cf2263fe4e641166f19660d54784a31984aed605d4a3a9338d67b8df6853cccdcdbf74a38b2a2430b4b42a10a41553aaeef6d380dfdebbec683c7a95dc61f8201435623b724be9f4d8d5593e9503073827f51b248e80c0a470e2ad00dea9e41645a0828d462628d7d3032997d8c7ba050202fc8646411eb9e8d5d30ec14bd8c39741d9d2665475aef4bf5e80e69f85186338aeeb1b2fd4d99ad5ed96ad4ec3c74d21021d70c2b4caed70c4029040e413a1e6362c2f78342ea036a62a7907acb96759fdee18533cb82e640045b202b3d82ecafab1fc5a833f8f9d18ead445a5bb6f03f72ee7b5d5fd6668ea93ea8f40fd9890713e7c88165d265c0f8898680203d019ad9e77449ecd8bf9a39b855acb25f8253230294844beb9aedd7c80d9988bf33d9a8f0ef036164b4a07446c92b2733b5e2b8e5b00ab2c2f715b0b2131c97fd5576071c80e91d8690d3ca689a9980ac9a296853dc03f7f11098d8cbc6f2dc00711b53b3a0ded9d65394103c80f072ffb7c56bccd997c2e72acbfec749e1d7c05b448c2783a948c0987533c639674274ec354f0703f6a5e77f47a40f2c30b3bde7f705a235aa5d48def2e57601f33dc02f743f9b00e343f22aa1f930b0fc27809fe651460c76fabc6f8efa1ad822d8e3d219b693c07412c0430b7e73407505571431eaaea6a08a11431dd5751dc17205a2fdb4aa2033b8c737c57d19a0a772b4fd58ab2034bcbbc750442bff7ed7ada85cfcbbd5766bc35698a73afa14101205c67396a11849abfebf899fe4606f95408d4e4a8c75a59f1e7241d301e6ec2ba2e91500ff74ca96cc17a9d29f6af421372ae9c58c329c966e0ae05d4834f0462825ed1502a85ee5f803f1a887a5f644f942a4c2987bf9217b41b6e0eee3901266a48e760e55e0a1cc3f5fd6fbe839a4d0e4b3316429b737ff1754d97ca0d13bdbbc74a0e4c3d725e340c25b0db3acd67c1e9ded8912b32f86175c110c15062aac96ff7fbea60f36914f73dc651cf698f5a0f5e695f3db746e767d090121766460628cfa88f64ca82131a50c8f3afbbcf58235de418a4c75431b88e8e599c2f687a210ed81af1e684cd57f1128d62b0650bec680816f2a2b60f1d842e4fedd0706e3ff0b4f7b8633e37751fb170240a9decec116a379bef8ff8335343095362252e4bf76e6a0f1d349db48b6461556f3121fa7ca83a08b812b91a1c5de9a004c7e68830043540f3fb31ee197ee9707f1d466e929f6d9d6950a34764fe3baa151e5ee1a27fe3fa97cbd9b21e777632bb59f6c085a85a0333a3afc7fa3e387abe06e9c4956c8eef2ed67f81d5151be954c4250e4483aa27ed01bb8b83c6463ddebce3f2a0351c8bbb486c1d6621209d1309ee7399c7740331263e90aef9d9720a6f8a70320288f1101a7f2699b60e1185b662b51584df4924aedca49fd05e6ff1880a12378c948c4c153b08a319bc52bee806eecc032bf7fb4e1fe27c7676ebd0049a2841429fdb316deff8420f45f7b2c828885a0beecc5acd84945c7a25dd3ffa3954cb776db871ec6c62ca2f4ff9b9d10400ccb7e49ffdf8c4009b917f6dc13203aea5a9ed1b017827fa3289a9eb85e0a669647d1ffa7bf3f53f23e33c2f5d86f7c922b8f2b10b93889ada35f6992a1c2ab6abff8a9e3761d95e883b5608c9dd36e549275d6a9111f5459f5661579fbb1a462e7f3a8980cf9e875b2b75ae15d813477416d5caa320170c831ec5781ce206a08727365b73e10bbe719807a4b5df490e08cd802eaaefae1e0145544e85874905a96228c3661bcfc2049ee7768ad148413adb17a391c7df2b40f78697a5142131c8a7ca26befa1b743591f0a229777c994b56793fdb96a6170bce7b41ad3e43a4343760fe2027557a37c34aac18e0fbb7866859d108734eafae14001c0c3e03a98a82b993c4fec89c4138cd05a2ba65edb91c82f5332694b1fc2147bc4b5dfa83910aaa4a7b2d33aab3539ea79416b3a5f9f2bfe8447eaa1d2aa22d558867414b548cac2bc4428c7b0526563b386af5fc4c9b11ab22a1d499635d1bb6c281301ae7f4c48dd2be0d1926dcd17333e26def279dd87dc04036dfd0a3bd7e51da8276b1f13b5b5adc430570942a6c1c7944d89702a171b5362a44f90e9df989261362222952242ee7aab198369091ff5e7c51b2b354e03046f781ba52fd36d28bd68f0ac374c611c14bf3e1779dd77e33d75a7351438d8cf4e88ed2b1be13456ca6067784188e5ba6c0289e9398ca5c3fd44c70b81957153fd7f376999de130ef8e1c9b822dfb0019053c020b668464dcdafc1454ed9fe40487be267fa9510c36aed9ac8199866830b8480f62c3ccb352ba833f53ced6160c7a207568b707b90c0e2e6ff32a06a4b951e17291d6c1242dbb45b542a79592d21419122982ec4be58777f102774b9a8cb11db278644d0f7f594eff56a222ec79fe2a60de0d9e23f1549cae081942381e1d5215322aac40fed50a49f52ba4602e183cc4bdd2682f46adf02074706da25e5db811e3916443a39329069463b4107e35d56a3e8527cca025bfc12b0407f84057e54feb7f5e39f389deb59d7c1b0c9ee718202aeda7d083c69a8a75f151c834f17a1b27329a73eebdc52041b07c45d4b8f9fe91dd02312bbfb394ebb7fda3a7ec2d1017f1f1dd6060a41d41ded807edf340d56aef0484a1acf8a098d922c11dedde86de189c722253e440bed338a34718f271e2a4c14aeb27f567e73303dee3705fef3d3df02f1ce11facbb4951c6c905362a5bd2584ed8285cfa62e9f825c19e16f1fba17ada9ef4e9e0f4f20b6ebd2ad924bf80b9d28877df73502d1f32c03550c9c96e2157b02051d4d5350e96afac2b804b5f3f16c1ea4b53ca97d1f6052f3a8bb9f59bb4fe07b5e92ec35008cd7004c55009318d59ea45e02de7e3bc552875e677a00037c7cb4d6092a75b005b00137f0e85a9cb32a57d1404419dd138624b41f8b0e7022ea936090390fa672b6508babaa70ee6736c80fcbb28a8f3dba24af0bb6bc09a644bc4f0f9d90c91957b49a11f2afe22780d32747b81ebd0821f778d8d856d81cd8c8c88ed36489adf308c23419e270d0582f2d14996a4a76ab012c6f7a54b80c2aa4842ef7cebda9752e053221cd70d46af7f50e77b36dd98e9c9bd33486677bb407a1fe63da59e1f80d11a3e8930716cc40185bdf6d05e8ef7227ec65f488593774815a5d4c62b7754fe69c973196945e0f3e4d1869261d0fd8fbe5c65bc679b84c365df880531b7b65b44e638a77f8fd5de99c80302809df1dcaf011ffa431ecbedac43e6b85068252c57722e2cb50567be5c340a7586cde1dd37e3bdee1958aff20e4dd93a8fb41989d452e4e385dce2533a6a10dcdb2d01ed97b152ce673c405a642ac0d5b4f7fed29ce6783c86ae378ba450d48687f44abd23f96fb6cc3caa3a02629e8d9b4c1909424dba3b160a5e717c57440e24cc884fcc5a512c3874a66e897ac66458ef2d575a2269ade14e01e50c2d5b7b6b458eb783de29ecd1ff163f1bd626e6c3df18d18111ad88166a2b1b5bcb1b27053903e7608874fc031f0762f5cb4590fe796de762068bca72cb83d191328d16b468484221b83c2c79cb47dafa91c2ae1b280109150bbbc9fd7641dc5b568d8e4371f08b10eec8557de69304f4bd12336f48908b3fa83cc04d31d0a10878c9f5e04bccdf156bf8bfb653fa2dfcfc17d1fb38a4d69ae075319617843f25fb9d36303aa6c4a1136a490284c214aceffa34129951d100c3ae0f3b4fd342a1a48a953baf04279a4cf5f8aa54f3411ab3072163a7c4f9cd4e9e66f7e2b0907439b668f8279e540918b1cee30c86777e3bb5b7892378f26510f2234508b621ddaea246166935ffa5ee55fbe4b10c8635198f96f1acd47dbbc277923b5a39672a2c744e0346a641bc1d24f6d152747a8e65307648494f5c77e14a3404f586fa5b47951ad2974ab8c3a155a44ec3021747197a3942ee13a316c4db48e4f3eea5ed9548a3e425d4ced68a42911719a6e73a2f21eb2a4ed61968652c7b0990c9b80be78aaa3a12c110682496cc2b499f8792385f3a6f27749c96974066d04010605cfa44feab5e290cc00a72aa7ecdd5d7bb58125362b6a73a031f26643a1653a46f2ba45c54138330c0f00e1ca67bbb5e1b55b93e9479d0a1b80b5032e13817a153906cfc7349559d3417ec3762d4c27e34784ac930145018dd143164ac2983f1fa842be71bbef5b426dc01b51fbe263614829b6225410987d7d49fbf2815ad4b3e240a1da00ef040453795b9f8d94d9d991f6b44776652a077c21e04e4aa1867ffd38e381f2ea022b4fc8a6e3520bcae92437ae02236b0c3940b138cb3f4ebaa24a97802d3b0e648545e3a6d6f5daac50cedc1df3bdfbc5151faed7fb20c3cab4818a5a2c51c97dfec6e7ddb152984eaf611db17b0bcc57d932811f400f413fe82374403b3a4d1018d22f9e3923f427b26a36c7a0377ff88e07fc37c49b4dd6a0f4d3ef6f5a6b3906a802471e16dc6219b47cb8bf571d846024ab6903f983d0321e4f418b57ef9f6d74e9e1316119d836abe0cc6bba5469d4a6b54476dae52f174d5c1127b814d4e53ba92eb703b0ff7eb6eac80763fb033fd9e68dd4a1fc143b67052057510c95b42a2db16355a9925c70b9730291cd86b6ab7965894bc8a41eedf9ae70182641a1e80ec40b6df3a4124da140ae84c6c7c43c224dfc411b6601e64f8fdca97767ff3b4e39e103d8a3a87f17ee53ae041742bab3d669fededd85ebf470f74518f7e243bba985773b8a1e469a06bfffb294aea36e49e95a0546be139fe0c9aa2b841d10a49bab851e0bfe4ad11d62570c8fd6ab529e64c9382ba0e611e15f17380022210842347b8dd6798c1c8102b6434277bb87f36db85d64a2396312a430dc3b05cd4495d19c88b44e698d162786d09447d6773598a967d91af604a3cb0d5871f7d9727fc235c79a00f719179570ea130150d282a0103f0bc49a841d941f11587039b45e37e54e102242f583870518ed75e86b1e5367bcaac93945829697e2a2298b41c50500ee2769614faf671b762ce25e1b86ea4565623689c204dea05d8e24b8a0179e5b5e98038af4d0875afe8f86b30639c74a155f94b1c2dd89a703304ae078be867103ff287fa3b8cd1646e076c3430b03a99b58424c170749d06cd0b8e3aebec2d5186e9462ca5a3e89484fee959173f04e7e1d39e5a2660d73453ac8184970e3b98eb9d03b07fd4027782b071fc72e4c7856a790eaab8fb023011fd4c6566c03a202c0749a2c1e79cd6ae289e612ac3ecf7ac37a5779b2cbaa3bd64781db02426e73d9d4cd04822f23c2d3ef303ab704806f8c4875af9bc3c8b182722c8d6a68cc891a85f44f3cab7984ff5b6ad2a0b4fd91806f3c8bd9504e275ed8bbd0899bda43a6e56b4fbcfa835a2f1142d19c16df1b169a06743db8833c1c75c41e932e9de6bed8a222b2918b82f3f70022742de2d389642f50f760c7fc15dc5e3c1930e9eae8095720a1f5ef45dccac05353603231aac33cab1ed237a6325bffb4709da8dd9597e532e885f78e1791db2f349f9a95f12721ecd0475a7178e6dc9821a6e396ee622ed6cc5cfe3ac554b881b91f74ca7d273e9f9c4203204344e18c128447fdbc0fd370287c3876c012b641dcf1329ea21f8a8bca25612b12509400c49790f44bbf265103ce19b07bcc10ef45297d97addcc80dc69b437f2a912edf4460f7580631ea808f176cb7f100ebd799f0948694dad723fba881e345328808a27829195e2a03baa15bb182f5f27b4b552573108f6c12f2492728b5a6c8f278528fa816ca89dcce1001199bb34d207d340015010a5949a5deb7363c6994a467572cb17fd60524f13e928134bdb1e6067ac8658760926a3aebe0e942398040a2f9b0382c750da74fc69cf3656f034721b8ddc724aaeffafc267b20fd8ff6455e64e1992db85e0ccab6771a2bd568ef9707fdaf4c774a74da1f2e646cce8edb218ea6b615caac48e3955fa20f77507810a68b25079d16879ff220d65a95d82049fe60852253cebf2eb7d30ac0eb8c9876a589b4bbcb8bfc5557f7e8a02a21495ff2496c4afe30d4632900d2aa6c4307601ba618dd1901c4b8a4631b52fbba9c3559be3e285ade73e48fc4d9f32cd9191874d4632a521a935d0960e44252d176925ea04a77b63c8e7629e93f39a7997c970f0f3127a5fa94290bb390a611896b293d47cd5f475d8518e69ea3bc0e39c7a5b32b7e81c7652582a4a2d381b94b1017a075df836e0863c9b6a2e2f9a8e01ac351286a42b643a7438f23cf999106035f97d242db693600cb885a58db1c3f36335d479bc6f67c768ecb3d7b918196377037acccfce8a0667754bc614b3896f32e2a07cfc4c6fe526016bf4ff1db0579713fd782097422243f3f22f5115b802b09e0d0fd41195d8a4948a384e89e5c6246e82d4ad9202622053979a07001389f031c8d3bdd2286b8b417bd876154b8a7c9002cf9223567c3513efe4de2bc3f9458ec60b19f15514ab2e0197fd8848db096ed3ab1fcd75958ebad67bd4808a78030f5ae1a1e3275df6884b8cf41221a1b649920ea10f4413ee6ac5606c8f067ef1b33d447e116c7be02272fe4800a8a363c2119d4f24c66a4a34614d2562811d018da15f98a4e01af1c90e5a7294c2fc96ae0494f9de5fd19ef4536871e063ecfa3dd5d685eec8e035d026d0f94aa5d4b74d9160ae4750553f72f1170c3459c47473011d51f44e5ab26649ffbf8e26ed84207bab1b4ed38b10eba2b86897a8a5ba23c71c2db71e27b09cc20a370f6064532c8cae7363032ef36d03c3c923f7224bb51ba83b09e429ef9434047ca3ec8dee4e52442a79dc1c01cfaa4d962b8f03a379e9fe3c6bfe57dd006379476141bcad00f559a76b260f49cd0ce3f75da7a10813e0f7779544e5459eb088b44d82d00a2a5998763811a3a5c9419f425d38077df2de48de09e50ed17f0820e6f1b925ad2a6580c700f0995935e263eb25aee3a3b62c94b762ce167931b1dd0ac06c8bbb9a406eee11994c287939b06b3523f2a9305f7a80bf14319c267c64a8596640f8fe47838b51d210193c305afef9c481f6ea4031e5cb0919bbfeaa213f32b03901c4f806afb7ba66f392ece44a94ba460082dc9a88279dfc0cc80861419b7e083cffd3b193a4257444bc4015d59c4ba54f9152c0bccaaae488a361995c98305b577ee1bf988b4ce126b91d5378504b5885c09dc6d385f7c6b5d818e8adab047084d15ebf4d2efc12fce27353ccff8f6f07b8c67f50bfb253628a71c321d7c67586e04a9a49b491d8c756935dd62c24f0352d827ce5ef8fe52c7996cadcbaa70e5f0a9dab0c4bf60f587e7da251100f9d9b10c2e36ed68df806f88aacd630b97a013c8b5ed93a0de880987e80f012eedb1191bdbb869aa919d8eb197b6627b08548fafd83b8bf4b205ade8740a45bc48661a66949bb5f209d0519e7d119e9b5ad3a30ca599af31bef345492b2359c8611266226e49deec3f20ab5effc56cb489f39b419bc52a5975b029634ab1246dc54abf1372d66bd891e70e08097f2be4b4a254afd1776064424c2ce48fba278b4816f880e7f79587d2b92058083898c3f000b69375b92cb7a189581a8ea9261b58eca9d31474fb6e0e290e06d918dd4c862c578e83b6682c482ccdbf24d0fc285795953ab5f5470fce1a10c100932828b019c28cff3e848bfbc2b48c59c9687a85107d4958c102873aaa1131ad62f67d358f32556fb414a7a8ef6a8e615af1bfcd7ce60563be90cbd1df0eea2f04d86c9687992b7d7acdc5198825c246e099b4e4fff21fd25d7cfd4abe5217d8d3cac7b929379840202ef37c5229f2630c30a094980284912a62bf61a3382bb3ec3d665d1efc7cc02a4b1a7d78e199e00ab3d80bbdde9801462f74360ae24e430fe92ef4d2180ee421766458cc78a25b10e8d8a519a8284a7b9e72637bfd13bf082eeb2291bee2d50190b4722684734ba2e8a0cc66f56436ca8ebf4ab7a6e38ca9161c68f02515c738ab4bc6e3215f9beb4fcd85f7b7a1326974656b3992bd046b8b2c648524c7115515a7b4c136d1a24f45847628e7561613bbf04d7916b10aa744836ee790e2234511f7224e6398f50fbc06c603c116a46e1d5c9eb2c0108c0467a76554fbf73bcaa08b3e23111a68a773a00f87d6ec0a3c3bf0186268383c85aefe9edf8ddec8c2d46f269b1f543ed08b1bfc2642d3a0cf7fc5b171b899443a391ce2212296f3f28c9a5c5f3a6eb21e807c90f7cba441c553bb7cea68a5957d7ee14480a74df7ff7eae6e521d2d8ac6adf973a624972c8059080eeb7a855068bbc7a2153125a0b8162762825c0a1ae00fef73301a7c74d7bdbc43f53f0ca6200c4d18d0711af07d3fc679896e80c065e996ca03d9d2eebfdf51393a6d3fff7848fb6b740027757e917bf7e79e8a76b04fe5c561d160f7b9ec3a7e537e5e7743d497b5444c48571e97fac704d309223db6292d26864d4506f0905141d93473fdf03fa9456519c18e4b54ec5a5f48336ee56273f914939a6680bf928fbe716a3aeb5427e960f0c285a97752c6f4eb7d5eb815cb0fa78f65fc7dbcf0fd83f0def54d35782d21013f799330fc414ade8e47a35cd2d8cb210d5a2152978837769014309b7dd188038bce39bc6a5e1481a2528529beaa22a4e065609a6f3a19d1d62e7226b233fcc29193722ce60a499c94430f8bf5404f214ae89f784cec16d83bbe24b2ebeb27d22101c837caff997a257e26039c7486bafb44e09a24328d62af4729b7ba0f45f5b19e8fb4120c7428c41d03ca96c3c36fee60aa57d36406df8131b738e469af18a19a3bb521ea9f6c5719ef484ac81fe0125e3f37103ddd710143eb90fe8bd34957de031a83b1b1c67d4f98d1c320831dd25aa40143c050ac8dd6590e4844f08afad7625bf263967f58e9a5d0313a9ed9ab7f7f41474c5033129d004c8fa7a68271a83af9a06dc5470b27223587e9502f52fcf965f80a0609a41bfe875bf20bf1fc29d000a7c21211447dbb3dd7e7835b5f6a770253c4c7a6944f7473db7fee1621323b236862fc060c825ef6ced085f897ec74a229000339573226aa4ee86d27efc86487d00fd2fe45770af8ffc80c296409c921849bc4eb250203ad62b5eb8182049ca93a47186f7c235073b4a56fc6fad12ba0f121cd505d5216042d0a8c58234d2223f6a5d6a57ab735327f25675f4d57fe6f81c97d19bcf2bd9d37ac366aa02306df345a41b730071ce85d13b4da4c284bf0c5b7197fef9c667ffbb2d4035d47cb079f7d19a3c09301983122341fcb598bbc074b37c7e4b552922df06f62bfc79932c9d748e8540a4ebd06ade346ab024ce88026a8164d0437d5e3a76827f58b693bafa1b0da8f964050140fe98bea5458b4ad86c4be162beff3c53eecc3a1c55b9e5685ae1959e2d37b2139780acb06029fd2e12ea799946961614a072d3b7b967cc64c1be7a7f4dc1ebb537b1cba64b21c603855ff083b02e0c2d2e3c9933834b48afe061cdfa09f5b6239dd12c23c9391a22bf0a5c3559f68923627f2b4b4736531f938d60bcb7f79ff6d56ecb99f408970a50b8cb711584182366c97a04e2f04bffea3940391c38756576eca03d59c75c6e39ee2ea265ddfa5ef9f6c603ed6a8da54e0651de9aab5d6460ca269850e894dc4af9e1f41f84768b3d407f5343d0795c11dc9884261f1e33ead6b4238ce2d7c672cdbf0de94474f11dd6c048ea25a9b72e07fc29c0d10458c97d02aa2cebd149898709047fc3c438bb20f88c8189b9686f26d518bea1a8234ca35049d80a1cd8111042417e12f996f5af8686f8b677ea470a7b0fd6729ad89508b6450fd77524e9bd04d544ceb8c8a44d0e2b0e428ec1c4a7c2a3a1f3d0dc12c6fef8ecc4c955a250d03a88079d05f093bf8800adf6a81391e81639a671ce88c4ea64b04105e3c68d828cee628fa8dac7ef2bd166ab69e6f53dc1d73be60de24223cf1d2840dcd07c0427600406f0e8f448796a53f09898a78db7d9cae473b7d7fd44c4229b295ef20f5a44a71030f0a558575333b53f0ee9309f789f70f51c358c1be659ed87c52a346dcf68fad2bae4e56c752aeb74d304091bd66b7545a179bb0e6e5e2eb28014bc44232ea77ab57e8a649d04a3b84c0c90a289f2b9589db0647320edd60eab0052909e84383e826efd150018ecc04c58041b8914e70468592e523784d98e84901cf950fc8cf2e62ac05b6fd05076d44d94d7733ce31af5913626422c94556f9dfe1d4784babd3c621229e9c0ce24e129a3047d724e897951fce6d185ba2e08a5d95739b69ae2fd38fc8fc49da07b7e3776bbf2fb1be5f569e7537586210ba0ec08205a213549d4cb6f7e6763a971d08e37f212e08b9bc6f8d000f44989dd5bc17a1bfbf37837e4d27b9df379ae494459d48cfc700cfdc6be2361c20e56b4f61d3bc134a2634143bbd085a72ee010d123eed6ed3bc0671bc2756e17be4c27f90e92454827c23421adfc4652042361c00edc4661cb69fdd7fa775e48aa00605b2f07a0b2d5508c9353316467d1b933b0da4d7a953bf2e7e266107f3a8f2087433b235b632f4730579e1e4267484c94d6f11b405317f9f2326c0218715a262a85d71913f7a90da5593cc8fa59e10d64e983b47d70c1b161f760473d72112f4827640415ca8a7e4a2126bc3d4c5b8960308f96a5be5fef34358b6a510530dea6ade82ba94115b18ae685f842abd0e79e3824496bc33ee68e1563ef510172de0d1bec7229bef4c05b46b895dd5e13ae4a7d9d52e244c7058f93b4d53602fba19e4cce311e104d8d39fef559923fb7b66196c57fe88ab241812270f7c867cc0b2c450e11bf5413e55a10b1f1eb244db68013ae3b4486bc238cb2572d4da6b0f6522550d3e9c43519c54a49edbb32bab7acb0abf90c4c4dbde36584be96b2555f16c4fba3c77c06b61ba74d9eda2d25f4c43c777f5fd6883951726c7ce5733078325709c2a98f430008cba3e1bd63eeb9a7f4297c9ea50e97a9220113563a511e347ebf666c2ec172cc2bb33b2abbf6033eefa566e1c992c7b22516e9fa1b710926cd6ba39d06285aac70b31e5360703b2fdcdf54487719f8064fb35a1ee711a6a76f7b17b7a3d1f6ed2c37dc29063770f4d3298277445045faadaa9ac7befa78c67303d3e109a30e139d5063ed3640a449a087b0f4c7b46cfab85d6b7e20ba1f2f9e4327f514a134423710bf5b56d7ec2174159b3a979ad710cbc3690172d4e5963fcb182907c33f350e17c960bbfd943a285f392a11943c3afd30c6b98d5a681d93055d83e5826ab17d3f0bff08feb979072e4e53020bdc0947fb0598a9fc53da5817953392491c2fff2dc802eb38b8adb09352864371efce6ca330281a4ee3dda9c3a6a7ebd33c85302669542cd5da7db6d76ef6770540b1484865c0bc421483bb5b234216cdb9d19f8fa608798000b0eb50b696370bb133e119196ee71be897508b0e30b00a60864f3b9e2aa7276c46007e02bc97cf9d1776e0c92cb75857103a18f1fc7434a77be2700c22ecc5245909c2328d4d113194b3963838dcd4a045ada6a00db80e4388d135699d313a8bbb1d5dc5e11987386a09233259c42681c65ce05f73f0fa37b09b7897a4ba17f558fcc4ac0a9ccabffa897551ee94455ce8b63d0fa4840f478c832cd7664756dbd6082076f62e7463d31ef598b052df5223990b5ecc14c742a741a54a166328c8e24cbc2c09bb38bc85a615a993be80a98c768565455afe512fc983a63163a081f760b3481d8a99b30718fb7fef4d93cef105990740389383098a64bd6599ac81e728077d319062d613c6a18615ede88ef048a9a99de381cbcf93bd7d3c42ed2f9102240241975ce0da19a3a7f62ac79bb692aeca70ab99c2038e394e3c120fcb96a0d2f630bb51fcb37690cc518fd976791f49dc1b1f3120d22296fbc2fd0e1b17c0395a48c6a063f5cd9757ad1915e28ef24c40f41767b6a9eb574fe698a5d4115877fe8d2993d12bbefbf84727a125d87aca6cf0fe0290daf0963943c1f8308fd849190b0ba84aceb53336e29d9a1ee74a078e6c5c8b782b71a8b905891748e650d43df410900b1921645186fb4780255db16ca463fef686d88f3d3bf9b700bf20ee1189ebf0ca8342a8702138003c283c021bec728d1e1ff114b8c2e3908693d7828101bbf277452cb7770a70b0ba8dc482d27528722307a6297dcae60f971eb974e4b06c99d04e349ac31c2898fa8b0ecdaa0b4fc437507489ab58b8bfb40ca50e01117539b5000ecd12b65feab3d0ff9ffcb40381075052aa4df6cd2c766413b886c965dc3d19d9de459a3c2c2c43a49f719af5d51f3029f7f8ee05f52cc05af9b6e7a668921fbc564fac8bb36f7cedf21060edf8d4f3863e30bf86e2c79d2005ca55b526c04639932b0bd0623bc711aec4f5132c0aeb7ea7da4323781c1ed62cbfa13d7361e5afb452001545cdcbb4a2c30005d8927d62dfd393b982f6cb822be3a3ef15a260223d9d3d6b81fe40e1f3e6ffd27ab9b65fd157644141e92180846aa90481dcd65a9e4c41d460c1fe7ca1506561f1fb0581e82a388d3f57d6c50d117e622e8c7841b437fd5b2993027609a2a62b05f720f159dafdc8dc5e08589d71d310011f2c4dbb8efef0ad9e7585dffe708c59798308ca8028ed245fd680594fc5bd3762b528179260889e67e028c0387f65fd61cbf707b7fd0dbc733657c25146bff6e1c065ee7698eb03ba9a7082c51841dedddbba9a413a7744acb6bd39a2736ef13ef6a8522c0c7a28aed312a96d7f78c771d9eb0c7c2379145974489ae98dad90dad48f8ca71e4f974457b72b43e1bfb1269fc8ec00c65dbd81ca342e1481b57da64781f8d84fb958dc5bec4f145eea417996c09f6d2a557d55f5647ec056fa9485a0948fef715f060da268bb45936a0fecdb01c7e12bcbd67fa6516471977fa107b7baf04aa6a75d072647bc94e22f7d2856acd9e5d3140d49d4ef61b939b43d9e943b64a41976ac8e478cb52e683cab12df64176bcf5b585e5ec27b7624b3f6ab351bb65a3140383a63eeb3bbe820f9835d83dcdbc2cc5f9774b3267d611a234dd7e3171d539ef859b58a138313c5b8024451ef7fed40a98f1b4f3a8be72836312f9d81307f03cd2611a2025a0845029d838f7be2f405ea11c8636c63f9466c13e92ee810df5f1ee73f5133be9b7ec1f34fecd3d21a77d0bfcd3d8fd3fbea247fce9fc0bcc7fab4f4869df72ff3476ffc7afe8117faaff02f3dff813923fc57ff02c7dfdff01cd6703a4770cd80d48014d0f52a0a0d2d50d3444725a048d9dd97d0a22a84e5e1d625b5e8e0c03cde3ac008a2f9807cc866f0d44ee78ad451dacfdd245cc2f0f58ca47a5a3a2f3e512dfa90ea8b4ae1991434bec24c056a7597e08700cad653e1b5f7f7a2b5b51e0205c8147dbef3cd87f278685ebaf722c6f13c850732ece313342f164027bba318ceb3e335f70208bdefeccc5cd220482f39162e073ed1390a325a6b06a1e4fa83a7127dbd0d6862ef46f403ad3389a76890fcec15d02a25c7caf65e30b89eb20a9d7ce17e6bc507a9469cbfe83e230ec52370f3030cb7ece026deacb2a6079e0fee93e9c8aec0f08402b911af29212505aeaf1818115c81e14da5f62a5ce8fde3876a3653aaad07183fa135071646a018a593a6830a6f03c471d800da9530c6d391993a60a210ef5f73fa78c709aa1a9098b6fed0c6182d2fbda8d5960d39c015be944bcda09a28076cf2c3fb005a5b87918b94df20656763e914a0bf24204df792384d2f087a4158933287d544ae02d104354854ee51abd0a97a04a75033e9cc74279f056498ff88b6c6ebe0eda1d9d73c38a587ed03b62d2b87db772fe194a2601f353d422803da15508c7a3f0399678edece872d15a76004038587a41b4b0a770446e7de513d9cf271843475189a649ed6038a376cae103466631346028534a15c35fd8ca15cb06555debe9913e02879cea10bad14818b6979ff22d12b44b474db11312d8e36e205834cc3c6ed9ff0b7d1d721a3917f8468859fcdead20624913c1d6eb0ef1e8d4316a30c0124136124601f30e624f4d212d64f6e104c5e90e60bd5ba3021febb3ad657d5ebfe3b0300d18d2cbd2be0153953d4d4eec3f574ecbbb414a034b3289cd9de90231799bad29da409008b1d0265d3b06ee53023e4e03b83d343b5a72aea1a55d78014114a27d24de2af70454dbe2398991b5404c44c830f077d17c2415bc3f30f4abcc0165f220d04471d84e4369c10e9f0766f8be7a26ca0ac5d289e4237a6885f331f7b221ae561cfbd59730fd7105398c79e97ccebf0ca6732f57f07c05b1bff1921c8e7aa93c465e76f8ee8a8fbfafe0d6375ea2f26485f033fcb2989b7979d1db0a9efeb4c25c8cbf94ce6bad5047fc708ba14f1f75559ba15a684e08e5e9d6802f7bcf0ab5b33bd94b493437743eebf96772340c4bac8711dd5c5facb317a6666cf38eaf41f3df75aebf356a43d37eca76d2d7d71a8aaa75fe4ce8b9a44527c9c9660c1075280b7edf4e4129b13ee13baecc3a14edef91e9a41b8b154d02a1c10b706a51e7fdb24936c0b88f84bacf2e612deb5d7b37517682687df0015441b9ca4996d78933f650552722782a113b3ded85a50e10f1eba1541fef016a1096cd46af87e33d2d19cdd5316c382244bd520f336dca15cde44fae1e4e12e118c3dc8ad1e8539dc0fee53f329d73d85d062a86ab5b536363904bc8343a9b77efc23a12b1f3c65b29051c6c0869cbb5df3596bc24db3563b24e80b743de97aa09981849650d42837584358685768c779011f328df82225e497e23c88339b8cb78d62c2b6b0a793a225051b2246dc19727486a963578cd84c4d8c8871f8853c1049e7831ac637ea732fea0c55d1973de0f774ee154f0d38f5da47fadbc8bea08b090178e3541480de4919fd6a04a96dd1ec0ef57ea833372268628589dc060900ec0879fb1ab71eddd14c13d782f8433159dfffd47ae038b7b2a324bbd5a2f316271d2e61fab0ed3a0ddfe8d6b6c9494d5b03bf2df885ff761592209e555ac65735a3d6c3f98691087a458b08a1fd49edad606b9d29a4b1fc8f4786da975e4b651c05cb6becebdfb0ab28878c84a857418d417373b225e455d307cf5d2e2019f1cef94fe2a36c592a5425ae12ce9b058036c7fa200360428aeb9876bad2bf84f1e6b8d541d3b54638594aa1bd76edd061ca8662fdcb6f7d53da7afa30eccb2e8d0062114b939a982e5507b1cbe016fd5331ded6db8f055c70942611c64dc2cb1769d706687cad8f85d4aef2ccdce01a789ca7b591991473a8fa7ee6e118ca6e1fabd845a8d4680c80dabae347963a425e6e00359f6c4a4c6ec32304248929a2e7686f80439292da8c045c6478f7c283a27f6927cdbeeee192f7e574a22b339b1545472e9a559eb36212424fc012d89f4e1ea2138ff53133f56c892c11ad996e473d266e7e520bc20cdd06566a423572da17ba082434a8069d70d509ce4d8be7a44a0d83f99122b39f0c83540ab1369cb0228434a20c12696d4d7f3b75893f1b28573dc284f8e23a1f75feb10eedfa35542334849f99e01aeaa2df97c2ba99a0f72b661a70a1569decc9ed6daee61683754fb44d641586a63f5dabdba5efd64b04aa4ffd8c2352fa46ceb3014969130ea7112a4651f788dd0abe6c77cc555e3c8da148fcb0fa43eb321c774a2b9da03d56d3155ffdc1c09d880e1776cb01f25bd8f02225b9abd0b861fcb7ca2df6eb57573b9cfef8d6e8aa334bd457268492de2ac695178df0a63496d3fc4a3e65edd95d26459745c1102198e6149f4de88d0bb47fd66a2675bf8feeed115ae5051e712c5dac4a9d6a7b78703f77bd3ca92f1ba0d46bcbc88d66a154e3199450d8cefa8be5f9112034695901cf12b941fe27889826f9bc2d44d61e61cd5480b147d7fbd448840cbbae97ea53188208235369e8ebf094d58146423b8cf7b90436f0baa42a0b3502949d5bfa2bdc12641172d6ca2d6db8f3588ab9fb408b4e65a37141c1d86c27fbf5e5e6fdc1e683b15ff7e7b793d5274d186473324e16615683c41deb4e1590202b69a0a3c3297427cf8a836728d3592d09e0e14a3a587dd8626c6c93ce383cdc86aae99f31af49e02f4c44094120ab86de6c690b830151445373e701112c50842b1e16c9e784583bbff8b2c3e32537686a91b88b5dfd0420dcbf904f5b3005613dfe6a71e3b1b2bed13f47d156de47755e56cb39d3a28fd720772eb6887a75e37b930c42ab00801536f66920dd4246599d9db9121e05557f13e8b4662ebfc9eddf7b93fc0d1412bb164e1e1a82b00023743e890d8108248f1268af82f0b31c3a3359cf8ecdeb141a1ce5e8044865cf27719ddea796389537e737332a84ea42699fd1dbb0b327fca8ebea810008a27b3a249cad51e52abca3b93982f33a4b38f2af3fb9045440a9e1937f037ac596bd7e8cad8409d69e89a03907d64200ac5b66cbefd08a8f4aa48bec160db815c0d01ed76cd02955dd95b44b962f91225904149f32365f0bcf56d11f2323a66726b75ce08142e52735d10acdb383009e0d149119bcfe197d0e370974389968745ea644bad04a0b89baca342d570361ff714964ab493e0fe9abf90e20290644eaa498aa34cfeb5972a684f701dbce40b40948f850f1c5cb2b010193c9a786c92c1711ed33d255839231e818cb24050a7619e0f7f44abe59c78e0493deaa8e65ca9117d9c54041fbc6740d337b5a77af4e5a3250e05e58b870a73897448dd0fd2e409053ec306236c3a751cd1f1c86604e59808bc0dbf8748d7dfa326d137829dd0d5eea3f89be4d7a3e6f0e391f514fe7edaa0bbdfe3d3d91f75ee5cf46ec474fe56822067e5b9e404380a9b1ce193e512c7d4de1873a5f230530bb36522e79a18db229c1cb3e25a2a4574ffb258a3b8dd73cb992f24e3e5341824bfb608f9a4bf572b62e1130419b9c0c47865380ef60b8e9fc63f2868500cfe24208ce66b4225d2d5483ba378fac90001f415aa43d02c08d7d9b9fdb2d77986f3e1bd18ec8d8ae3c77059e66daae188206e91366490e07d262dba4d37c14b20cd4bd3c0a3280bdf8842378dcd7b844787cb4d43a2f70a7bbab5fc8cbccc546cc3eaf96e5964551625539b4677470100768f5bda46e0e712b11ae5a79affec080ebed579059c75627b43dfcb7c97bd78b91c4c0d7006af4988ba4faf1cd11363b39378b1b9c5cb931939533832ac51677860f18c531afaca64dccd1a9c7fe0f87a390c7b79e42af55737cbfc787730f8a101c8c43802749c7fb64830274d170b450331f050ec75e8a64067e82d8bcc388d5c5c955c52ec94bb8661349791e21d3ecb4c9628cd95ede8e7dc2ff9b6604d0850f747c67a23287bedd090e724b6d493038d4d1f924945a1009c947315a9981f2795457b7a3bd57a1db79ef5fb553a405be69c8eb868af1f8a1dd04b75a6ca690b5ee4638cb2d3abde1da96957c2c6b632ac879b684e135e0ca7fc6b782b859ac77fa68802cd2da65033c9041f03d4ce54df54a5632dac65a67017e3cd411adbc1fa5b3e5e494060485e427bfca1157338a10b358f18d13cabc75843ff19bab40ef37a964f268f38f7f37e0bb4c3b40d35e70629e0d6691a5782ba941e4501ba41f90d873305354ccfae8073a20998459a05c2599993a563f2761440c26abdbac74aea11b1d8be87de9008d847b9ac351fa890f89aecf0e87fcaa9f09e0a35c38c1c4f5982e620ddae5a7d49109f934691f6f33b4758b2d4e28ede6ad3a56603c5b5be0197ab5566a31d5c3646362405410ba9bc00f580a353ca1de5cd8e1922c043acd79494290ac873a18090265c9564bd891a2a26162208fb5afb8fc26b9f54087e1f493caf6cfd74b64193cdd5a37e7cf5bf39551685525ba137babe171bd34cd6eea591921093f8ccef0b9ae3439ad1365fb9c92b2cd7c660334d97af9885431d8ef88a41be2ec78c092601213b4e19f8dabe19f17f3dfd1fc979afe79ccfb96571aea4ef35d6119532d6daf273287789978658a0aba7d0550419737febf6bc2e76d416bc238bb348e36ad80d11602749bdb74caf22f3fc5a26d3c0a2b7f4eeb07e08abfe6bb3421487fecbb8e845fecacbf26361d5a348501c68a279e9283d281fde596e6af3f553cd3ac02087bfbddb4294fab3a0880e38f7127382e9eb0ffe7d24bd15d862f57264436b8c8430dfd5b3bcf86e65dd0bf91f09a19e9dc0a511d99ee2ec3952a307206ee00f212a300e257a60872242600f4c24b00e2162201f2256200e497ca00e238606e23829cebee3992200da3ee1189942b8c107d9a934bb2bfdb783843dea639f354612bc46fb2b55893362a4942a92323e513e7f7469f2c655237b38422739932cd11dc36c04319da21031e61958b45d4ac135414c90d598c698071dc83dfe00b962e32d876fd6616911caa3328bf4bf89edb4e29495ff6a3e51cfff32f37f5d9989834d2248a8425ab0bef21152d1bf9e51bcffe00deaef44d6a64a9b8369493b77942e00f2e7389e461eb4cc2bc0dd360539781060509ce49d8999dcbe958ca4ed7b60fd338d1daf3ce8f71128501c61c52960368c346b1a5779d1a3875a5189787cd40adc38c9c85c81a18a52ff53c62fe20d4943fe67b4dc725aa1788c97f3ebc0680af79614f5eaf43f488cf3f0315b5f2541a4a4106ee94ce70d9e6bb2700923552e6ae99aa3f460da2393cf9b0ec351d6d8c30e4c073469b3f5be2ebad2f87100758d58c4453eb729e3d1ca4a2a08dc15258db78754e299e32151374457cda9c409b0934e275db0c7ef3a32a9d2a4068f3c24a77b13e2b541bcc103a0e8d3d0905695f9315759c9889780b6a07a3c5268a5c23c9a2833ed27e23b5fbd1c0937e64277cc0cad346f0dc15a0a56da1e223130a212e54f73d9bef755997868f8f7275d9a8cda5c080a0b3a69e82794b2a8ca515a774823e967328c25a543a2b269dfc78c75deebc41a699fdc6b69b7547ac0e622d4310e9313a3957b4000167d95d460b5d29517853cd1cc733dfbb2e41110f4010f63d501f90739b7da562a26e4b34fe6a864e59f78aee18b40603dd5237a332c82f899133a5286e809389cec5f75c7d16b872a30d21f0ec923e9cd633838d1484be5ccf1a72db62e763e7c02402dc01ddb416a7100dae791ee5da1ae47b6ca5ef807b04a2284ad4f1cd4388e2d7bb4a64db2af312608ed2a52fe197beab029e6e44e1a14fe8a2e50e479660945259794623110d15403c8fd154fda72912da1901c266478b31fee04bdfc67d13ce401a70b01cda4c4adc0480ea1acd9149415a0797baa645e54d85da95e1e031c4c45ed2c8e1ad157219b90c5c07284245702482a16e4f4de784e4402b51d4425705c4d3eec34c44425d90c835f3114a81a06c66401f10d486aa2444c162059d990fa8382f25205d4bf29253d19677c21b142f01479a16ab4f131e93ea4df6e9b9aec083f0479336300b2d43068c75a4044db4b45e3a7426754351f1dd99e962b369dc837b3b9b315042b5a3446b089e0cad326c178500bce7c12ac39864f3155579c1643dbb4e2b8306176d1b5a54769cd291440d7524ce18b12c21dde597e3dfc8eb5118481661e4bf3df821ebe5e3b31952172cf305610448fd847c76ddd333c5d628954587c67d31390b0c5919c6db555951fae2b06224beacb6dc6904fc0f5ad6aeebecd136da0ae0683bd93c3b3aaa40af540a98137e1e01a7af2406ffadbfe76a5810ea43b66abde0f6bb3d0b5cfdbb61cca3245caae2b5f0159d992aaf73b3df71885a8bc48a9ce2c3dd0c0a51edcd30e1ec0dfe9b12cbd15e4e4a3985d60be85a5c8df14e33d37b969ca6ec355c70314a5c2e131396131965ba01f912c45cda112d1c1b13027293d76e9341e0d4c3c834a25d1c6ac9a267f17faf483e6f5c0fe5c3afecfbdbf7e6611cda1687257f3009c901aec1dab43fd40364af7a0b6090c3317cbcfc05b848b4677189a9035c1d6890d698e82911c80e3324833e980358632c24b9c57cc065c8e01d210431915429805402ccfbac550e60a83ca9d2b5e80d03be9207dadbe68dca582ea1f889bff455b5f54c7b5f438c42690fd06adfbd3fa0594f40bce21e951947ad022cb2cd078cd85324bfe327f40961746633b208121bc9cca2ab7ae8706f114bc514eccbbb900cae3e17f26b772368c214823d1bec4463be08db40a30f694e5d496bd142051b891e48e083e4250d809c7c6d00aaee1a05302a409a0b0f32c06ed8f1034eb42d5cec448099b1dd3bcfa7925b904541577e1f2329b658d68f4366e32ce9a381fa53e2bbf9c3f41563376b30cc2a174c1a71d5846447c0b6be1e4bb5cdc1b22c5912ce9b589957e01f031356f623478c7eea9279bbb4d073c51d816dc26de57290e27a0c125e65ba952ac514164664025caa6602416263c23a4112c4bc659e6efc6f3a3af10baf3eb19737228433ccd50f3309b418995999e264fa003e57899e59df81f1516b04e48cdbbf2c880de7b8453688f02d13b86002b3641251c6517b86912fe5afe4964e87d2661bbb4442e69750ace6b78847f0b4b5a577e060963c3609fecd088406d2087f6036b309de2c1c1a6e92c560f089ebea56c552edbfaceebcf148e427cecba6b3d8273cd6dc3b20f2551e47e9656af15b548cc63e27be85df2412647bd2995b62636613a1364e371758d80ba4a12e17f5045748ad4e94db8384f9d7fcd5e23f31084e62d1c006572f423455aca924841c3229146f544f3c0dbb63a9569a808e9c319acb0706e38e1ae20cf23ed2228f0acd5809ae6a6340a08b0d58f84bb34276e2c218a233edb791240f1f5b387250a7d1b6c9288eb588f178624aa08f80ffe6aac2e178a5cc88bedb5ba1cac1296b791b6e0bb68e04bc27a21a26124060a183a507c9c76d36816f1e1c10901b80b08c14051a714f35bac0506e8b31fb7f29c00313f01e4c561cd9ff7ca3972cf11a9c9e87ca344e9b776920734ff9087f888f7dabcc48d812dda3832c31c73cf0dda006842896444508cec8ffa302840191fddabfcd28a8ef37984d43b09f64ece1e9f142a220de401dad554752f07901b9046bf04c888e46ebec987d13a288389a1c066af348acf03aa33d730f1d85df1e2cc157535cca46393c70fa59214488a66a222cf21afd700dc6e10d56568bc4f15be63b481d3c444cdb41c24d99702ae4701241a68da5170b41070a6307c024dba87b73e5f442dd366d3c199e2e38d0de356976afcb88123e8558ccdd76cab15c8b07b75ecca1685f3d4b3fed27d9f4203914c02d04aabb6bd666b2b663a978d7b43c3b853696a5ebcf92720521372baff33d0624131f04eae10aac3a5c6b28767e79388d947f3379e582de7f9d6659baa1aae578a3f047cd0b9d54f0b184d3cb44b622e697d2d85aa9791d698fb16b34dbf236ddea44fc9ea5b21e133af42f8b6a782a4193b62063c7408816cb2d2f9c5b739003125d9ae719127f69285270e2616d129196ae38203b7c1f8be4ad2e04c1ce515a74771da03643bc2309cc714eaa7ff47c761e337812cbb253450780f2092b92d90fa9d35267cc31d6da9f0ded08135d052229d422d0d02837075b85417136799181bcaaf1e1644c023318d78d4212a227756ae64d8a24d4f244aa48e6084a4ab9796aa0884ad78d206cd850c4953ccec543d0be6d1318fd8a773a6c12ce627c27f4896ac6ea3445162181e45f0c8638928d275b4aed664f7aeaa2941f0de4d64140ddac132ca7d56da30b2f5858e6d3d38d17bd482ca5ee1b5a75e1a66a9108d5024d155af19b5f06f9108812ef6f58bc8aae75c74b3e49f372543231426e1095ae35cb3bf6708813a80390b6b9acca67b7daf1c5a84e6638ace927a1ad57c443849059950900004f670cc45ccd41376e92d40f34d56715afa72250f2afba15b2347fd47823bcd732f7203dedd17363cd33fd8991e726f1bbc781f1a140330ce405084661870025033a6dea791e7d03550c6d4293380a04d3acd41e2e86bdd88438f010e23e8aad67913e1fd9768552359ba4d75e636415da48bf3283156e0118b3e183907040f243d49fc455d7cac5f509288746b4980481a40ec3524892fcff3ad4d806818931fa3da16f8a0d9f00b070edd2f99f174deb6f453b181ebb52587bd1150a5bf7f5fd65c5bc2203f6cde06b0721bfdeff3d66ba08a2249700e70b2a81e27000d88d7f16487808d6d270dfde310807a1ed90d933038038e86a67c1720960a4c86f6aa4efc40f0f19928c69ba63555e343d71e905868a82cea00853252f1c5ef0b160f6962e62c92b20554c7131a3a43719484b7bfaed07c97bcaef9b86ae6b3f71e609d6a1fbfd6c526a14437812d6e772efe20847bf5722dc5d9a40799505659202f05240aced66930cfdf6e566f4c50cb050a7f2241decd22941a091a2010ec8b3db37c8d32bf8430f884cc7f169e225a3515a1ffced7306a933a30f1bb22a63cd758ebbb523e9e2bfc690df0781d0cc395f0d9325e8c02003acd7b4e14680d48d77f6fe00f4b65dfa1e9c2ae2e49609bd681ee3fa20980a46d7c009935653869dd9e6ba6635f38bdc31731ed34675cb2c41db12007f1fab33ee26991a169900133fa1027c872ed2a984ce25a691d6340cd277c569a2ceed53e28a8220949435f4d5b81522319e0234c885f82972433aebf089aa12f32b073319816c38022e8444dd7ee3619f6ebff134b10fc63b8ae6e2d8a9304ad9efa2ebc5f467db2419a731aa150f30f6442f15a14fc95ad4b2ff84c19cf358a0b5d0c9d244a641cffb485a00551280e27d35567d339691b94c451cbbb68a40a2ce6242a2f780c84876f46d2fc11a00b17422eae0e6a6316f558dc0e5cc01746b17bc96952701dc5009246626cefde182a1a54a5187dbddd4d79999fb0f291c10ef934bc50dcd3b93760b21e6d3ae0fbb51602aa567e76bc847cbf82b6d79aeb09ee38b1abefe3b7cc0cecce8e1dd8d7e9ab6fbc9bb65ed688c30bddf80b4c804c1d74937c62a0decfc87f08fddef3874a7768130185c5a3b1e360deb30eb272848da03f35a34fc30ed08f5fb4007400003d326ad30f643bea1b08a616076aab8e9f208803c4abbc713ec50b0a5e97f67553b9d0e759e046b6f12075e34e93ca4dc70feb6a2e029178a66d22863e49dea53af33a505e46c9753ef9c496d322c980323ef20a0136a10f51b9b02abce2d3af5280be7dd0dfe2337435fec5f3d2a1ef583211d286f5ff02a58fed9a36468fc69413112ca61d03bc2d3bd4fe2da1e2f4b8cacd277d6c678a848a87081abf86c1813beafef5d284f2daed8ff22406cc76eba73b4b0a25fc741dad9892f3c05344e8538bd7abf7f1dcfc8e17f7d1f55c64702d40982e132b256e0b2f22807251f1bd506bf4f462c78702cb349c5ed9e8b6b023cac9241eae851571a3f0bcb0357a7a815ef421ef5f43de874fae40605b54a6530c259509212d4cacce248cf8a0d175b589cf5da87b6cedc72b3efe19d39b9190b8b017c6008de1b4f1b07c4754ec5e24db4939e36ea4d7ed793c948dc931c5dbfbc0f36dd1201ac039d59ced8bb140e881609f94ac2fe9f25f24eea7eaf482322a1290ff54e2b799a5b213c9f3fece9425fd04d468d03f74635b448c18bcea6d90bb0ce3e2eecc42696b3bb691e09e29d9fedf1861bcdf1b31913d21de27a66a9c060c8c3c5a19abac084b53001edd6213fe12e0d0e38c38ae5f8332bb0ad065e12af43ca9d0a581fbc951a106ec43035568244306b47f989ad6dde94fbd30ea55524ad13d40b42197ef8a4909d20bb0803075af5d1069f5c62e6149112293f9561c593179301975ad21329a322282434dfce60f993c411c96961a207cfed60b6a133fd8058899f845953f7128142fa7edb0a51a079237ec09a2fb182f9b03eb272d0ecb6a0568c707383bee5fc44740fcc487fd29527f1f42307f74bdf1139ba6489ee2a76c0162e6e703d7866c239a32acd9ac2e24e06e6ad97430a33c1b295b4668f9798c3b2c187c65503c56fb1884e5b6eb7c9e886b05f12c8e83ba9b34bc538f398085817e93dbbecd89642883bc438f0ee0640a207bdf2b227fb625fc4e479b9b05114b1966d20cd0f50a26400d72a07a85b7f3128b01abde44b235edd47d5cca1fdc80bd769332cb1e23c6e75537ab9695a369c8b1181e6c3c4a90cc182b57b47399b80aa70c6b8e2954a69f36454aea43f8da8c52a6055943830bfd0f8fff31fafe3ca7059bb292bbea470affbf9e92b1bc631842f91dd7a9ea555d457c6a025dcc312e3a403174480b016190848680d6fac44ceab201a71d184587541349c8d67118ab1c6444d8a9cc8db61f02610eacc34763e81eac09e85a2e27c3a855250f1188b1203fd19a2f8327a7c0b2b366fcc84580b3f81316d4ce74dcb4f674373c8ac5a68d2b91332836d1522f5f349b17494601252831033c51e458ea6c097505cba3531de1d3a4ccce2913ce2e2a1c1193208202ed2e8c923970ffa480a96ea6073eaca753a866434cf183bf65fb87e7abae3b29fa08c5a46bc3eaade7a3659ef26acb6fcd3b86a1b79e4c9f468d6edc5d4a8aa6316631de934637c736c8fa84af58282e6b47aced42eb12793ac67a3af672af50a9ec7ea12b8c07b9c16840c559602f03e0e36b78070044d91be0a301238e01939a3379bd16241ef60dd5fb1aac2d18cb1d59206e919f6547209199ab0b6928f87c004552202a236242f0348e5810ff00e7dafe868fcab4fd0bbe4c2aea00aa0d32f2fcece779a1c95cdca59f3e68e463e5c7d7f8d2ad74f75f8299fa8bfb6c3ff24f1c7a6dd1cc9b936e45534261e3302a679897a88d84b50459aa357db39406d8ef3e1479155b05ee7f809ea986a59cae02b9c002ecb48b1892dcd4e052674f4fe0aac0ee8e47e77998046e0161713a1bf21fb60127f809a7bf66b9968e8915945bc2e2cf8ba444450685b970841ba08f3aa043f55517d78cc0b3c95569b67b1870c47e3489b306dcdad288d2890152cd45422d1e706273fb670516d35d568a5887225ceaf56ef365412f03673083ffcc4f02459eab3791ea2576dcd7e53b725d0c60ca8562aae8d721e3154d8a7233fe498f9af226e0d1475af31416836aa9360598d4471e39c69c437f5072318c68ea12c1659d78a5689cd9f9260b9950c83ef1db694a997d7e4e9ed7b5986929c53a400e3ea6c59be2cf98e23b9785d88b19ab09bf0f6223a1d131497a550779e690be41a09f9bbc2abfeda021bb0dec10d9a555704aa3625a08651ec0f29986670a56628f2c0df2e95b36dd29b459f9c65511df277e5298d3cb9520f0e4ad5fd9930421b2b1318a98758708de08e49cb0c030e0054a1d4e3aec0260c70f9cd758b37e8ee986dde1ec307f25cddb3897ecd65b7717b0e1c0601a53f4afe52be459984865fc9272d36a1ef928b8cec20e0c531952d6843d6fe159986fd7e028b5b1f78a0e597bc834655269afc75d225838ba27a324dd2bbf0ef38bd9ed56afd4a918cd775c9079ab2f8a5955013e805c15188619e3fbc5424b70ad4cf3fce65d7f61ce6fb7f2e69797b25d7f8ac264fd8e63acdbd429c74dd79871e8e013e6622eb7adbbfab01b61340a952555905841432fb1d21ef7fa3be178d8f6665bfc995f59e63083a2c2d0c4a4fe5db0c0bf5d4dc5e79cf04f4a87b99dc560d735ff32d55c27818608807865e753f6f0f9d643a1f15eb2e78f1ceebb19adf82b9a467b8c10526bd1ee3721274f03b3b94c83d51a6446874dc538995995fe3381190b95ca2f391dd294c7a66efb323edf8473f8e14d96dacac4ca67af61ecf2c18645a06d60150a43be0a83fc561e70e5cbf895fea23e3ce29c596bb5466d7b28c8d88e3ed83a349accb116230dca1ffd30e3877fcefc68ce349bc3c21f57ea9eee1abbe8252ffcb64445b3a869286c26838532018fe4f3fa79debdef722cb993d4d58ed4140af16d17e58354e0b311a447e83602249b479f52ccaa9f0b52b8fa2d91aed5cda8bb036d53546280f163f8e2da4458f91383bf1a9a62875038fbf34c0ccbb7e64a34d0954429095a829fa75609de3a5417628d086f3d4d96897845672a8d424f11b377f40aa3b53b155324feb54cbb8b589f69e254462bd3e6d126b1e523ce4efdaa280d5527157dd9ac23a6f9fc6823728392d516498688a4166bec09f762f5bb9b334000de7cf464bc5584ee51fd7431514587dfe03c6f04a952f68988d2c86b8fb77627f5e8ecb940b71de7dabe8fe752d5708fddffc09c5ff13c7289eb7bff93c05f9c6be8af20c2ea989eb7a30bbce8baa36d307415967688bf0ff5ed8636452b3bd83ef53c568a9f7d84cec0ae65b29a458241fa96d69764b6b4a5a87bba11cf4efce2cb0254b00d911673e117141ca119ed2210b5375cbcf63749c064f780fec56939bf04056121930941c86aff7ed30698fbb29129948715955524db298d36e6a676fcb141d6f217b73cb27b0ab6b0fedbefd714550555555bf4f13cd503ea7fca8eec3deecd810b0aaaf0d39da55c229051bc7cdfe72b9ddea7045cf1fbea7614cafbd8f16b121f1e935262610437586a4b946348ce8053e3c3471c788c513a259d1ee5dd110da4163ba21505df3e7952b7e60ef93f5500a246ebe3f922c1484a38c0b1aab12195f3a6ee46bc46d98170cf420e6297a3ba2a49cb6005d981f5350ec7088afefea860309d2624476e3ca6007a38621c67af3fcc6c42d243e7882767ac62f4aef00cc26c15c7626ce97b0270fb1cc89e7f682a6cc01181c7973ee227104b843e7b2253a9fe20cd18368ca8c72776e4a54a1caec958b8c5353b55206b5d33c9a518878cc741f28fa4cb7d84967eb56a04c6c8000504ee00b53727afc865a968515612ed7ba9f70a347ec64b9150a751eb62fcda8c9902cba54ce42c56248786272d153534455a44e546e8bada4094521134d59ce7a62cb35cf56357a260df03d496bf772fb978cc501729f43e875e789bd6413adb9bb2bfa8191c69eea8ae72fb560d5f3e6df4a9a8c790991fb4df07896ba4bb6dbb53e426f97788a8f2a90ee507bb7eb0c1ec2a6401c82cb64a9950f00db8dc1d317252367311f49411a994d08b6e1400432c81b92fdb295f5b29dfd66ec244deae3a20d85468d8e7cb82c8238f0420a64730f267a355ecae3a955fdef23e4650356fc3725f6b7f048fd28266c17d47d652c105e4bf4b87cbe4114d994bda4150df6e83b1b0b5dd24bf0f540218b442f302ae2da19d04d88149f557b6943fca0560e4cd1058471f86ad2976dbc79d36385d588352987217dc1ee8ac0a6f4ecfa85288ad8d664090454cd738e66b719cea765fbdec581ae8f85afab32e68c92bc38460a2ef30e61ae4a0dd6666c848ffe45889715ee87eaebe4675f37f1db3c248cd72199fb30aec396f358e5685b53317ce0e0d36abf55bbc1635650153778f47a08ab3432b16f691516b58b5c0bac89828ca2e53aacfead48f8c99c14ba43d26e59a251bba006f5ab800ef136d7b0f968c47c2fc20a17dce0399b9d20dd8653d212bb95b896a0bdf984a6a88b2a543d4f779402959cff68c829492ad2cce98940d00d463838e1f08351aa657d052ccfc8ee0980a66695538e707165e0858b4511723f67c2a605eb3456e2b482f2b94d89dc05847ddc3828d943f7a78e9cc19035185a4a7da2214f535dfc9a7bf57cc9c9b484ada63b151cbef387344a6f644613f228835e95bf5f2de35af63dcf501561d8d6fc9c668df4033e3bcd67ef22bfe9fa07c120d062fbfd3a4663b83fc3b107f433d4c14d8e13baac3aad9888daaf14e3f6b6f8dc95c32d45317dc2ffd8ec97662c0b2aabb379e6c1271aa80aa581212482113405fa6fc2bb60ae1c483d14a7cb6095d3ba83b8745fd5a5e4d93e636d9fbe540c186b8f9b637dee78738895d51c426f9f45513b1d45c1f2cd2f49ab5223d438ae01a638e87c13665e1a63c07b37a94f028d53441d713a0c6c9aa0eb07b6706dbb690b74ab58cffacb3961f1c36592d234a463de9d4979c47c41851d46fbb81a0f1da2792fb24da19a0f5cab64fcbd9e7662bdc1ba436bfa64aa70f58b1eb7a71874eff23fddfbfdc19c364402a66b2dc311aa1f23cf19047f496b2cdf02b06114379aff24840b79ca4037a9c42835830da19a50366a99251db25fa1dbab9ea24d515d77a96ce5ad355e8913b190ba1599ffc1bae86dea94290d91111dbc7980218fa0e0098693b088c0314abf0b7545e450ddf577be18b117863f74d5adb7b932db79432a594028e08c407fc075c141810651abb426d843df29e8606649588bfe82269059898e50480d9005b20fa433e2b93f2f8414cc6271eb000d39b6e07d147c7f406d273f9b8854f465a8c8e6e14ddc8c2a72f3e7a932658f874c55ff988030a9f224a15f9e30d213e5927a41b41272e1f4f491f6f58f13f62908e8c5600c615ed410c5237b70388736edce8f9e83f2cc7de663561f427e69391d0b402869387dfe1f8c10164c4394ca5ab31522b46481f1d0714ee667eca77a72d46dd8da04782e5bb1b421f6f28f170c3a751296c09a7a3935111a6d1e98809f477323a1db50ee48ebe9359bd92d1cb28cc4e667532ca56dfc1e841860000eb1a3480b67c74ed6c7c27b37aa94ee667c715a4a4eda36b07b770b1f11d3462199f47909b473133a951588c143ec5182831aa6b8b018aa912c42799cdf26b8b99c2391a0325464acc1422ce915965ab4645cf5c5927068937991e3e459f478f9b4733606dd5b08942958e416217c8b80f6689c9a8647a62905a670710d28c1a9a1d42336a6a66405901fc6879b2a9e83534337608714eb70368c715518ad99d8c54d01f27a36a54741e1b9e436ec9e9a85151c6a8f2c6bc742148c9ac3887e7e30ea0d6d9f1d3a8e83b7e3e7aef000ac0eff8419a3b7e4a42df9d8c6290f8f4e227a37dcb2deb9262b2fb686b0325cb9b63bf66b64e8607845e0617808f96cc94d8c48a162619904cd015374449d0f2d1657e5ae7a5dac2f1535aa90e7d8ee28873e61624cec1b89c924e61c496d91cb021793fb76e1e7d4cfae82f27995537b37cf418550c96ed91d43a0ad431505847a6a2cfa3c6a2cfa0982913e9a3cfa223a3efa09194aff1691d8ff9e19c182b3847e5cf4729354032ab98263e1f3b0cc8cac1bc820ce96e2b4858d5a831b2120527b0a9688b8d8a8ea2471e7c74b8e523e492f566a424c4bd0a790fe40953f81424ea408fee2d65973853a3a263cd6e6d926fa0d106b5c0233ec5980d4d769915e7a8960dc70f9fa2cceaa3cc2aba8c93e8323e9c837d741921cec1125de6493c194df92803f45126e8a3cc151f657e3ebae49311e774387e4e5aa285e387733a68641941a31d44af07f0031afe1c8b717686d0b1e8fa129d5fdc06c6db67b886e572c22cf160fad310699d20578a4ee0f3a8d7a124b63c6672e53a195e95e88c2b8daaea20d252ce89cdd0d4ccd0d4a84fdfa9d675a9c5cd6c9d34c960da3a2332fcbad2d8fa886923f9f466a39ec778f458e369b8b89da6d778dc6a7c5261769aa4e4bae09cc2b4a1f1193ee3309b0d8dc3f83ec6db6b31b44886cc3279716c33254fc68b91194fcf65e0530e9cf39c080570b0805b72c6e15604ba65b9948eada639e64034c79ce77178e859b6998c7c077d34cfc3cdc897b624f04d1c02f605cb39880320cd86e4078c0b99e133dc6b195b275fb6ce04b3e9c73cbf3a7d07f0c362763bff9c8f98dd5ef9e790c389dc29e83ddf270b94c417af595e8792e879d0196a9a10cc330782695c075d3d736cfb8c7366de20b8753c4bf8f454da2f4ada07b7f66303b3e44a1ee618068f581f31cbe7bab41f57928da5e4f912d536d915e758dc5ba2193c53843def4dcaee56e9104e99a4385283b4dc4c49d750a35d48e9fe524ace2425cc6e306da23f2712c4014392bc67a6f157aebc6fafd1940e663725520bacef9e11a6fceee15c3b27a93c5097ea10e064e2f18d806fef2ed5d14e4494034c6f3a252ab90d9bcb06eab489fe3eba4d744d592e6038ade34ea663a507c4378c628f384213661cce83a26484297f9f98ed90dde14699fe907b42b2fe9fc1ec8d5f7e67ea31b0d7cbcb7c6a1464200feb668ee12e3fd5177902b85d127629857eefbdf71ebff7de93fddedb32d72f2873dd04dd4e29cbdcf7de6b8657d857841863d4223001655f26db44435ebb990d3983df152fbe9bb1ba5790f8a482091f6cde170329719e94120c24a8685c869048a28819f33a840493262c21a59437a728825abbc2a4f13a7404d1d011413eee538c524700f9cd901142df394e3cc224a7e9999ec9e48bb323579a4e3829806166666666e66e668a6f138cf1ed0030654c888851ce002610e2db37291486110d17420960f0ed392e95183594078e35c010864a30826f57124cf9f61b9349149b0dedc793e1bb4c223541c1b7db4cfea18a19456cf18de5db6b4c28c27839a2889f02d090ccccfcc413244e8034822c3891622923380233b3f683b180aaf1018b467322c207df3e43fbd1466066e6991edfcd20608808274380f1ed32b2f8c206460c8d51052a4444b1623a61967c3075881517325bc8666c80a1806797b3a7896737f5243d7b8ceb90440f86661085131fbe78c2872e62a66a2184cfb3c360c1420d90c0a58b145d64e1041834dddd2f33366e0ad08510413cfb34f90927082acfaeeda81759b2e822850a90483a01947618babb33edc75b2f805080ef5c3800932c4340bc20881f98b4e0d9311c24954a25441737d0228314409145092952dd021298babb4b45365f78f1a6ef60904878f659c5b39b867ec8f23c3402a02126450cf9c0e4d9af1d66e61f7e683033b3542c6cf0ed1633034085831966667e3b2a20be1db292264cd0858b164e188105163cf3cb3333f3ebb26e068631a88c912446976f6f93ce2d5421468989a30b6c0bbc052c3cfb1423896731a2f04c53c48c55a4d7277c67e3e63b179eb3e0c4f3119e5d6a81c9b316b83cbb87718467034099a52e3ed60f287eae2e508c883f9c10925da6804756172b2a783fa448895d8c90827ec20876490a82674c51ae304aa3c8d9450c273fa25065e108247e3cb16a94a68c08d2b2c082090c132f9e2cc10b2a4df04207568ef00407d8ebf7ac1cea850d50a658b9c29430acb6dca5f680c727cea36a42da2d7f5b91dee47320466478c975ed3bf0fcced8f01b3e3d2a3c19407f8da383f7de5bf8e295bd26c1dbd65555f81eecec690ee0f90e7294553dc25cd9fc058f31f52d8ce6009eefae674eee10a60898a5114cf56e1f965453df7b3070192e67d3dc0c18f3bc5d29bcc8c9d7e56e1eb89c04cc3ec9fb78e8beb210c058d20315f6e71b40e092e5c9ab152eb8bc8e572b5c5cf138288f65ef7608211756bcd72e376bdb82caafcfffac139c4afa1227e315a66ef19d42e1028a62c7360ba80ec68189b9ef61cef372eba492d2866d358898f2a5ab15223cf11837cfefb6446cb74d3a250f3995424c78aa364cbc73bb3937c20302ce723fa86a43339994776642116cd69984d0ea1b86ca277629f535abea5e2f0ac6d07970d036ed487c3c741e86de8dc3713c3b0e06bc9767cdf118a6992adc319eb7ae071f58f5da2feacf9b379bde6c9a55e8951be3b1c7ccccaa3a640d2ec71eb4e4c539eb43f2bd3cad5590e32577240736c3fc238be47f648d5e7a8f850ff3c18a19bf6e7f4f6a33f4779a10e8aa3d4046ae93583be65213025d723eda068f6d452ed72f6d452ece267e6f5d33e0257784012fdd0763da3eeef7d201fe1d62474e08bc8480a9ea44292dd9281fcc3cc09473bc637e6a454e518059329839fa98b703f0de7b6f5555b5b9a90da1aaaafef09eb7bec7ccccaf396666ee02eb25ac2a0e2d6007b1832348054c00cf99570a3137000210801598439771e4c86184d17853cdd3689030be2089305af22a6599f6021313233333338326ebad9f1f336536356abc340755c7f3178267df043c113cfbd6b069e61e96a7df5b02d3577e1eb81b45c999c3f8e1e2b345cb32110c7cf881ca92b2c5a7e771575c1cffc5b315860a72567b7b33abe20830765665cb52ce988dc42a4c76cbf25d08db08ce43ab2deeee7f7c8aaaa31527086387aaed6fd37e5c6e793b06afe5de8ce06837e42fbea4bf22386f0447c80cb91de079ab63e727b7f7d8a19019fc98e76968ae03da03a2ef7e91852f9c5fbed756c4521d3998cfa15c3dc71842ee9132d365c038a7d34ca7a9719a9914a868da613b84d027f4e9188ecce4342693d7a8a971d375f97a0a56af3f781d52e2c96303706d06ec949d6e6ab86d9bf11a34343e85b01c9a909d138746038d4f57ed86e604bda600d92a8fe8bff19a9c86c96b6c68b8c966dbcf5153e334aa3e7873c771e3468d09a197208470a3c1edb00fe3266d06188f718c0019f7b4c9a43d1f637453f418893e463779f7565d288f238b44b410688744310be714d56842dae1cc0cd7ed77357cc6e315f10af4e8518873666842563e40eacf6834cc1a53fc699d2d7239a3efc76833f0d77436be33923d0dafa1e11d741b2f02ddf48cc06868dcc6218ddbd8f663b407d0b8c9e4348e31dc6868dce43ed806dc76d8e13d0d3f02f334bc934ff3344e8386b36306c85679bc902d4d1526cc979c060bb37b2b1a28cceea99ec6311c9a2d434a207d3b0d678d86761adc0e5b049aa00dd7d53071456ab8c9a3c398fc45a3a18669d3376d34ce1a0d345ee3369bbec969b61df64dceaa1a0e43e3500352c4e449f66bbc0647b36546134783eb4c8e79ace18a98bcc6a3bfd4b8a6d160aa4140b6ca638622309e64379a2db3dba419760c1a2acc6e939ecf0af35864fdf2e89a2644fae59946c372af62f045f954c36d93867b09c08d97335e16692fbdcb1ebe581936b3e9c3c82cc976b0cb47571f1c75c3883f307252e6069354c7f3f8d33ab08ba29e1bf16f887fd187a73d96f29c30ea61dc7bf2567c7a5c149d7b1e7fc139924f45902b9229191e623b4b435e3adc3a9c8757bcfc3282f3d1a16534b99f25b47c740cdbacc578e82ca1f4a70989de62a8beebc18787633cd6c3675cb75ec29c5b13c2d3638cf13e9a49f0a54de556c4e26c8444f7fddef6212781c06c69d904aa67559f33569efb866ff8074278629d17e70ee7b9dbf1176e1f3779f6c82159fc248cff612f6d0c85517c713c854ffc23c43fdc3d21cea9c2aa7f4492610a2e8f8d829a111df804bb9d9d8e4151f04506d7f9971c26c69febb39c323c06cd698732d25a808cef0fa326a318359f10980e4938a73533c2270893f009aa4ab9aee42f8ec192cc0e9295b1e5a043b650bb4c29e713b39f98cfb117b3213c5c2205428cc3e99d497e8c77ebc5438d715e14466514e34e1b0e9fa0c7a4e90287c327222803f322054a66a2464167a28cc3b8955205d67cbb6a34d4fc4ee1135c2848dedc80ecef0f14f3f9cbaea0d9dddddd329835af433e5c60bc93e15f02fee9b8f12ffbfd99798c59311ce015a597eaa63da1df9fd65925280a3acc22030cd72d160958211d1aec96082b71ccad16d501bdb7cbcaec761e3640877c8c1e3a91868f752e5f80f4eee6e14c3793e592d261e4e0121531a1af435e957a783cdb33965062565f5555e5d68d6cd2b2b2ea5abcbbabcfb974b9aebfd2b6449b854f457c3282430e1658e69ca345824d607a8361980e49942cc15c4b57a682dcf3b41fcc319355ed8f8a73569579fbf6ac132cee5e971bd9a4874f323333335fd7d3a74f1f3fbf984969e3e115fbac4a429424e19c247cea4c2ad26e7b6dabda9e45e2c2a75e261209b353241d9228e19cf51fac08adddadda00d3b7e964f2530e7c364ca62c6610ce698090c9a796df2be09cf7de7bae8fe3793cbd3b05c6dc42efd7457ef861a51566f74cba8276999dfce8f65ef58c7e0fc62c6a3fbabbbbdbc75a6f1f086394cfb264cfcc4c93d194329362c386c98f4cd714f39a624ac73863ed875c2f4d31b32cd37e5c53ec6e777777f7f65e53ec6e777777f7f6ee767777776fefee767777776fef6e777777f7f6ee767777776fefea72ea3e9a6f7c81294b2b4418a133430821fca1de03f33c66276064f92e7b0603cb2b166058f1d72b166000bd6201c6cf770f0b309478ed150b309afc7e00c8053f8f0701aaeef1d661950aed645b16c4542e66377f378c8979bae932fd3be655afbb0044bf061133e66ddaad1ca17ad580c4bc0386d810844f6f073308b3b11da6f680cece97f3cc33cfb8329933f16991483fd283ea4b1e370b4992f9d023c786fd338d5901d39bee21fdee8c8d66c8cc90b99f9472996c13284fa03082a24885412d7cb4650740b8e271bae9b38a228a2bf467ca5213134992f95d832af485f555ad4a799060ea901428bfde7152142b723466bc49d1f25d07e990942fbeaf3416d55dc97302513dc1acb51a44cc07a49ffb60551dedef0ab339c875cccb44fbc1bde21c4ee21cc9614498ddaaac18204ec88098d2b18784a9e2ec876973c3c543c92fd588f4d860dc922e719cc493bab8166aa2557510af5aa7f7677d553d3e52808284ae10191d2125b18a7b5a4753eb0dd452be7f987b47981d77319fd5edd20a10c20d6cd21789584367ecbbc4c4ecd9a3d6b98e9e3b757139348a7bb20e493617f87cb7aa2117ac268c5bb73df43cb9eddbb636e83c0f557567825a677d50d4fa10bf2b74a56b2295a907b3e35955c1547f485c33657ddb577dabfdd024e556df16ca1ea3de4b9a3cf955f144d25118342a27a03cbeb4e4606486082d24b0821f6ffbd80c9f5add27f7880079303b9c7f38fe9ebec7024c6f600f66b7f60230a8c47cf006a654df734d9a9dff73c532bb09bdfdc14704858b16415a10a90c3d41746a04568a4a07deafb3aa67042929a0f46cb37c8d3a240429cfbe5a77ece871eb1a3257849ddfdbad21e6f3ee2fd2cfedee721630bde9e26a91261b7cc33a378c6ac702178e4ed846139e61128636513fa3209d13a3b68a0a4dd98132e4c12b449f20435ae7798cd0e23cc29d2cbaaa3ae4dad4a828cc4e8b6092aa67c551a8759eb7c72b7b6ad446a2e519841e4e7aac7af8f438410f9f1e3edfe34a8f1537e115af9e857ab46821428b966727ab454b0c4d62e88961f53108c5d013c3eac995e512c255f4a59eab688336e8f748c9066d15a0e7f37cfe5d59209fe7f3446ba2adb412682b6df59a90b6d2562f5306eacd72de1eb7ee0b165f977559ddee831baa1a76bff760decb9a7c66c5fbe0c69821607537378fdc98028c57b7f458677c3df6320bc6c7ac2cc2c764665d992c655756c2b20cd366607f25ece55f118f93ea500821e41ade419745e4eb8c6410722c75a4d5a82ad48a5af97dddbdcbac422fe59c2693fbe9c480853e58fbb592f9f9c6dcf14155d8a96a6b7a526e666666b6a46a2755ad2f56485180045b3de233cd12db1e0f080ae0442b46abc811b03c21094b50e20b26a4a215af5828b1ba83a115f8e0cc19f93ab482d577d35fa07a1dfa71f25bbc0e3581c5b73f9f7fae43591cbd074cdf2951a9549fc494437b75ddda577bd67bcbb9ae6bd4e899cd1569e7d7b73c0e31b922edfd459e7372e84ad277607ac7de52907460f4a265bdac497777cc92580ffd7906b9de9ec36ee8580379924fadffb8d71a6c93ed59229a1cd13dde6eabbb7bf81779c5cb0e39dd17bae326bf25d836a80b79d51baf5e73aa47743a456f04f6c4b08aa1490c3d31942086550cab298ca896abe8ca7209e12aba8a6600bb2456d624ebc988c89a644da8108289d97befc1071f64d81e39dd179ef3d6551e52b12fb0c76e9e6ee96f0a4c759799999b9b85c0a413d365e8193e4561762675c146b9f77d8508db8d57f9093901fa02bc0a2101657db3616f27df57fcbe005ffba110321fb17a787f104000cfb6139e76c2a755b8faa3e3be2d1865c0c80d23a09dac7460a4aaaadaddaa3b0c43498889a3daa6ce462311b430839b636076369e3de7f90c15fab657c4d42b66e7ee021c26aed838b9d0e4e83b131345df49938d930b36ac4a5dce506e4c7a64eaba38e96f83eea3a50e1f2d2bee78d58428135eb92836d197a8500a04cf41005fce6be6a28d7243a0472e029a821c0ea4c1900a8a544044c4d42126825e875470f4ecfa434c000d2d516403ab52965b1dc5e2dee64576e85797f1aa0919c02b1705042fe735f71863b022d9571facddc080d6f78c30e51c758c5fab6a0e23e1d7ee24fccaed1d73127e7de5da0915e9ba994c619c51516b33ee8ae180dc9048c74adb2a0ac2f6a54196364d651995e832badc6c6ad03c73221a97399192d718621ed1a4cbf5e879523c297ea58d49c92793cc7babf1c33ca2499fa41fbaa6540f10bd9bfb3455da34856d9de91f763d444dc88e2f591f33a83d97a58dca0e1c1b76f7d35e2e6e078ef0756f0fcb231f5c02367445e893f4e78002d3d6a162f905ddda9040a7e17debe9bd8b5b453db95179ef5181da7b6f23d243e5c8ce1749d22ffdf43ca9cbb1233b46f46b63727910548eec7c923ec06a4078acd4dbf459888cd520d0df6eb5d66e5d1b951dd8ea6e095f24b70373163f285760d65a0b2d8ead17955b6b0c666f078e707180c9755effe59058fec266bcd1f0542ad5536b1058f2200d3c0881de4b64690d08cfb7f70e32b3b65fa46b0f781e6bbb082b40d0727294b6c95443c9792b0d2d01857b1d5aa2ca9f94077b3b4a63d63701501e2fde5ee3fd303b2522b2bc6d3610698ec8103e350c38446c380539ed9c5cc0f1dd4cee303030aedae3ba6dc2f3cc0e93693c5a0663e253c36c3616b3b2ebda27acfd0880ea68b7b409c4f8e5abbd70372c592700a5a65f1eb3d9483ef56c545b99f6000df36c83d17e64bb9c0d10fd759e7f5b275f4997fd8361988de7f9056bd229794c3ab641bff64709bbfbe81a3875396f56bc06e4fa3d92e337e8d9bb1e7a7e6793deb3c137db63e3c38840042eaf3b78b542042984dee606cd33d7389b282f6e00a4d4120320a534cfe425e5be689d1a299facd3ab95aca343a3d647eb6c128a8ace449990002c167d54a92c75a23aa2c7a99a269cd6d1d40d66f4bb457f51b52e35d95215bbcc377c825bd27e302c7189d38edc4d0fe11ce8d029c0366079e83a402d0f931efae01ce8d021844d78e816c70d511dd08356a5052397721a59a0f7805a47038a7afe829e9683ea782b3569462c90119094739a4c0eb47e2491669205b0986b147f90c5a83ed4a5a827a7cc76f60ee7dfce731c191806ced1211a60f9e743de2b1a2a55015384a94334b0a28ab9bbbbaded23eeebc7c120c57c1ec401399022e8db7168e87ce262b277defd7ac0304458d18229ab6841d3a2a20d4d9925b0508511df4d7f42154330314baf43416350010b3e4152e832add7a1a0a320a31920050163666e665ec9ccdcccdcaf3ae2839073703373dc9911767773ddddf39983344a9973a8beedf57177cb1caa13a12f17e19b9f83193355c1a42a37616077f3a95989deebde69e75ac25cefb29f42ca138cd1094b5a325ad2a515e1ab62494bbe9556154b5a32c297046945f8aac0172d095f4cc283f0c52ad282af8a252d99842448ab8a252d19e14b82b4227c555ae60a98de746bf42c99fb455545697e6daa288d53cd57fb79efc1dd9cb73f2c8410c268bd1f950e391ca922b43279f5101898afe80ae0020e4a7bec937d81dd72950f11a22130fe3cf9f9594be513b96eb3f8589c65dba99f5f1a10e611c5ddc7b5b3b3233520ac999e705e55ad579fda6355fb02fb8c225788fd0a5ed5f6d82af405768bc3b2529e6ba03af8bb5505c92028be35b25a0610f2ed4a5dd7e5fa94799c1d1bbcd42b587ef5888a9eef1449b95861fc4ea183b7722075becb1e2ae0e114413ccce2350701ffbc13f05dc5c9b7eaf4174f70428429f4abdb80f7fcf97b12b042910f64a004134bfc907a7c837fef74e2d376be3332e0819c5b9e3d06f16400e55f97e066b3f82e8b01957ffe3c065e489de64ebeddc96eba428c2da4408134050b53a4d8065b58b1e41153eeae16f52bddb8b0417c07830c7a85aa967f5bfe291717de7b50feed142dde5a3105e9df73394d7ec2b1c23fdff11164c80b7ef8e7447088c13fcf41072bfeb9122a59a85e80458f16fef94a21c63f5f9f9f17689102140484c43f5f181441e89f4aa549ce11c0142d9e8d3426128771ae98e067084534000b23305070e4822e4304fd704403b490e204892b3d5192684016464353ac284103ae98668033440552110ee34861c2bc0e3191f4a26566423c31652051345f5e879a78e2658889bd0e355145eb411338f8141547920a1f2a5080c3021afc7b1da20289ef1c07080cd3ec65c9b784bdf75efcbdf79e964dcd94714b78525eda4e3b4122249a1428f12c77149a79601ea8d8c05ee2786887df3d11460b96a005a1741f0c2d08a165b90fb624e4526f319930b13c3a936b48c9069f1e11b9e9d0a877d4a827653a67e213f360b2421e6e9803d56112c44ecaf21e99920be891294b013da7d44ecaf20ee617c08fe7be5214e958c3f7debb585ad6a61db9253ca953a39e8f46bde7b007f3e6c6341b261133cc92578c313e8f97c718637c31c6cc638c11f36e579c6d1e4b31fa6e33db4ea3a2cb2d8895458c51864c0e38c418a3cb90511ecf979f13ab8f8e4dce81aed3512e2336f20a94f828241b152d972e1d877db0322d66c9a7635500a13e680ef4383dc6cb7d70d4d1c1243bf973dba22d3a730b142396a3cdf2d16190a4d4b88c3b8a43ae2a760acac347359a7cd82e3f2f2f96b8253ca955352afa6c4b16c332bb3dda2dd16397830be7741ac61e2d51dfe7337a0ee5013da687c7b3ef52f1024aae5d966931db2dcce2954e3d71a90957e9daacd8d0079167fb01e182a02d565b00959e607a539232994b3635446fbf21daec6653433bd68ed9dcc09e397be698f396d9ccb036479e5fdf26d59bcd669b4d0a6b69f96894e5174ea32cdf6d886c9465b9b42c4562e617a8fe75a6c5841edd03aa33a3a8f5e74734e7f2175dfadbc2eab4be185645051487a73a857fabc794955fa248f7c1925bc2933235eae134eaf97bde5b90463d87db0f2611cec181023b84493b7bfbf3e7aaed1c448cb3a9013af48ce3818a0d18679382ce83cd723643328f80a632c73ce3785842e586fee2094e54298ce3017ae61967b36e394f8a87e836bb8e714ba82c6703e616b7e472cc97943c739e94a634155d53d86a3f901c11b2ff1cc90f181f62b90f4ec5cd26a5491b88d8d49039f41b32871c0f5478b0c9fc06cc5f381e20675383f4e836cbdd203ddadc6039c45cb3c0361bd53e1ad5254dc11c3a2979183e2bef8270ce94b098252a94a850a2820e30e06985b607e4d6685f78fe7c879447730b86ea780e65425f1ccc9e5e4cc8756a2484d33a979fd6a851cf253febb9b2aa51afe79fb74f96c98e3dcbb2a2252d6949cbb2a0c5f5aa7df8c485d1bf977ade3e36caa7fdf7fa073ef9e02a0baaaaaaca0c2154ee56755ed51d234ce5623e29562be541e40ba257cf8098ac4544a45f7e719a62578dc8c9c68625ce99248743a3b67568d4faaae80a4ea3d62defa00692b2f2f9911224b445bbf2c1691d5ee1e0ec987e77fe0c2f112c424a6732ed15587efdcd08c1d407425874a807473ae40327cf494ccf3cbb487a55dba3b9bdb22ffca03c965b2d548710cfd742255a8886aa460d61769c0455adf390927a16c60016a13a0b0445c152e9040000d8d8c890f1188c81f2e8e502687b2cb744fb027b04aeac0d667bf79080be337df4e2213d87c1276e15c7495f4c9b395f87880083675fa8ea9dd679dea7d3ab64e43a09a16ae11e75313b189474256966996b4581e98e7985c1ac521e41b660cdddcdfdae52a9d48f9b170684e9ffd6833c244c2c73cc79529af3a41607bde25d03fdf64f33fbb014060ad22b446cc4478cc4491da1a8a8a4ad5675410ad5cc0c80008000b314002020100a0885429138281ed446517714000c81984272589b4aa324c761140419638031c418628821c61091a9a1a10100eab99309c14ad6ae52005c6ee27b952c280b71e69151afdc6faf5ea5ae8e27413a8187ea551a7c6a9daf5362dbab1475035f1a3f3462bd0d6467e0c13a32fbd4cae855d25ffae7c32223dfa655a537f7a60b172aba00bab28ed29c616305aa8eb690aa38bed4b442d53d2ce1a443602556bec56cd35208be148b2370cafbdcb01116ecb48227c956ee598e21bdf5ce3147488133385046bb00e002781be439e4d1d22d3804673185140ef41631f09e5b6803db83ff9b6d48894dd048dde4d24e95d6d335c8c40cf6db330769b735babb06b7d1f4bb378155e0b9aee77040a1620da585182b62caee8ff474819804c370e0e082d6a4a2d4e888ecb16d1ba545106864596d0e5b9a53536b3ad5a5ae95f13529b6f6fa340443180ec42a6d60ea5a16d7a28505d7fc370b673aba537f3fa05af95c946b51b5939dfdfda4f4698d609bbbb5e1b7cc761b92c5ea18a0d5dca3dddde4f173a2bee0414ec0cfdc3cacabf4c14185260a5d38113b7842f83204382e234e748fb6f31411a38cc511c835423af9d24b7a5c261552b49028d9864777d9dd2acce96a65e3e79a426a6d8a2b82e255b1843ce70248748535b615f39e0ff899757cccdfac0f11d84f2de0a05b13d6c88ba2623787fccd7c3cce6a9ad52cc6880f2bb268e8f65d7299a7f8f6fc0a4796d53563d81a6af2b8927fbdc27804593f3c01617d3e0bdf116b0f7b07d7732918a84ed0cee73a19f5275c1624d6d3ab5550da0585b7853e3b3c0b2b787d15b885df2b35b799bffd99c2751589f726797873f4aeeba6f0833b832381f184da7af346eab4baffb18e7bbfdcc6e0da26d1203608c52c04e269c79cc23fb7e5406588d39a1f21a575403e3c20a901125e7b405a6d3cf625ab13657482a3bffee8bbdeeb0ccd36cf353a2a381258a7ab6dc1604bb4a694472b770dbb341b33c6729bd704efe11439443a496a2fc8771ff576d4f1ad4edc5039ea29d834ae7468a0f7dbb79f189bfb64b8df20cd2aaf5893292fee3fe13036d83bcdfdf42d56126bd4f745a17650ce1ed023412639a2151e42aab05bb61d912a0f92424534a5213556d107b258c0ac7a02a256e5b11bf9486dc95198c3dc0a6536bf71b59089c5e493820db508c5e4dd43847a66433f7e75621adddc6643fa8a10a12fa8fe656b4419eed36680246cb466db7e2f32c5537a034edc745fadf272b9c5a925ad1b6a537eb2efb3bebbefe9212f06eb7a36dfc9097d7cc4c21f41374d1f26d3e61f6aabcbc8c5aa78f9589b1e5c942909ce805299354780909ecb04ef8325072a3430bc0ed455742838e82dd173e0bf0efb0e88c9d5bce94715e2d5921a8e1cc211aaee706e969823cf4f69a956babea95c4f3518d3aa1d13cdb48d43451c8e3a9c41f43cab200dcf633c658dcde1388d2ab3e3ba72419ac826a84739081c980bdd5ec8d526801ff2d6aa718114191ea452abcc1ef032b5e570a908d7a2f2c28a265293507cf93f16ed60a97ae7c931e2d8cfba3a65b434a7a3c79377d95b49ef0e6894b9db2a9194cd6a482cc55b3dc9feb5bdd6065db3388ffbc5b7ba8dabaf8e8b655148bea122f9b0a12aa0640518444faec39ed1585eedf22a4d08935a31f9b9a61a7ef524c08581d06430ca1a4aae529ae55cd762470580eebfff9f09c49a7c405ea6bdd33b8b1786efaf35ea3f4bd82eba429daa27344bbacdd687354d6845ed1238754b66a46aabddc175ce94154ceeabaa8e5332cb433b832518decb380fc9ac0f3e69266427396b28526ad35b648071f622cc2b533a407e21731d69ce43133e040cf57861571eb5730d0b6601a6e723d4c57127de003717796bb8ea40fb1834b7d041788c0f253823f659a0cc50886284600dd0e9836e8498e1507c807f584113405327199745a0c2e0445fc6dc32ac65a19b16e32ac48a03e0178fa10b138ea54bebad9f3079d58355040672a9760028a6e88ed61328ebe3050aae628f1ca0451eeafbcce4db754f4d5f82804f45c018ce8f2c486c04e35c5a8cf707b49888d164a60eadd6148548dc99454def4d8931b2da7917a8e5df63af133968e70f2e8ef06853ea00138c6c6718685c0a0d47ef2d6ecc1c667cfddcc367f710fbff06c0545057d2babe065002b11800917bc4dc88567d19f6e70cfc796b1089e339dbf8434c3c41e434e03f6b564b3764951abc586c6198fee18436722e9a5eee746ec54ebec2d1f5ff50ff0d14fbeaec8f2142fd53c06292c5ba1051ccb419a12b6573dca71bc5bb3fccdfeb441bb4e8ab03b8dc76ce87e793737028de6e38678119dfc6607beb337b66ea1cc28332205c4e9de21971b05ee481038e183bb57cd49f7a8104c6f94867eee92a8557d18d9d6dcb7caf67164211d86522a5442ab710f8a0c03e4ed67b788b5f798c83fdbef5991cdf62567a1d8aaef18ae619a78381a672a63e31410a4fc455a856bbe2cf981044ed0e19518aae75dca7414c8d1c52dce041218e775a2c249cd68233730b70aab9d27bd0de0d651ec8437b99fbd800d931e0e09e5e06004ec6b9867304789bd0cea71e1de59f6d24b963b1b37a886355b0d4c7b53b7a15d886b0937c714e29f6cad574d1191241600f6b99e2284a97f6642ce77ee8a3db794b0e4884121dc88c7ac9c4ae6413adcdc7f7e20c39cbee7c4924c3950911299623580493917165b17825adf9da81a85407c8787888a118df35e8e6767717dfce70ec5ea981787648e550b9256be6c75669955ec45efc02f03dc1acd43de23e75763b3cc8725f02797cb77e8d129f97b3a686987d5bd8ba04e8db1ac81d9a041e1febb6ea3a37964ac8e8839a46c3e1bda9e72da84758e8595b9adeaead7b557640676b8ff6dd2325a6d6d32595e50a67a64b71562c702615216d410b6b65917e894cb9ed6274c6904351be2040d331f8fb9ead4964291555d2aca32413ddf6beb55b626873274c8cc95864b0ae41690bc865f3cf866d015d6b90e8921da8238233f7c874804bf2c8e8f41010bf46dc4806bb71cbf9753527ab97fb7aa73707deb8bfef43ad826b987e41d8f47942f8f3f866d42971fbc9954684b2073b517f69dd65c972d819e111f659f413c7ed6518340f22967265663e78a99e2a8a8707273017162840fb16bbeaa05b72039de0b7fb20c5aae043292e62cc8668244ee045caa02fd6e0cbfa6cc1891391a080ff124014ef6cf977bb2fc5de26a75750e780e7a11301af3011377526934f5809bfdad6ef34c1f76524e69765de21e4e01571646a10a403c1aa01e3346a4b4415e129c7ba29230fbe72ac2724e017427569d0000c4c79433eff1d36101b8b972b3ea356885b0a1ba4f358002c6d2d0b4211d6efac848af14ffeebc32ecd80dae667364de7481ef8aa8bef8f391e98699b7d51b782140fea2ccd537ad882f65bd13027acfca64a56f5d184838effdf7b11bd687edd07e758aef8950e3456faa14b3b714b47a7cee65ea21ca91df2f86d30116b8740f423930a17a6129a6cee8693e4e79c0df24f4667a6fa6e730e77c4c4a7dfed10229c66e4432f55a90f91654e4f66647afa6c830957c404d5cf6d0df39d8c8db62a945d45934fa9229e8a26260b0e45d6eaab81529b3aab02174e4296acbf1aa7ba706ae950bf02a3e7c7a21011359562a9e8e3911349834bc6f493660acb3e70634c6ab6295c9ae75ea8bf2d0cb62f88b60280c35a1f8ebf4d1fcc9bc822ddf7a38a8b3bb53b12a9083b8b33c8efbf1a7a8dabcda831b28bcfbb8eb1d4c07141b8751237107235385a754544e047cc2d7debb6653c978112b70fbcea1d362480db768d53210dfb4d80d7f5a422d95847cdd1f920e17805ce8066017813499c036d93fba60c9abca3ca43c5c66e8c37f420db7a5267a5df20c4e61c400699d9a247d67c5865f5ea8c3dd662cd6d62f0499f0067eef0fdcb1f38d8a0688f286e280463b28befec00c5cae8ac1f078123b90451dece4e75ad9a8c6a12e836269451792276567d5b213cd1971698075ec6d0ca68a1d162cafff9edb612622bad3497386623e96b2c04c0cc510601bed10e96f59c672b68aafe1ab7e15f420642ea9e32e3e19c813f73d7e36ede82086c64e54be214939ecc230eef64ae2ae4a5134f6051155077bdafeec6044bbaa2a04552cba84a9b5f9fe226e67817e89426f51d169a3a2ad1a9155dad25590f421148674eac1d45808171d15c0c816ab5aafe4d9ebd308e618d8cd997774da5195529c00a9ecfc9a9673484dc3d0b8417ec01c89ed1f05332cb1f4d9d1669ee30b76575b176e830637458d1568723f6224b55447cbaac3d1f1dab79afd3e1b4517d5c3923f95a96ffb2b0cdfb862a80096ddf89cd52ad7d38638dbe0a45b91531d2e435c71403b52c2b9388782964d90251786b1ca59aa4e6d0672204c75710e829adbaa7bd401fb7ccb0f8db7effbef258b0154bd4128de0beb0fa3aa2a1c66d54c54079e3a1a228bcffe35a6fc08eba885f9cc012639ac65fa15dfc004292d1793535aca623be7f7cf7fb8ba83493fc010096b851f944a6334f274ad2a3bda33bdbe236788b84a743b381a881b5a7871fbdb9753265aecaa00c60acfcdc469ed09a31a8f124d09bcadeadb8230fd782f3b792b59dfd973c39ba36e65f115dc7ea00dc5d8f71a99230d1583deeb4c3c64c1c3f55dbc6e558b715e297bb34a2bbfb582b3638764cf4020263502ca769cf8bcff73e789cb1e0b0442f34086825d346006b8686afe0c4f3bea0f92993b72a128b8ace4bf5a2553f8bf0565e8ea24f55a20054e8cad593761d9c3b6e9398acb51b10f87b7b6c7abb1f939367c73047ac7490594fde087ca2460256d9fbb113cdbb4e8a897b4b1331e110d7281856200898837b2c92f76b1d39276305318d7f602a9096283723148561ce94a4093399dff68aaffa57604dc186ea8e41057a2a3b6340d120958d028b7bbfae06d4b499fb252d430b78b3ef9cbce1a2a7873ba991291b7aa30f9f79e434c90c580e115d9e70ee8b73bbe3b6744b1e1d741facdbc7ea0dada5e3e1c473da0ee46956f4d784175c32726fa2f4f5290120021309cbe45f71af0abfc464687cd38410aeabef77d5933a9a11108c8d04cb1eed14fa11115b53d09b3be087953d66c19f80b4e6ac60e6a5222a7f657c75177311f21c95eb1177910c5565493f0a4122b6ab65747a3a33a943f0c7e43c83314d06fef8d70652b546dedc6ec40bd8c3b8264885b2838eeea5d86f2e5d6c30c038d74d1aa6a5c70332a90aa41388247a918f3ef30ea01d7ce978341de15b03af21d90194e5d8462bbc20503d671ddd6d74ed98d41cb69719c8a53033f2dc2b3dc0e945e2bf240696171773e995212b188f50a1a77837fef753001f4a04fde6f1ffc9b74a2fa44d856e668f95561acb45b2961f49193138830f8fac2d1909570aabed9aee7f92914b7093915e6b7176e6942a8c0d2ed3e9f53b8229c9de8b47eb950679c5b60aa32c3b3c6fd036e5ba1bba3caa8295ec10dfb71ff689fecd3492e2fd876712fbd82c6fcc4dce62b527efd1054e9538aceed5ff64a258278f78abff708a872f100f509a585e37cefac7bf085f8e2c516ff4fc16bb9595e412857417060a1e0912db047facf0a4eaec3bd6527a8de9006658704b3f4bb8c622633e0e08fd417597638b17509283a502f12c5e59692238d0fe84342f66495760ff2c83d249b80ac11efc1b9c28fd7197429341fc2696e8ba0bf501d48339df18ddb0d4a597734a0effc018bfb100164b4e44f368c2bacf81693c5202e9fe19fbe6761415e53c0eae8769a1b8196a57311cc54c20b45eafb3e8b31fa414d4e9173e0a46731db4025fb32e6a5abbdf4441f63b2eb84a62e3c14457a193b4ca23926c272413860a0d98b1e46e263a56fcbe93566647131fb54112faaa2528b695e918cf99dc8022fabeb8c8dc12de8c43ceb2fe448e30574d918e1580522d6aee35c65c4ad9c62098067cb32b15780b6b2bb895ac98138040ccec651c73f6448668974c0266917acd39ee7981737218dc3026a31aad248251e8957a542e4aa4cd4b08422f2e25eb2db9adb5318ede8bfbaac502b3091a1785e7428d9ba43d7407b849e09426d564b5ccbcc19f03f24829112b127ced9b34561ea363bfbfeaab3d91d09baf23794a992641b16e952bc5d50fa802eb438561917b88672177b4ac5c9081da2271889f6499182e142591e78d264d04e7b0622d912218574a1351503c25a56dc691955c537af02a4acdbbc7a543369c5c244f248522acab0a2d9328bc4a4eef2197d1b8450be51867519601923dc3882f7529b5ec9ee9aad867a609d58f07d072481063ba2dfb0dae6a4193ec5604309594dba0946b0bc6fe50fd5e1411b81197af84158ec4c79e01ce388c155cb50fe186fe21ee4744b5242204f8c1a041d3c5bde3ac29117d89f0bbfbc396d2cc3a7cc7ee2bd1928114162e78c6c0a748541baeb29d61c35454db4e8d29125d6a5f9d6e79f57f25235d26259825c1e67f6493d1762de696a94e43fa944242dcff4e6daa04138d5b8e12a96a0b6a6228bd42683ba275ab8eaa8ed3b1013aba38a94e15e517932f623a96f019ae8652c26af0beb0fe7689b419fcdac0fe17fed18cc958c51f1f33ea25356e95e4f2308dbc81637cab0bb3a55abe22593298b85adeb78bd1ec3d7ba4f46a02185fb8a453bcec424355e9a476dac0b1db3b7c180767f00112a44225e29a0032cfdbe13f2ead9d4c316ebb27ec0739539bfa9cdfb29bab9266bddf28ae4b2cecb58da4e0502145954e4994b49126d1ca3e7435babde5e131c2bfea18814aed62e7fa4689d2c940b10cd5ba132cadb07b2716fd124c4861e09b8c65c2b3e532077470a78c579a33c676b1294957646f225954f4d7527edf39e64fdcdefd20269635356e28ebaf81984a4ff10fab3c4373b206c79ba67ad04f5bc720f8d783d66b91a8322e00fda917810291776a5ea21c977217ef29eb124079348a126d955c6e921833711201c2b503341c21346c157923bdda3623856d6e61377b0dfa94f72e980db961f0c6c310cd5ad0f4584d90d529f33c4c0ac802cdfbc83c83ce734035b43ee62754dc30eb4ba2c6f7e3e782e631f6a5b41eb39a26e495479470480a96f66f456b2ca3c55b61f488031ab46dc569bb738eb8470d477dbfa20358b44190150cfe822e4205746be2e740e9c550aef5fdb963ed8e65db4a55b73f584b92b6284d2ddc046dee19b600615485e090ee0dfd156ae4704f600cd295d2e2d1b3963d17dd83166669a52ea7f2b27948dcf033c04fff8c37fc397f29b596eed82b1c189536a0bd67f145c9a2419469da5badec642277cca7c5855b411df15210cbdb0c79ece3cb473854bd2a042131b8898580b8ff8f3b4b893a7eb26c2be05550de2d3106d3a6ec8e2cd52f97fa41697ab15fdce770492351e6533a7129fcc81008520f26a8c6c9bc6eb828ce525d673c0e7895720f605c2f6f1d355ab7b6d0eedf65e30d4e5d237367e11ae4b35f7d5a0b333913f35eea81e706cbc79069a7601314b2ba5055ba1bfb0c755440360d95126e9630850df768355b41c5fc57f56f758d3869a8e4c0477ad54af742cd2542f3e08397c99a2bc8a8416f01580a7859268646c7ce54ac1fbac15cef7c6a6ee7c1d2e251a7b6f98b0863241e7e776d9edfadd5d83a84bc5c3a5b5ec2e4e43735e74640168465b97aedd19dc1913b292834a485653e20c7e7872517b13df1207acee064284f3729dc3c6e29b992e5d28e8ee679e7a5b8ae1a5badb829cc0c3a9890564a7036367c624d55cc1360e26286ff4874c6705a7d83897e5130ac7c4b2f0e34b3b338999de33ffa35930fe57ca7c027b4c3583549cc643dce1a6ac8e8ebd3fe9092cb0e62eb1d920708d45b0a4f02de28fdf402186dc3c40ff5525e2841cbe806a34916db678ccd03ea43bfc5303c33942e142551dae1b961da7468c7b323078a9b3b76b4209abe30ec7265f009babc3a5646a31d631d5ff3b0a6a14a5cc22422d3c9ae77231a83c7b4a18144051ee2ae8e5a0e3ca337c293e15fa7569c45c2b45f12c5150d732afa7fbbf9c323c68979742631b610358985124ac13bbd535cf445b380e2f0149cf99477af7b1f8c3d2854751c10281ed0a8654389e468106bea720cd8c00eb3a65fefde718979400cf788408fe740c02314533e3820e4ac011ee0f19bc8c5f18e2bd901f15e0c7807ffa1c296f93aaa93e48e81641a3024df63ed1b13d8896e7ce9c362424c6a8ad47ad0489c26a4b9ecd44db0cda80667349db3cdc6c4175ca48758392588d64db569bc4c524651bf4a083d6dee596ff1b1a383e28f0b11d7a623ae90640b5f98b47243babe0d59cd7d6a45b78521cc55bf1bafea2ccfa55cf829ca73204613c297e4b9ef2ae71d4ee49be48bc7ecb99b4691532328d1c7d8c6b770a4c78e49dbd91e46bee5d096cd97c272bda60a2b3f535a23509da09d22f92c252584aeb9a03acd1a12800b189a47543c558bf6a20bb672ad4312e34601532b401981b25732013b79b2359512152a065005de1931e52e4c7a1cd5c94294f98eee8d80dee920f4ca68505540f8e7b0456d76c9721fdef0af8c4e0cf0c52ae9ce188f07eb109bbc64e9d35f8e56592c09d1c1cce6e1b4ea7db47dd652a1a2dfde1ce79cbaec4ce7c22453c4ecc176d9e04ad6acb3559e874526bdb0de54e245aeef3e51e1d0e07d9b41db40a55e9b249505282bb05933e49769d4bac55347cc836d13f1bab7ae8df24dc37e8cbe9bbaee6349220b644a1e54c4a587fb7e6d0b00904b795d7f60fcc13c11fa919a5cdd534551e5417eeb98436332d10d8258eaa40826e18af6313eda646059a39d85edc146ce14dd1124569c62fa1267184b4f70b053f549df3f4316190840bb1f18ec647f395abebb8caa23e0b8b74fcbbaa7bcc07cc2760e184a4d4b1af1bbe77ffb0a4aa613c7cb44b175ec83d610aea1661a4e5ddf6cd99002714d71615ca05031a6d44baa6fdce48b3479c7bdf2ea51c27ac0d03559997b098c91cbfa8beb141b6f1a39e531456fef85eacdffd20107d859885de2e5925fba02a194a06b1253a935293687df92cf85028c546893191d284581d188d36860c3a362c97e9bed0e6c7cf84f29fc293eaed863045de0c57aba265b5914e47addc8d335d991c012561b407f1658cad733cd27b1c8dbc92c3b69c434fac51fb7e00f40eb3a5e9189d243ab4c329cdd65b822fa547ba4372708d23c6225516b15b82fd24fd9a9fbeb05ae04fca31c669b502970ef47064243418229f1639a8870e7c7990daf94fddaa0ca291ba2927ed4bf24784a500c477a224c934c4d69914e00ce13b80039e2d635995c6aca04f77fbeb13fd833677470473710895552dfc8513e1d5b4b6adbcd4ae0e60e583b42522de4275402db5f394f6e81c5c669d41b7642e10916453c4bd7bfc8a3526317b921320e3db4d80b97f2b692e59745ac6b602fcbc072889a06ca16b18b12d16d264a2127520d179546c51c61c0fb8ad8f8fc78e3881e5ba9f7d2b0140873072593c2bf0663e82c8615f4ac14a1442c776ef03c20f0e0435551acc92e9f2fa539431a38b0db2201b786c9f46a6e38409e197cdae830709b272cf7e4c6c810d36bf517b9577f52e12387484098c81b475a32ce689f561ec7bb7015428b4a84da33e4731bc5e06244d51f0a609c0c55d82ceeb85520308d2ed9ba8f6a1add3f92094c8c980f314e6996c4830d2d507b538aa40bd7a30dadfc26a7dec561ed817c882b58cb3d93dfeaf92f4f0469057633cde4774c3eec7c9c1ec844c44eff4b544648357bc393ceb8f78f1f4edf1fd36e3c0345aa7abd0a443f3a13e5703cfa795d700e13bf144e525618c2bed5304ddd7d262e5a32e3ef41565c673719c83958d4f86ee2f581a77c7584a3db8f6e8df273cbfd2687d5f7c7f413cb4031172bae869b79107b43ed6f7d8db342eb12544d321eac33d14761d9873e94d7b5cfa4717afe381d8c3a10a4b55646078d70d27ed78bb5e39e428cafeb0a5f65cfe8e30665a53bbbe57e138750f753666290cb00dc8495eaeeca4ff81393154f161368b61179521cf1073b10eb0a6bb78ff8d6e3e78890b64acb7d934beaf84bb5379906b2aa21cf1d80ac90ddde9573981c8217cc23f3479afcae50bd991e42c31ef8e1b9aefd2687d2f77b4e1c4fcb3efa8dda5dbd5b6f72cb9423f0e8e1f599a79c540f7df4643dc4bbd3044d30b6d87875ddb53df6d1cb2ef14cc415808f3de131c3edb802b06b81f464f11297517c809b89a70c080452ab51d34b178fe06f112214f2e8cda7d6b05309cf1f86aab586ec562580dc62ceab4c55fe13af1393499509e9a63820bcff85e9ef7d35e1a5b6f82edc3b2bf055a765229ad609cde0bf9764824068a270765241311c2ddac2b9380d310ff34ab85b4897d56dff1e1b1e5ee88cccfa3a92f2d520d36a2d0fd23d306943e9b5fec603fa8b963a30d2c30b412273ea30a4d49427f418f80ea55d2f413557d452dcfb49814f391115ba9c0d89af186e1a68cb16ae4239d9233020facd3581c357fc296d0f0c347794210769a2639926a6a305b20134cd361392c0fc1b64b6305e9265dd5f3b22b0680572604eb22f48cca022837df5ce34d692cf471f0332e7e73cb98875d3c1d53e8393f24da7e2dd9a1f7a2d6789d2e29ea08d6604aae2aad7c8a0cce22defd0d7eaf7abd57d8035442fa953ceab4f0b11ed7efb6333f5abefba766b43739b52aca80830a716009c644e9e674606ad2a9e12e1d80cd9604969be5f411ca9304176583242abb384b1d6f2189ef1a4e7547fef7527d539675db04224c4be9cc3656af9ac09bf3ca335458912122c6c1d5e5fa7f8e11efd7d25e352a19014d308614f19e02c153612437a1e7558b02339665158326bc94944df8537b38ad81ead2199af40340e4fd376b44d856bbabd30ccc5def1a2bb1e3e358f7719453c9752f3006301f0b528ab8956ec0609463a2e1ccb803dc5a09976949a8fbe9704996f339e15888d236e488b25705e15f0861ba4faa3860e7cd77840a2221cb80ae1154ff1c96c07128808f32fe6048bb25a68f6e0d60183a523def4d0c8e32bac37e704942c1549d50033c600cf6d101823c109e09a605ace977bf04c6a0d6914de6395b984c816a55ee80be60ab26ff601ee6646175ace5d418365dbe3329bc01b502c81292df0319070f6da1cf953378e67fd313d2c842d9588ec10ab7b8b153324cf71e0c137c6068389254b2d839edbe98840f9a781f9c620622430150cae63bdb1343ba2e9fed6583fc32203be93294c4fa60b5f260f59c381d9713387f6b70787030034b0e3dd9c47cc4a7ace071e5ce91f0225322b5a17d611edbd1ce270dd73ca596988a5a0b348cd3e6202c984078f62460e0bc4025699e45e171ce580cda1d9cefcaf0a23b0c33e86e8eea36c054e7d9c1b70ffc32ff5fe0c86893ce8290d4af44e14c8b33e156a889260e88b5a7474baca54d7e04a6509c00860791bbd5ab257b4dfe7c57927e6a0a34200a6c86f9a53eb496ef7e4126fa3d7213b14043beb9806322baabf652514cea09a3f74c2fc96c71230d597d5f77f4ae1e9be27ebc7549a4f56019eb57ba5415dfa880e10236575859c7282cc6b164a8e27af5a4e9b8445dd2db904b335e970b03e2101fded152bf946201c5836fd3fd14524bafb78587d32a8c1dee08a3a4aa462cce88b3e65ca3590c794f756dc24ecd73a5399d9891c7c0f955b44c05c9fb7cbd956928a4e7b91575b64899a2bb69871e9c7186256134db2158cd915bc2960da7121e486ff21afbefe1d3de06ffb8583b9f20b7af184aebab180b40964436fd5e3900ac235da8ccae0a1a0701e0cd7ed4bd6a098aa8fe2088156f0f1f15280eab971146a56b455c3419ac3ca3ae1e6ca2598949a19c7536e159917f8bd6d6d14266eb6b14132cebf98e093e1e7a56868f0d4b424ba3eca870aaa56b3205e36f9b6b671cd01178f249bf0ea7a5cebaa13a64a20930db89c024bfd8bd59141cc69a23d5b8151bbaab191d55c6720f4c6f65faba56bc4743c863edcbd48642660e44cd8b267de3b923608dce8292bc76bd0bbe33912dc283c10a3c3f5854767022267823d7ba6bd8f3441e0669e1292e3d2213de05751f6874da08768c6e6281d42a1420e6591d500e82f6f0ba0ea2d0c6dc606c7d06bb405c3e2c627e39acd933c7396e005929b4f39867ce295841f5604f8a868d91a2260c9214d4f998adc07764dc175c79b884586b72774043de6ba3b3f2f0e9199a94b2626cc171e99b8019726a63205d773e12a0eff5e67ff10c8751ada269d463f24dc80fad0b166804a214e8fcf5552b8fa28af11a0d4d3e8c92834ecb982961e6237fcd382ff65799c227aee5f125e0b4cf73fd83fdee34763936b8e5b1c9be5b9397579764794df3becfb55215d466a489021c10d8a674e0b701bfd391256f9da77f2de660ee23d31ce8d37dbfabcae38b1c373306108a3013f0129bf1f58a37d325bcbb5ac38fe0e23d04c6756965c5974a97230eb745f464e9a0aba4ed6fcf4736c6879f6f4f5c012f0ebcab21e277ab1e7b5402c2d5113917ec6fcda1ea9a094efcac95613608e3525f0a74c602113faaea5e872d781a561a4a8dd58f3d563648a72f30f6ec620bcf6862c3a42961b94d5230c06fcea5c5ac6f5e53dbb0a0e5c272d6cc9157b9fc707c9f4b044317498c93020b8501ea995528f138fa09f0b81e25dc254c1dac7d77a20b27156fe7f9aad929c0798839fd2af7dbc7be5405564301d74dba91d7551759c3887e313b2c37d2af0bb7072c7b6cdf2a904ff3fe590a9d81de1b5a0987d03255c0bfb5fe135ecead0ecdf54e098ba8cf8423219b2bdd42f56ce9b955f0547fa3b7400ea39c84e9bdd34d33ddea814780a3c1c800c980cfc6a6bc4bd72646998399beb76f1f34d947ccfdb4c55fab526762cb9aa04a119260a02b22ae6dc5eedaf38a34ac237aefda3c5c4276cbf2cf554c41c2c041ddb8b707023cde32ae0061e985a62dfc372d5d7a42a79dc96a2204194ecb418da905e5db662f4df27d2635bf1ad173a10670fafc731e35b459cc90c63406ad23102be96a33eb2382b01e0a417c372875f1dc098c3a1385fa81da37a8cc506482b8923e240b9bf2a2a60c569a779cef18a5fea058361e0755d534021971a6ca799421d5ee9e44a911f40cb680244461d248ab0a7a2f4800e4c8e30f2f41c167b4aad4c3e7c8e11273d1978250d2cd0ff6ca95dfe204e50b5b1d9609bf5e0163993a26d3ca033292c4c16a60c7976672cf1f0877b5188c9a8ef782dc034a39c043186cffd7de30ca1f89cd8ff20489aa59c93b22928de36f340937d5630091c02c1069062da0874c98a979f6cf38a2eca4d0906d4ba39690646ae6832797c87364812934ffa8c1123dd65eead0cb8138a6d31382c50d9c4c1a6594d599c62d91578f8df31f72103853e45a171b2750205e6fe57689c8eecd7706f1006292ab4af20868406e886757f13af5d399ff7270bf73a18a24586df54446a6794620e7f0b652e45be9a039bb36fc0589f539fc9612849115e5ca3ba9b21f6c44a8eaf64f91960b5fc62a7900817e8a28b3465ce50e8dcad9ed12932b8a5bd148be2ff5024b1ae5ef995aea4c060c520660ef75b972b61b58ffc0f5cb57073e021d136e5ffde884b4f1dcba02cc9a1642ec4a8f621b99c6751af0babd17baee9a429e9bf4d7aaae49301475c76d11d4a2f6e1b35a0f31fca2669984ed00e8e4c57b466e2d8908096fa1424a1b92718fdf9e81e1c84ee4a3b8e873a22fb83b4b93228b542c79d59f458a837613a9a85448f4541e11124b4861018cb7e53348f30547be35d4e1d35f182da664880d252985c56a1b944fb054c50ebfb5830d3feb290f9d963b9b02e1f4d098ac48b2c4ffd6e20dbf8b748a854ec8a9a1613709f905c23049f21774ddc7e8904b70bef2f853ab4b8f98f29657718170b5129c2f6a6fbe79b730871ceead378c7c129b7f0b9120b3ab0b69c2485f6c5a5e1b965a3f023bcdf942232e9e76c11dc2dec72a12134f81c3c1b4cb15c18e159b23a9bd88a9b96d49d484f5e6e5abcb80e4d29c25b149abfaf32cccb6d7fa0ed527486850e20cc0602efd5959482c43b9f3f52079edb75127b06a2929c95f71c4a492dff8c4c52cd294405a5088144a4192472f38fa4f8e348f0d2760e2949e4f50240c8953ff901821eb64c36639e21c20ab3cc8c00675861689e910338420b6a165a17e1c0e4c801de82555e93673542498ccf05cbfd06898a3257e605fa776e0e006cb7ea0b765c39a021ddb02faa7b76034151340016a13cf39a5530b7122ad37000cc128e7b67cef4d8fad63a72714f6082bbbfb4cb5ab28896920f38c78edf9890d1e6dc759ea5e629f91a3600190823446fa99be751dc2e0b5482824371fab2b5dfa17a936a5dbbcd81d275a64871fea0477ee56aaa69e385a0f30ab0e63f5ba3173f9a1e6de077788106e46cb607c1e3494461d92ad913781eb7e68ee6fca1927b7866a3dad09952f058fbf9868e38e91f3e09fb96d1ecb7b1668f33719c39c22884f86ad638a205b13a14ad2391bcc7ed2a284e955d762123c76eb5b67d6d3ef13d0ad5075c23e2f12ba08fe2d79805fe809e313388406ee7ceb3211dfe9f359f7bd2851521e1190c00f01abd0bf872e279568460ead9f91ff8e110146811a2b70ec5582af1582391d24a246417a4d87ec9fb44ea89b4872da62bb11329328a64394462348f305a71ece9d57f1c7cf17472f2bd2f492ad8d1a48a740ec9a56f41dee6397a6e198be262c580fc41d219fdb8833a6bf2952a4b7f9e04951c017b7ac17477b927e4ac6cf4ba519e4d10a255de7d907d9db39c04c16e0789ed02762b15d758c9dab52621c8a1d8dcb7154a158da0d8cc6cb68752a1922a87f1ddbda5b35e836d48bb3f065828452234e9135dfe70cb6f2d9fe549997af8789c32a9b1ec01fe2cfbdcd6eefda69c2d35dbedfc2d578c030838e31a0654197b66d5201d6ebec720fb9d58e9cd5a09343360a081f3922b945d79c44a53e2845e24a2c4c50df4ae39d9ef084f8ffa2218ac283522054d61924f76f0034ce89ebe5a593fc00b1cbcfa90a41e79f0e34e0f727170a5f18a05b336be3cc5f4b81ccc09aaea61d9413fa159f67df76337370aeca287e4ccc1d619be89887104a6ba454c503b7a9c7f9efb4d279dd95c5b2ccba78b621e40e682abd6e96d9bd273e240df1263921507c8e19837648923b605cda8f46904e30156543f4a8947329723757727e4325d267051986680124a6f90e496bd3b95c93e1c36a45412dad91504a17e96518c3047c88621682058755e99e50f21ef99c4ada380cb11a0a5de144a10daa065a25a3f0521515efc4fccb0863fa6c33f1720030a2b188ed6aea3defb38df2d72bcfa8bf397b0172664ced74563206cb720df65eb5620c651ea70480ba7f6a27d44469f4d3c8b90dd53304b4038cc0b368db4a699a3173f6976066782d913535464a7656b618a6fdc6e17a69031f99cdf98eccd5511ddc5e277e94984a8dd6fd5d17137751a0b20dc5c8c06ca052d9d7af7d862502873a20e6d9c0496a8261a86f35b51f008036e0de9911664b92fc9ceca21847c6a6117b2af4605dbd92f1e654d652c759bd0d41f1034fbcd09b782485332eb8617d56b5318b6126126de8a1f3f0306e102a60cec7f147d3c81e5856bc5769418ad8ca7bc90e86457abc75772b0a56ebae62e0ff22e8aaae9073b37e8dc5b10e56360c04e694377193438360428e9f0b642054aa62189edfa908402ba833dbbf58dd338070235b7bf546ab1b44fac003b1662374092d352110c93a2071dcca8cfe898c589bbe716951afc2c213a4f3819d3363f16796bad2980299718dd0cade97321c186be210de7284a7ff4401f0bfad8279de767e39cf6384090c89a1676c56610139015c0364ed35ed77a0740cce8c7259171f423dd1934511750ee3905680889219f23b91a63cd588fd2cade83fb1a3d2319b184df5cd1951c10239625c39213d049fb741859afd01ade918723270b749aecfeca5d028eb63436fd356ef3600d0ccf45493ff0409b0ae9314c6a74524da532ef09cd562d74ee8aa7e9fd8a39ac14c7e5f636cf4fcc61a529ee8dfd6f01fbe7e4adf520d8fcc169ea33c38e65899afe5a009961f261f03227089da8b6e43ab2362f2c773f42d689ab176e563e05b75cad668ba81c5845887800bdc281691c81fe993bb07fa8cdd51db84d2b897ebfde36ef2273371237c93206addc59c44f0f23c85bd207eaea9ed2c0d162a03541cf75a35dcd9996ea07193c4ed9d0b6aa1f0e99f5d671d8e8cde61f9c9198deb1e3533ed4bf2cecdf827d2a241afafa7825ba126ade9e8c1b80f473a6c432785283876da69e67add9693e061af17440a85458160fa1fbc9678f1c4dbb71f1683a32dc969091353c31108369c76d30dba6c05f4ef04c725239628d21d4505021f95bdb243c6ad5a3dc3db01752652dd98d113f0cc239002539117243bc65bc41027250d194c1580c62c7dda9de8507f4e6b55d3f64ca23034d0a5c5d4f091b672859bb44e8cc93fdf127ab00cb303d31f0f1febe7e8b95058d09e12f512585c58e92034ac711bc7a474748b93f65fe2e7460af3868983e4cbb20d97797a5fc938825a02fd9e73345db0823e49be25a3d65b8d5e1c361c5521c1dbe409c9891d89dc8dc139d01166c4f9685caca3279ced22176dd13cfc219c9a1ad037a85049c1c0c9ea80f4b86994b518ddf16e67c45beb2df5e67fc0d078c6473d4857487fbd218e0d48d298ad85de5aa79cae5619a8cd1e2fcb874f59373439b2b74d96516659dafc6aed43fa90f3ccb95a9e4476f8812610e37921d254feba6d583f23ee4b1e403cc51c92cd2d52e5dd395d9148abba6a76c8f521fdff8fa26b70be34dc7ea84d472b4f3dfdaa2d0ddd1695674b73cbbd4317390fa7aa7ded66978c5c15f84fdc699f1178272cebef50e1efa0735361241cd086b06092bd7507c40564ff05f452bcee89ef5148159c67fd91651ff7c63d784270861e70ad2d6e810ec8077802799970dbf4f87c28e4a4ffb05e5d435ce55be2455ff92b79a58c490985f1ca05645c044baad5aabc555e536ecbb92f5e8402cb32d88c0da1518f488dcb59412edf57ea529905a1b5723e23c373670f042804c7d35d097a931c44483a2bc8dbae9f8f1e2b4687abaa5b131063d0ef10f264f696ce6290ca41d2fa46a6590e485f0992cf092cdcec7ced119013eba13cb00f77abe1d9540be7ce12cbee4367b0b41c8ee8a02a721bb7dfa671ab9c3a6160bc4ff7e1af1cadebf8cc1ff4917b200d9c1608d42fcc3dc718b08f48c031a2d7921f48e037a53ad83207d30d917a256203c5a77a5b37793cdc2b683d5418a0f873bf4fdb83dce2ff11cfce040c9e5cdb3ce7680590d490a44866bf02e49f1f1a41b1f234faeec462243a05dbe45d98c6ed66346f5210d2ee7a7f763c7ec086b09e444298ca86526d544c4ccab0bc82738f6157ff02a8c29a4884e82439374301b6b4aedc03630fda3d828145ffbbafb5d930b7ce61f37c199c3d0aada0698b4e053199cc284c89dc89574931f1fb2bd8f9bbf840e1b34c5127236eac06440b3eeeec6c19f1032d1a89b249c827cbd880acfb8f2df93977dc00a512841f59b44556c56fb59b46e846d68ba380b066880ad4eb21de288a43a859f77608ba6a99b53879740fe248652e8763351aa552739187a3a4d8fc840ab6e8e0c2b48e7ff7aec2ca42d9ba5a9c23dead52809eb6ff812cc3e132705af0f515dcd79581c494744a1813524e33062a044586f18d0939a14617db474e59dd7711657a6a5cb9608627aae37f0be012fc5ab06ecb3a80abfca87c810d3bf51d309eef09e231d380962753ebf32a48b6695265e76a2d7d102a30047aed9aa83df569d85daf104168bd968a997db6100de2deb4906e166c106bc0de5c723cdcc65b55b884c04f1bd5c1c84c90893d0b25ca16b80ad0048e84076505a5847477191b9b485d749e6b13dd052e5925dd2e15e074034a11412418f0780f2469808cac91f4a36498895b4dbc29ca865db9476f7c2479ac7bce1e6354ba5df2a816e3f79fa05232092b3da7d4040bf7e6721539f9f318f21dae0f894a7305113474ea8d7c5769860b93e2ac659ce86f8518b41400611554021486ffe2d7141a57ef7157b9c955beb441392d4b62414cdd1716fac0ce24ee888fbb3dcbab18a286e3ffa5457eecfbbad78b93e6bea27ccc00da06720c7b6e3907eb47d16bf2bdadf3a455dd650688f1b12f61661dae47afb47f176a426ce330d9e49b4c5e62c7c08159b8ec965f959bc4eb0114471258ee7abe0f4356ff27e2d5d76717eadde03cafdd4b3e80a123093bcc25ea6a0c059339b7d0015056fc3cdf4b531101b28eaa74a45f8be19c0add7f5e56a6151698cf4d39795c38d83c614bce6ac0d5a59363f8a7e3e53eaf2cccb3d9c115c63cb4cc922ea801f30056a41b071b462efaf13375e78bb4020ab6c46c913da9ac0d1279ef146771ec21be2ab8027dca115d922500f56f3dd86b4d244ef9551c8b69040893f737d4b21d2e719eeb4d88d92e6f1f0dfdb415f2f0c97bbd1ce0aba4804d12461239000a535bc1c249853b0c02569e58123dddee84b5e03d4d46948dd443acb010ed536d6589859912374ef9beb815cd012b5e95866f718c0803100330656b7f200737912221244ce5bc3a372b2c5c05ea0efd5b3bdd983e3664bae67e6ce401d81c7c87485672b9ff449a8f403b4243024f2938a506e05cd03b8e2d2b7cefe7f4b3abe2f971a7c3bf48125ab0721c465634b8a5f249deaa90173f09a6b1d8df7e4a425fc78334a520a8837d42c07ee4ecc7a110efb80d76e464b686e8b26fda3e441c10c9441bcd57aaef7b24a5ed228d19a184fff2f6a7ae3f665e42e5d76ec32b2f73c7f45f36d55eef505cbd64faea021f5f7c2aeac64622ea05f555e25503c0b0e6b090d6ea62169aed9956fca5c0db00249accee70a16242bbc7c804dbeff615db23c9fb0d76acd30da7354214e7a02a5d56eb48788da370750a40127c9f2f77f01bc3bfd7b3771cfe05b49b834b6008678c60540756fc2b0f61395cd8b95ebe7d2cf06a05a00e0491b827825894e530681f7d347b54141c4308d5236cfac02e95639c24f508e733f531360c7dcde9c5dfb8fa13ac15f4fee0561df2e6ec0ef1f8840e4e572c82c16e184a3ca0f1b4e1d4d3f8e46ec988998b0010867f780f6f72b6aa984b5f516e4b0426b1a3447b8d6949457ca05f78ad2e9e5a21f63f309be36912cd32d00e66d864e70ffa20bc9689d21c287a7b3847a06aafa26432bedd5ed92be50828fd530de4d00a6a2e08bea1e00ecb93be9222d3532f69de5695eac2d5d985f3e50a1400d5e39ad995f87dbd096ef109fbf52607acc76af431d4a4fed7dc72c79681b390adf5b101e9cca1c7b8abd0ed1472e9c8d587a1452878036c8d4fc8f8cb19f4cc43dc5e388b24e61b74032ee5e9c057f35df5e42e178e1839f5775a7d65fcb6c9a38c34b4228dce6dc372f7399dbfdb67414b7c2a5290e9c8300fa1647cd4195da6d6bb273130901aed370f0e4abe5fae617fc1564c691b4d5d8b79a6757c6bf0d02b83348b7d9b7549261ff9a51ed73a223b56b3b61670786ff21a542a0b258da879bdfe7d0001e34e0af6c7736ffe82a5aa9d1ab5bf1fa001e12a2fc75fbacb72e7f72d67502b151fef4cd407bc355dd21c236b3d8202dd2e88e5f9c74c56474690ba5729b7eb9512566f1c75a33c7b45f35436604448704ec1d8679e621f9e54346dd6ef6044f2eaa6eceddf68cdc8da610156fff439404b180ebdbbe36d343090cfaf71152afcd59465c1ae7c28e3e4bea180349ca874fca4d990ec0440175878eb7b81dbc26c3649541ab60c935b767cd8a77fb86715a00cfa38f8f9b3cfc40641f46ae928159414d86b79c5f5592773184831489bcfc54bbed4266d9ce5ebc56d3fa05f742c57021b4470e798a754cf7e3f59b2b10a358645dbbf0c27dfe9bb5372dddf8c1f8efa8bc9f91b357100e4a9f3eefa93e674d25a02223aaf3b660e3f6b99132ce287cdde5f6285999b510a06a90a47aea07592d01e9af65b8ef801e2469c5e2e3f123edabd60e193c0808b90e1b0c1d7181735774c4985b473902cbea31ce80c119ef14c1d195a1bd5ad00f209f43c6520537d4a91b59fb120f906f45b6ca16f9862c09d7690cff94a130304ab3a2cb9c6cbc3e8e88c610011c6aa809bc6cdc8923fa9bbc3eff4c72a5e5f48b646eb48c70370745a4d0b3b4f8ea88b665d9b256791085d9a61bb288314521b8cd8a38b1c2f6afa9de35a44dccc2c342112a753e2863b5bec20db1b132039f312e4590769765e2eca23001866aa86fc72aab09a593c5718a7c555662b92bef3237ec3500b47c7e1de2dc16bc980b48931374cbd1aa9aa83e8566a89d16bcb212409f13f8d3738717a09717359e1027f11c680d9061219573cd9432e08db769c0617c4587d05320e5ee39646139366c7dd763f50399c5319c1914c839258ba034441191df26ffcd2038a509d57ec114eceeee9bfaec1bb6e5dcb3131064fe3c8cdc40bb5e72515aaca3cd2b597a18250840a43e99561d9447bc88d97ba91fd9b310fae581540c6de1928963c4fe294d4462d845048bb70d8e7e2abcac1c14077f22875bb24b68c6d683fd38e88f2f33d96d6851fb821aa097924765f86e5db77982c518fa86ac9ca55028d1b6af02c6aaa731aa076ce0679fe58981e109178087e82a99b67513bed750ebe8c105b6f5ec7b31c0e66f0053beeefa672ff4c4c282c4fdde31bcc458e744ac347115e3bf104e91ff507932c9ba406c52c006f204bccea7dd95ae9b9fe5f4ba3310a52b697d31fcb8a2ad4f1d711856e1fe874d6d3642045a6719d904d3d8d0be4c64e05b5101e3a5e52c3a08919718a90ecdbd1cbe1aa84e51d8bb22c992d2a6ad94990a7e4ad83e04f671919ea3780d402fbce630073b2da6ad19c1e2ffab5dba3ef5b4cfceff03d3230bb9c3d52ae56f0a9f5b0623490795e77242d495362626e9e61397a2bea18a09356b2d96d92f79c4a5678243e48f028a732563821eb636b9f186c4d954bf6750efdf33c8a6869f9c10f96cfa9a2bf3cccdc052b1bd9db7c2b060781bd736f6c6c1cdea9471f860454a56809edd10335e8457a9f38b27f0ab096012f755121eff6687a7a3c009ad034719f1db4f7a9d7ef6ec91eac5bc51363f080de07bffefc0f4722ba6e85145226960b437af88af09cfe9120790bb35000585ac5325f457c3b4050e603e1fa52c2b5956edb8a7acca366d9fa8bd2a6b1738888938af50d03ed84fffaf2535b3785dd24d40cbaf618d8be811796de5fdc1bc5c8d2b94c704ee28807c06334f2b6e14dee65412ef4dcd6098a77f0ad0260a9a8e0a8ad07f68648026ccf560dde3347d49118da838a2f1f20845ec5ebc6554a2e2a550baa7a916c232507d43701be5b3e12ccd2e12712161e3d2d12f26c3d2a3a6a65db93d32f2b1db17667cd9242e50155f97dccf25dbb4af8185ae25b4213e8b689f5b67a0a207f99c682b59cd3ce2f67a334e650ca793c60fb96ddd2f753e08cd82ab6efa3078db38f9a9158721118faf86f43441250b5d890c54f1206df28f69d73df67347a347187ab481e3e099e203034b872791b960b644107cde75405b3a805416a927a8adc295def739cc2ff691d71963752936c97e7fde517bf0abff0636cd2f32b83b0f31ec6b33e915614d1b6c5978e79d0b089b2cddf887ec0cbcb77847e6c6a6acf2b57d1e681e060e3c2bba32a58289cf4743296247e2389fa4266b1bb28c30d0accc6268d1010522acdddc156d7efd548afe6c0b1ee751ba32c7bdf059e1edb3c82bccf544a6a6e8914622ac742e31e7386749cc0e3828ce4f8031f5a88fad36894c062b1fa5d9ae2fdec7a9a831aeeb01221b6450cba47aaf8d589566a05473e042aeb62a2f81bc9f9bd6f9e551c885c4a2e770d0264bb20345e31e47c3439d38c3a6b64fadab0c9f94e68159fedec81001011e21775759a312b5a6ef0b10691dd4ff44a478c98b61d14ef3f25e2913a7580797253a238fa4549172975acd911e3f8224bac00ca4e965ef0f407cf2e5db5033723f2136b04d896de60075d7b3db046cbc8fc0280d8aa55af4959fb5dcb5eb3b884dcb342214dd5e321f7c00fbb6c695a857ad964250ef440b986f6aac15e5e55f0c8c505f048b4d471b62e8cfa9f40c6a5412f7b349864947d0660f0d3d925385a2a99e7e295e3dc2769e286a323ad55d6eb11d5dffa72b5cc5263cfb1a29373da4791b76a03868c7f746c97dc84ccd8d1fa80a55de5308094c29620252228209578afa0d6ba7ad68f2ef883747356bef240438bad51617ceba88f8ec690365cd28133e3ef7694f01f5fed8f30044e3296cbbe27db655fe3d5c2ae9c583701f0f3c09ea88a4c34063d7e1ea56e86797a96b40b26470d37f1944bdf140abe82cf71273707d688724f84c4d9c12a94d766fd7185ae40850ac5b92f99cd7cd3a035921dba9ba2a01c067932dd76c58b5ec0521d7e2cd067d85dc426120281bdeff8a318151223846b7837531d7a6fb0ae6839064adcc4b069f4c2f11dd4d02738a18819420204162124cb19caf91c19c2273106509c269fad03d4b123998296606a39460984e0fbda72423833945e620ca1284d3f4a1679da186275912000c8fb4113cee556cd70a0afac9fe2c43beb86164761c71495043fe8a411b651af3f003bbd236eecb2d8a28e91909b49c8430f96927444e597f4b35be510ca749da8dcb37392bdb6224814527cf28f98a11c04b73c7f11ebe32493ef4af5eaff59ff1cbb1008711010dff712b8071381c2d9c1345b227c058047ced497cf94ff4f520322c4f55b9ce830f19ab04b001e37a5244b572134ff45df431f6b177cdcd41db760e6745abfcff88921f768b8c1152691730deb66c8fb234a1e5d870b681b86d422738871ddf34f82ee0482bedc6b7b13d0b355f4b1b3166bfc4b93919bbe81ab2a9fce6ba8bc1bb7fedd8c3d0525490875a865272d23f6b8f65e47dc67c9856ddcfedb0dbcbbac32c75320d25f64f20595b165b406f85be55c61934f0bd0aa6684e86b5ae64177c3c3ffc61bc2cce5f015193b212d42599fc8597cd4cbc75ac64b1f58557b58645eaf72360ff6e6254404c4c2bbca16ccae8e707d949bdc0d98b4f387c6bb9d72f2b7f428bfd9afb9bfa59635f40a8df7545b1bcf8a5c9de0751b27a4179d1485e535e3d039a11d0818c76817025dc51f3093cb706f24640f34f6ce7e8c6e3c1af67b431c71c3b4d68d09965b98d9a3bbd8387a331377e8cd40544a9e4ead1140fb6d2856f30205acc073e10d9f2ac07a5d1d850f4d5e69d8f611ef357cde25fcce526803cf839b2e0245632898bb3fd71af6bfc4e40af828431f886d36e7460d57774674f76a7058ff9f228596b9439cb5da7f02ad160fb6a8f7ee971d0e06fe2a40313798f78b0197d3170c97035f446cf9d6aca99719949698d01226d906ef383226fc5907e2e985c568deba67f28b9ed2775c02e7531bc95ccc85256b2f2fda4d604243462679b5f10d9bec9bbf561c6cf04023444577063dbfc09ffa0f1e122e2eabf7e393f11b20845737e4096ac9096052d221cd5e7c2bda893604315959d25cad89b1575a5649bb2ce5dbee386f65f558156f1a43c1b5bb14e843ca777e7bb9c35e8b6b7a6d80454344735eb4bf5a8045a720e2754b00594ddb6ca295aab1fb37fa35fbce3edd6e6b32e3a7ec6c3cd5041af3afb9494a18aedb671c68b3a5d201a6745629f7e3401238b698aea7d0afbb81d8fa8bcb09299d02b98ff9dc86b78d1220fb8d773ee9a34d7b19c9461b7c0870d754933e137403c4ae5b7ad68e5645f00b1c8ed000c8c1499778b06033011636cc9be0c684a97aea7d82b20b42700509584ce3f0b0675dbebd15ed05907798402869a5d5d8f6150e5ea4619e72b3ed33c42bc40b2b272442958124730216e60cbe2b969d4e332811e7a54e8ae13dae3e2fe9a981a5150ae0087e71c0c59f8595ebd6c07c602211ac0bc4a657f00839c87ebc390f6da14cd57a46835fe8b6cabff078ebd56dc1291261ae8353957d0433036428df4763165e6437120ec51520c1783b5abb8ec9ed8b887c92c66ad1ed7c1b4ef9d0caaf2b6f7ae21101f1c06d5906513896d146aad4eac3db7d8f5dc7a3d970c8e0bb0562e8884a285a175de0abcaca5a901e4cc7a135c35682e841a23f300c894c90aac4b5df0a21f3b2446869a5298f3b2a63875ff8e87184ed9426d098c8134d26109ed280f0d095bd52b6cd34f4093926115f545e3fcf65ee250ab12c3aa69b88cdeea202600350685872db45cefab5a8d3be78d7d0139cb373cca17817611ec13f2406a675d9ba8863c8943614eca133aa2c4ab450b7170b5110720b58835a5de592c5acda7c352fd676468ad9a6906af139cee3b0f817115bb9e3aa006ad7e541c906149e852078fb6595097857e94d8eea63873031a65602dc930b25723924023378621f9e7247dfac5c6037b795092dd17c9dab80975322d2ad5b0c8e24e77f16e336645d2d3ce3db9416545e849ff8ae90cc4803dbd40a5ab4456a6ad9392c926f1e95d229e9053c9f0d8201b7f180c02c5d84bc3323271981d266a592f78462ed9980f90c548c1a8e1ee1f033c8b38d14ac5c703fa85c43e5ebc6cb4638cd721a9ec0c3b0f4119c4a409dc9d12df50001e5e26c85d7113777bf2341137f6cee2b2558ae9a24af54b032fb3372a93014f5b52422691089289c04a9c5b00e61c93a7cd66ef90dc6675f087b44216320990f0e52156e6f02d4a0bc987f0b4bb0a850c5eff431424b0720e204e2c268c9405315415da43c6578c20fe82f89c5977d6186bfbdd4160fe42033d8e5a40904f44c94bf4a209638e51971fd3e267727ca5bd23dd6924640df7fba6562892117b4e6cb422070b665cff41a987b690382130ad1142993077f6d68e67041e80734c6c747c01f8b3cb81daa61033f3f8bc35fddb339c5dc4c244ad77f9b15b02158ce0aa5c8923e82942e438b232e16cd86213d349949d8a21359d148b6f588eaaa120c118820cb888385645699c5d822f61b977c938d3e7309c1bdb139be297659e3e9b2b554a92e934ea8719ecc35f14ee02e9fef4461b04761611caa23d7d5d37736ec271dcea28b7624c6f3d892e44352e00d793648d6be1034f253cc485fc5e373e06599e2cb381a64f67957415226ae095ef0a0d8ef853dae90efb841845cdc62d9937a9fd7a213479c92070e171f2294476d26233e251d53b110b82424ddaf858f26a266d806c4c15d72bc770d98cbc29ee13c8570116b55129b3fbea39a0060c45058ac904466fde1830759d059f168c69760023c4983260a9a993bc3195b6173f56abd93fd7fa6949c3282468abe35ede0ba1f8587d7b8fe7a5edcb8781011e1c96d6b9ad56ae0a4f83d12a1c85ed3c85e9ec8900c6997fd16d42b0510565048946a49341685c3d663a3f100a430ebc5240dcec11a5acace3e8ddceec5362f1b5e8ef4c9d2ec231dc3c00d122c67fabbb870ae09500033e3465290c362b9ca550b5ea605bde75733747a9860f87a3c7cd4b0bd925255cc4952ede2b2c03d35794a49e6458f3ccd551fe162ee339ba595ce12445f3f91655586441d27e90a88d061b6ba72f8bf853c5a7a8840289255bafaac5f2b337e3abd9baaeb15bd29ff4bb8aed9b80baaf4886c4f68cfb90e5974fff43a4357af6778a042824480f18705f1684230c9e8335007618ee7b6bebffff3f017600670267cd2ac702ec50d0a33c3541a10bd5e2d6f2710c824f28c0cf077728dc2e03e99a0e27d5a88c3c64cfe085d3948ae7a47e0d7dc03946fe24f41a085843e2cfefcb06e0e33f9431b22bafc254baba0ff4bd93026a5b66ef76a8ade80083953124e7d32ddacd1f2f36de041e1d6bb78a38c2930c84ae2790fa513fed263675a3c1ddad992dc34668a0222a907e17e610f8cc0e47cf93a7813f2c6f0ede330d332f7d4351596ff6672bce72d044e390ce2e847a5f2f5104b96bae9994c29dadf9a18b938d4c9e923fc5875e19e96a64111786ca96f4f7147a61d437ac18953c74e6b829cd14296f3d0505d9cfb025132fe96e1eacf20bc9d13437fd55254bbcc88e8010f37db2dc4e3365bb1dc922c5179b65ea780fd68123caad95d24813d08404a832b82fdd3d8cdb5fc4bf2c6a80e1d60167674cd7e7ea262d47401c1552853930a1395ab4589784725b03c70c1305246f7f9a1dc69629fa3ca3e18191d9e3dd0ed6ae71bbc153fdc75c2f33b40a638fd895fe75a594ebd49bfb31791a96c4ca89f9ea9db0e7f16d30e3001a68eea65911383f69840f3d8b29b2a25588f8818386d4aa9d826e54a41173716be9e1e72a1084686bc8e81f6f982979e8a8ccdcc789e0946d7a5158274e98d400c88bf2e194d6f62c6c26a3b7d22faa625f3e0ae05d3b8ccf44dc384101bd97b08d1b2c911607610833a085449e8522f9a49769c5c028a161f4a670360f706f8691a80f7953c92c58b5fdfee8e14af2be479dafeac898ff70223ebaf883fcca409699b7e60ee4cd505d2b81b232310707850f3a32acc3ec44addf7221762cf9c638d43d151c77584749d75eb2e639bd3886c46511d30edb0a0796fb46cab4950c62547c6606fa510219e18e6c038d59564eb5bc044055f07563b9f75eb6b969167bc03a10de756c5307ee27d6ebd2673fe29623784de13817933c254213f10902a7113d248150b9439f8a474ec32c51f04b88a6a329c74fdaad342c3a2171d1342708da18fa4eca191a91fcfcdab3f76edb250222c193a64cac6802b7e071a4ee92510d576ca0eeaa4b4f2a02219b76732958a0b83dd4b53ac1108be6c11a51f8fc4a08bd156130e253bb8ac981415522e84a4944cec1bd7c6be018c14216ca6b6c9a1d5511d2a4ce78db4327e4f62e740e9dec39574647e09d651ed88208f3e13bb9121021e68fc5265e4e47c17820999e880f2c36b77578d4f7e1c3a2d793cf2af1de5b666cedce8007dd4d9bf0706592ebf42a6c7f845a86e50ae4ddb6a5abf22fc497711af70b82153a316f62b1d1e2623015c61c04a9bc7cad53f938e570ef090d1a728791c6f78e83745de0c9e08a63d72ef22ba23f85201c13381d5d36fbdc7a7b1e9c8eafa604409b77d7f489f8ebd9f99789c4ffb6e2d3fed7e924ea3e0c98bc4ad69b49c96a9c2e1afc72fe66ab18bdf783b471b04b3f67cbc942fd309c42bcb018b4c61ff0ff13a8282e11145b70d3aefdba15f772235699dbd5ff635ad1065999b90ab97e6c41dd0086a95186b5bbca2e3ae87cc0d81f371834ef145a78650980f0ac653f729b97d519a93dd47b6f68d65e13cb1d1bd52b40d71b9f094644b31d53c8ea37ea584ab0d59cea18a53246e77ecd6a5c41e2ba3fa6475d3815f66c2d7d866f0cc852911e4e4af8fc67fad4082561053743a78fb0479c58bca47c6bc0418f16faa0cf7ceeb6bf0bf34475207adad893f83e3848b4f377d3302939177ed8377027d4a3cf51c1558f75c7fdb88c0aec7f6ed71a8ecb153b6459192ec8d8507abad896cffb1b575893a1271a7f061917ba24427fd3c2409c4da84bd50fe8fdf75b42361fd7d8c5fb1e50f8d2d1115820fa9ec8342acc898ed687da22c778533f808bc0051c15067ec08d47148d413526b6de52cc3aa0b5f85bb55e983fafb5497229a0ccb2e451c42b3a9e1c97f592c0c7b33372891158bd92b8d2255ad28422c3b692e9cd1852d3c5208df6d5da97bfb864c1cc51f06c77abaed0111b47c86a1c0186b5cda2b63c3106c4bd7d149e5053905209607dbc311af53e4c1b3692b6de0569a11e6976f3d8193620c28043f27345f423a9d887f2436ccea9a1af24311f526b8ad4c90987afca3a054a36a276602ba7611444fadb588778e2a25f8e14c2eacf3dadb28f4777fd045c01fac6cad1453709fc10f643475be5152fc254221bde775680fa2fb50997623138a97c289415cac9c2be143c40c871e322fa4d631f401afb1f159f40f4c905413f18c8f9f7626459f46362b42bfcd1fb14ac5d0964fb25cb13a27638dbc68d026009edbc7295a9775051b316a27e363b3c90473570ab513eaea028b052e2f0b746c51c015cd4091afafd564ce1517e5df196387627f9a12384c3b97ffe27bb3c677a60922926c06620857dcb4fee7d644e443150244be8cda44032e3b143c2f04b2ab55f92ed67ca6bd1b8c067201fd5a03ea5f52f638c8d84f45275658905cb708525f48fb00a2c88567ed5bcce99bd51885c0c34ee08a0b05ac9bedb4af29e9f0739a04430e012211555d1f2947ed50d3a85a102b58b9d4d3f92fc7c4db121bfb86dcbbc2d4f4a404b4da2c2cee07866ebfcd0ddfe0cf5abfaaf57848e0dc3e0907dacc8ff8df58557be0c8f1044751f0d3dd8d99cc281223e060a0ca89bd867e65490f973397628729fc4af5df5f3a0413b3335ffd78474cadca0ebe3b2ea54932d56492a18ed5c27634fd1c335159319e4237a8cea2961f6c856e45b4ccae6c3a8cff9f2285c0b2df081c0abb2f5c5638d4383d21f883391f8665e11a4a90a05972d14f1a31c722928a22d8a96470ef2edaf2ba671cc8490ea5664a72f0c35915829f086cf92c26cb6bf900d2075efd04c1e84698bd02c06126a6e114b99b32b432dc2f25172380218610b3d48799f25ba38e82b918b603bf7bd261fcbb87ba735ecbc32ea7185063533a3d6e058b95fa4680d3a86fc525ffdbcaf94180d57bab3a4898d6daa77339e7d0c132e3aaf51af883c22bfb0f0a1946e116148e581bb26f2a89c83c197524a917d71a06aaf3ba5a802ec1f1d9ef1e7d2e6a2ce9ea65258b3f942847cba88f959c68ed859686407ac161c92d92e8d62e40231c90d8e405d7f43e0ab506777f7561fd28d506e1d4c3cb861e5d6dbbe5a4ffb92cfd2c04599ce955b2c7f0b8c201e6d490dbccea20bb0f8de42b7845bc022c03f255a618374fa427a0b8320e3ec2ad6f1a105d7c88a6766038c76a34538589dddd6b5ca6f932dbf537ac3480fe68e3968b8372970366701650741c70ff30c4fd24119909c1839b4524589af369a706f2bff6deca133aff407dfb1caa220f5e24567f91d2110eaa03fcb23fd3f9ea2019c4a77b25d2a9731120e2bf09eb3d1a3ea10300428d5e5724b4e9b602118710b06b95b12448e1302efcdc5e8aa7eebedbd0ebdec3cbbc3510a3c5a9a54e66aa660f43c54267f9bb5255235a90eb56c3f8f0b3bc5c325965e2707a37b25c9d01596259d19063383e9c7956955d2ecd3ccee9174c498b661da086005b30ba31682da6c9888232b97c0105f01edd3dde235097fd386b08d106ca5c3372d5a8a42e09a32818d48e9c5a2cf6c354c8c16564c8341055bb2b7b599e26af58c6747c00bbd2b02ccf1f06d9a49445a73c966f3243e17cdb110a2436ea82f6289b0b2c154a61fa9a82b337907dfc4716f9c6056f981bba63719472c21a38f22634312365313f06683d6c6fc35630f92f89505afe58acd33ecd35b09c309fa08eb9f4ccf623b9f53a72716cb9faee50db0d1ca4d2e5fcaa23b24b72b6b0b2c924c496508c1511eac1fcba0c4b14ae0290aa1739d23b9f132317619d7260f8b6a0d869ef84c40dd2f23be00dcd1fe12467ff79f5b7c46aa81827317a68af105b152d8afb2e536260701c3d349ba2b2bfe33a94d91918337de747a966586177236c46e7505a76e85d4b7ee69b84358b1388dab3ee41dcca2ba036e9e80d7b886ace8b9c0d7ee7c22751a29bdd6155cc22b4558ec942a69109efb64a514de11a2af2570248741442f35127d1ba31760173a1addc27aeae1c48c2a03bfd7834c453d547211124413ce798a730ed7225b018e1d15706dba4bc60105aa283f030f672999455432e4a897beb122c63b50184284912b849b2135151ee6eadc467c89047861321cdd0ca76992d51d42658aca67f950a2484749c2149bea7ea312e14d94f3b198f3bc72fb7e9b468897f30477f796ea2570229eb94b2a67864ce338f2d9c57d2001746203aa56fa775a3090b29a0e2c6a347f5696a778e89aebddf2389fad8d822bb83cabb6897897a8caf55a7f4a07706c6305ed124b80550fe1814a18c85260373549dc0da6a0bf77cfcdc409e77bb4611fdec066d16462d27ad392b6b79432a594021707de074c07a9ea79bf7db75a6d1ecfaa6395f7d18ead5eebfd6a95e2ea7da9df71a15095f278f80877d0cf7d38ce7c843de0671a4d52d993496afc9ec5938b2df51e65483795b7fa90478fdafabde17e198c1fd375726e28e985e0b5033d0874e538af842225308221063abce0491660cc766d1cc03dfd988f6b6db984a82a0b7c752c0552ca6dac538543bc2f7ac75deabb9fca3d3f8a03e3e79ad1f55a425b2ccde8b626e4628c11d544353e711b9bd3446e133fc658abf14154cdbf899c730fe552f5a7d2314faa1e6b7b53e4363ec2570946a358ab3edb88aed0301a71348a2c88478dc39c51c4a2dbdd5d17a230a216a02ed20be2a81dab5f86e190065500814c5085f81090601cc2b5259341fd993b0b20fe1d5106eadfaf92a92fa2ca24dbb68130d5abd0fd7c15b8e3088501e97e7e17bf48e52f54d52115f52914ea13a2aa9ccb472d75fcbc23d23ef6ad43f3aed727a50ce92c52e33744cab8dcdcbc4c8bfaddd4cb5fee564a81612fd56e26ff42dd1b679ba98fa98fa663b10623b8bd3a965453a9d483b03505a6be4845bdfcb8221c38b903f93bbec55d19690d22900e0377dfdd75ff5d775fdfdd3d5aee432925277f67aca190dddde52dbbcdc847364a894283eb9741ec7ac8f97c9f8e929cdca49453baab3a36e79ceeebcfdd080b64bf4115f6e7cf47c915b22a0f14a2aafb51f5bc2a30f2abbeeb381518724da122a8c2feaafefd53a8ef50a91490aeccadabc05015f6a746d857fd7aceb3e2bfaf40ea81aa9ffffc0da92b4455e93bd7361c0a546184fe0555f8426ea5fae2a78aef7daa8f8f12a2fa54e8fea7764f6b54a962acaaf77ef5711ff9bb6f6bddf1d4a7020b3f353e070a1952fb990517e2b57f6b7b5d85451048ff7effab86fbfd5e774f745d7ff3bdbddda7bb7bfbd73e9f93548eb2b5e7f1f8b70b3de706269992bbf945da749fce600423c79329250407ddb64c1a485144a92f9a266f780826c3319b7904597c5fe038989564614e92c59b76821a9865a02a5f21aa257ca1ba91cec9902c65f0e5c139c0d25e94dc913ab9eceeeeee4988408bbb6bc18259e66a1d37c8b26ce3f415ab30a0609e724e99a5a80aedba485488a8ae97d745a2124455d545a2b243ec22ebdeca5cf446a292a44a30a4dcee8c59d658deb0a2c88a24b662688a105592aa38c95439aa1ee02a0bac5a94a75a141f6ac7eaf7a00a12350fb8ee479e4a74777e34d5fd9e157e3a27a5b4485298ea32d590a4b051c36d4242d2a186eb255ca5e84c23171a7250ddda0a82ea54a24269d4ba5411aa220400781cd59a2c7921c06c8f721e4141654a0f42d49dd2c3931a4107a0ea563f105887d87fd99c555a6a4d942601385183135c6aff3f11852b294094258f550455afc2a8ba8d47f7e81e4115a2bb47f7f8a8c8d5aeb7daf9ac5d94b593b176d36bb7a1a218514a29254d8dbb2233f315ec7143d5cc9cf9928f02eadbf8b37d2f09ba3ab60295f911322fd8504d51ba42866ddb361c7458d9028639e79c9d1771c458a1a286feecc24b142b3f2819f122a5a1f822055fb420084c83fb284a7844c9217e4d1335a839ea2235b1a5862fdb86e8505650fed055a98a6b66ee586c266e8faef5d11be734cba34747e11fe9c6e4ca21d5bfa5bb37693826c77d8c4b388e9963f7c871fccd1923e74b98bfc8f6f28bcfb33246191de47cf4d81b8e51e6eeeebe354fd331f68d2027435555cdaf93d942f96598a837c3a8712aca6dc741130e9175fbf9fe28540a854ac9450077207f03973b40fd5c047007f3b74f4ad492ddead6f9c98dfbf25751afa911544281b8498afb7d1ab55317eceef7342f175571be1cb70bb3030dff6b988ceee67e8edded8d46175d069121f9691d7bcbb66262b8d28ec5999531c0134b2815050d394963e64d688ba9542a3048e6d0328c906b0f5a1f1d186e476fa4c07033f28202c3ada8cb461472bd0db5941c6ae3108627b437476f140dad6a31767777b7516d2fc9517fab9a2594d404162a94a8e8e2325a123b16e3cb15638c4cbaf81ba58c320888002ad33ae68294bb437eba6ee6093953a3213331132b3196c669cee3d9e6d631958ef96fad63ee04ed388e93cd3351501f726d8b1d637fd1a478f413345ca7ca463647e6ab67791d6a0810c767236b33a46d9c05866c44f52d3c30e421aa2b998099547f0e0cb906065a0109ea5a01483b06eb5fa4122cc11ae422f71edbee8d6e6bbadddddded524874919860fa20bcd534cef6dbeb6cdb6fb416f2a85b8fbaa1b0ebbb6dbf6ddbf7efcdf6a923fa75f3319430f9f5c1c3bedbee38427fe9a9c1a725ceddddddeb269f5e2f4a8378056b239f9790520ab924072a978aaa0cf9897b922fb73056d43828707b0e9c60b77d2db09cbf7dfc24a3a0aa1fe857b97250308f8e49a9002985fcf828a420e4024c935eaa0ce2a314f331639e6381b5911fa31205262123789d08ca3b3fdcbd0fe88903b9bbbbd39ea0ecf9ef3cd7e9bfd3e31e083f3a367bbebf9e1d1a6891ba3b3f368f67f34408c30f2a27e06683ca329bb3f3fc31fceb3bedd447351ed5206e13dfc98b9cb6c5e8149f9f68414344457ba4b4c4d44e9ee34115246ab3f8fde424ab3156638dff758d46ab1def320cc87c9f208c2b52a311fd58fbe254a51b6de954d188fed7a51748b22a958a3b0995cfb2ae99776f76b7bd79bd8daeabe9d87ad782e672b88db2a401e5ee6e17a26331d7cccee316c24ca23d26112577726e1e8fc720fc9d38375434bc54479893358dc3fd8a9c01782c53658f873ab6a463dc84077bc0386c074a7808719b0a6aeb10121252b239324fe2e5840651bb0455b6372ca0fca12c0ae5dcdd39599d4c158c2a24b437610845dfe2e0d862e2fb690183bad3cd01911674b9a0216ba956396852f9bf933ac6556841c3176c1986ba60ad71600ef0aefcac24cfe1198b4a655691141a7295d554798903a2167ab200aa9255a5dca0e6f34695cfe494c51235dc37aa94bfc5cf52a54c017df9a3713ee6dcc34c0089ec7b173f19c1a01fcc847d7f0789ccd83ba1049e356f8d2caa82d1e8662fee9e42ed6721afda5ec6d6621759c57354334932358eced66d62f04ceae9494aea494adaacd07093faf5448d8f5f0d9f41435992925c924cf2656ee33f5fe639e06ccb82f2330c88d7f8710475f7a65165742eea8a89ec32c68ddfd76566d0dd37ba4f22e4b30ad59fa36cf72c26e8a24b884b4af98291c9224b39b7e3142f4a5ffce290bef042a3dde0c465b94189972049026102d78b87a0b659a11f3c0cc517143d70e47eecf0f463884691b27071c376ac87283f7aa0d22f3598bedb818b123f70f124898b222e88644e560a14fc00451625a0b0b1c5174034861f4cbc64b1b2c5052598c10b23340611cc28c2862349868890d245d00bc6e48a23267aa8c10937b870b265e5848d1c362755a6932b46d41083ca16329af45046154b4600ad8cc8a1862d9c2c4d275a6648835be3c9d720ade1c31a499464db94f2fbcd092911890b90e4d0050d11d42139422131411092232492248c7c3bd1450f3c62fc97ec6bbab0a10b192297837be164a50bead54572a252c3a74e3831c181484e2a40d26d80b1264d2b29945517c989e88a93d0c79c82bec6e586510d5f2667582abd6ddb11572cf1700597185ce1658502dad1b4a03990b08c2801b1024329054f74d645523aeaa2f484cb0d198390b52c880732284aa886a586d51a54d645c21234059619ea0e57dc50b5aeac51e3bb5eb22b5c5ca952c3974527e3c52f3896685b6cd1460e54a62ed2165bbc3428d3164b5f13eb226d31430d5f06850f3c280c9c6f73db3624af8efda0a1b6548464c5862512d490be7434649367023258b1841116b8518515b3a9c59c92c362469441f67591b048515350488f61007591b250523b20742a63e1b2b183ba485c60a9adba48589e505ea2f5076255a1df93967b42a129c812ed22095e0db8c4800ca718cc008d590465de57aa2c8b1abeb0d43076a5863a58d47093acd4709794fa8aba14e5072941c0c164082eb3b6629594686222a2c112364c31eb7fcfc1401bb5a32672606389a45987610414146cf183510f4766fd3b9e83001f9210c1c60f6500b1c3acc80e5b808e58e20444280165a6e354c3185996c2a082c40c8c230533681943056d04316b9a54d2fd4d879a1fc1adf1948c6c10b272021d6e886e94c11935e18414c7162c648894a6c0e0430de90d2328dae8e246b536b6d4624811d5dc20c288982528a129b424b4146c4c5368533011828b4a444accb0d1942062d2c38603955f53a1d2c9c9caa6e4f465db948cc4a841bca90d79d950464e92c68059f20453ab2d7112b2c1a9c6a4082e524e00a03545a2251eadd6049428eabe0a08215e4a484034692724209850422ac11257f7db89090b2e28a62f48d47d55152c9a142c54685c1cc124e50a50dd8f1f6706f5ba4ff354b7eef7e4bc6a484a4e75b7adb851c3ad754658d850c3ada2e9eef08d0da231a563c5de802a09654288b94bc5a0a771b6c674be63fbfaa0f6d374acbd1c4d688fe621cbb13eb6c696a2062ea2b82656b957bfd6ddbd53715f0c4156a38e2f14e5f16c9b2742918afaae93a897ed85208bbfa17e878e2ff253284f842231c7dec4efbe99bde10e22ea1754750cc5a00ac51149971b6ae874e898744ce6779d0e207c35756e9476d835fe7850c31d84a0ce09d3fa8e09dd8eb54036bea8b4b97dd57a6e81bb37303fb80367c5a8740d6b1ce02274cc3f867aff1f2f4a534b8537f62a7ca1b2e035823d37d08e45550b5c85d4c042a0757b5ab789430c317451a6e83a6e8eb5cb1c79934d75df87e454d1b0b95c4cb4888666ffe543755745036dd5ab5e45030d69dda902ca208561c51627a5373f4de522925d47bce005cb51a12157819eb4c072bf7ffbfd38307cf71ce06008b4fad3fa02ad0ee184d4540f5188dd4a68a0e108dde72319e8962fbbdbdbdbbb63c7f6eed8b1bd3b766cef8e1dbbdd39a023715f4a29a594524a29a594524a29a594524a29a594524a292567443a89fbdb717f7683a190d79cdb9cdb942fe3149af193b28a36d24922a86324f2244a9284061ad8089954e5934c9249dd547d28ce6eea26a0de50c8abeec72e28c4a51eef21ebd83a601bec5f20f9a51788361872d006f150b73532c89a3628f49dfd999f83e1f7c2dfbc11baca2043714253303b1e531e07050a07c5f3589ffac25853ccf455df6a512893820c457a205086c21e6c65e9f170c72a1a9b3f9dd0f82f247932ca1143610194a32618281f1d1dc9284f15708d840a2715a5547195fa988209b9a6ba9af23c9e546af53b1ec3b8d3405420fd18c41ec0fc0ae651cf791ed8c18021735e080192eb6ab4f9818d7fd75bdd7416f47e68914e9253a6337f9bdfab639d04f33bdd4092921ee03a302bef97e585f546528f8e21a197cc263dc524edf8e4ebf856a997f950effa541ff3753bbe9dc5080df5b09e584e492c27204f4ad819cc37d8f30101712f1f68fbd5c322385bdc6afbd58c84adfabbd0e3cb05d37a4cd6ff509005fac80f02a4fa2ef528eeb7f9b247101456a02d353d05f5e8c892645d117e2155a4a3b76271ad1edca471f8e5364bb1758aa1c97f7ddce7f8b69ff9e6bb6462bc9d96f976e68a79790e0709fdeb7798fdf5add6aa56ab0589a9b5daaceed462cc8ac5b57ebf5b312010d07c6eb5a22d10d6a08ffcd53659df938591207ff526c85f758fd6b79382dbb8938eacb61d8b32a0986f7d7b301256bfbdcf0ae67f627ef530a00feb27f8d37ad6af7e7eac97dbb726071269d1f49c90820bb49de7a26a8c6e6b6068e8f3f7c0de5ff694838358c8a3f9e4c3beed777cabd7f1b13e47cbe372b4643ab21328c8ee3299ceb6fddcb62732b95d0ad27a4abbf5336663fa302d100868f5f28158bfc539e97c1ff973fb613de13ae62ffaaef26041e697b01ee7339f5bfdfc067d5aac6f7d91996c3dfd64658f8510b8c23c7b41e4772708c1451dd3a1a04c5665647be39f2aa2cd032361be7c135adbb70827b80d2d48833d331f6e12d0ba0dcc8b3d800581f5bafc067db81fea36aee7bec88c7eb1729e23dfe5fa1999ede5ff3ca1ff13f31bb84e411ff91cf8c3b9c0ed89c4c8ed5b20110e5c10163faf5b8f137464558cbc2bd293d297e73c593d7f0735edfb9002e04356e2e0698e36f8909b76c042fee4ff379f83eff500f858bfc127f3345f8e877daedfb1d3dfb7b3ff76c6c1b733007c3bdbe0db59d3829222c8421c14d40b901fbe28ea539fc5e6bcdbac8f57b5b238add4772e05fdeedd04f9dc4fee2791eda5378d5aa8f3d18ef84bec8d3313bc44f41d9b089df330bfdfd338addf3fc1735253a7a38ef700d758c2ce76804040f25f0f349ff531dba68393bfbd8ffced05ca97e00f0b0c02eeb88de7f866be15645cab6fabcc6340d6acf5d18d351f798e6b6dfaa8ce822cc1526aeb838fac48e12494fab8c86dbc0a0df928dc5a6d1c5fd5984ae3c4ef7a76b67fff1552aff2e7d98ee7ec2c49e989b5fa22b7b3f8a3e6364a4c4e9cc44bfe2a7ee2a39e1d3ee252ad27f8e5394f5c32cffff1693ea49bb4e3c355caf161ecf5e132853a341ff7b00ff53bbe98d7f1c17c8e0f265f1f4cae3e989cf960f2bd19ecdbd98e6f673956dfce3a16f21c176835be50940163e0d787ed531ef21cd8823012b867bd09a8a72091f9a86781449ef8cc47a19e087d0e84ed8c462d5482b220332f7380bd89b21a11c036f1c354dd03ec4d9401657b133de749093b9b791ad379cf99cf7a58ff56992f6167ab127636030201c53cf740308ffa8e7f3d5883416891d956197082b0a6efc33dfd8979ee1bf4413d7d0afec03cea89fc0ef52d03939171bdac4752dd6a7c148474ce8c510f51aa30baa2d694cc9071c5e7258db3833ef73ef439f0e749ff06feb49e8230d9a0cf7c14f8c3fa19f3117942a48522c2026124d0473dac411fca7aee613eace79e05936090150804e46dcf4b3a164f4801f5adcf646e1387b8ab10f50bab221d3da58ddc6e71507f117a7eecf8d2ab8a1fcd2ba02928ffea439aa4fad3248dc3b3d77b2d64221d1fd2d7cc87cf72599dace36bfdeba33ff3c9bcf771cffa6011e683c54fc5ef7e67db078baffa9dad07eb9dd6f1edecf5ed6ce6db99f7ed0ce6db19abdb71ff1d06e90aa4344a4b1551595b885eb409d1fe6e13a2e1af26b037deda91aa2d0ab08d7f980a653a3a13d81bff555f41f977f41894398ed2d72b1693c974745a707dccb3c7c2ea63c0f5587081b09ef2e3370e0b25ec0c0848e65b0fc43dfd15086b30c87c7f1a58ffcc17993d913f5bbfbd4febb7cf11c66aeb43aae3ae77aeba7c77b9bbdce56eb3fa2eaa7ef06cce04b64af5a6135245d0d6d3dfbd89799867bd8b7bf93ff365b8ed4bd8d9f6331f7f9ee4007de8cf803f1c088bbffdcee8ffc880b0d87ad7c7a95a6fc2fc1648e4890febe5138101d7e7ce4c904f41222c70dd66052e088b60100f0402523d50ea81b89f0fd43d10ea81b6973b1d230a2bb8cdc7426ee3dc938a874ee03ae61e8bf00ba822cdada8ec76376f64f62d38a0492922882f3c78510326244a336c48b49048bc98456080a3062f6b08a1435119b30d8c2fc25071d92c4da84832c41834fc60c42c6ae9c2888700a3736666f6e6715575de4890b32073823f2fccb84f8773ee7e45d70208db02085d5b30a167fdec5512e48ce79cf3f75b8f9f146af0e7851983fe82774771a3f5c14f7bc36a4145abf2bb3a0cf42b77ef1e3f31b82570739c092919585fbe7cf97282c37c54cf7a16f8b302b9067a20278126c4201dcc47f5a922b3f9edf9a85e05aedb70adbad27a24f0ac035f6e1363309ffd592f32ebe21746237f8a5fe8b59ae77450123fb590dbb8c72667258f4bd53d1af991d73cc9979cc99bdcc99f222d0ac5a148148ba2513c52f21c37024376e2265eaa1eb9fb1b84b91b45a7d99e926e6b4237aad1697de87830eb7e4c45f3095b651ddbb654b7ec4f0586dc4a95877a0ff52a549702bf635b4ddd3877ff886a7fd26dcd4c8df27b1efba3b7f2bc95e7795e8c1ed45c8c91a317d99927a27cd97d3274efc5b8d3de8742752147845655a94ef56ddf815c4d656d144a96ea7a4058d5d9d1e042435ad725d0ed6c63b7effeda1b2e27f26f5050a2d4dc50734b71286ec80a4cb677a7b727a8cf23ddab55afe565e665def70f77a707a453a9ed795da89b0b1deb0d1c02021a9d36ae30a463fda896563afe8ebbedc07fbd69156c21fe10aea2f6c69f0748a4d1501b634f7e3c5bfbfb406fed41c065b3affa17e4f6c6ffe381c91a79b68301c10ca02a9fbf8b31825268dd66524197d81c39248dac1449292a4b29a57a9751439bca8f627ed10d0c617677fd9342a8b5d9ff8c7414d5501249a6988673ec5b54c518e35c304c41cae0b52e6394ee93dbda25bb7331babba33aba4a87466989518bdc76a03c623139a70bb4e67f6e520b17b5b80b1c2ad52b68710777631c7fe28ad135ea031d9d96d47b3ad6dd4316e5d602e5119373fe406bbe9b9ba4e1a28b2e2aeed4b2a43a0ea132c81b9372048bdaa4ecee82edeedeee1eb7fbbbbb7d3b450b5f6d06e5ef2d8e5813f116a74daa213fa9d1440d9bd640df497a49ed1ea22d31130b8e80a25c496249921733d0a8212761a935642ccf45ddddc58bba6aae7b76f3e8028e1500f1a5cb16235d6ac8436838a92113b1107b461c4438e0a0469391a1599f51439d336a50dbb11051e1624350193228cdda8c335c50fb5d1bd2d45447c38beaee6918a99ec695ea6afc50773d2040ed9ef378bada343492a8a94ffde6f1a48a844802d4342b283b0bda666b94b6d9da46954b453d0f0a27f46f5ac738a881821ac783c20ea2719719ea6c3171d4737a92ac540254097344c36f5a738c46a3c550a37deb39f40c1c6a8cdd85869476ece5ae7e71af1ad2a6b9cd264d19b989aad7a6c3b4a0dbe2a0fbdddd227477774ff7ee8f8e71dfc0515ea5a813590b066d88d5c240356aff0e0eca4b87c7e359cc98a1368d3106000719ce71854b12b57f068518b729073698509a428c256698452e6fb4d1a2a8a1262db5b3a8fd324c44a3d18ea82a9023b498811a477a8083064d4d3a97c7d3311e8f0b20d258a9614d8d3c7a6c31428a1a9fdb22831a9fbe60d0438dff3048a3c68f4151e3cb16898ca71a91caa0a246a4261f6afc96c7139562802105e3f1b801c688a336c756daa03559c3c8952eb32ec38cd91d4357c5e951439a1a2a1850c31e3586612516461b62d4b07411c3cb1850d4f82b1dca4fd09ea60c169ec71363184c3ea81ddf606a83a6c6a399f2789c4989234d357e8ccf05510603690035765f9ce85c25366abf44e1557342892867d8e083123256d0b90c548b52edda1f3f8831c6a1951b400b10b53b4614628c31f220451833d8a043adcb2cc2a0c61863ecbea021ad4d3e9c3820c1175bea222925f1a88bf4c59124aa5cb4fd2b463f49875ba24cbe1464737acb92832f61403063497567027da963cc811feaf6405a62d40da67f9b889340689a48dfe4d09b22120db908b63d176bc31f46e50f9bc6a5f2af51f977466b215ffa8d6bd0977c697d3491116dc2e66ca2c68995bf85e0efa2c66155fe56e2209890bc58a2e96661d72af750cd26871f3449716a9c70912a4fa15109b70c21a0a122881a27642a458d13f2f3937fe15bbaf892bf01d4a386beb4a4865188ca63d4301ae1475e7376e2a4229ce641dd43d5888b7034b03e54545058904e52e29e8fdc8697a4506ce297419256f9a9f22fe84bcee44dee146931280ac5a148148be251acc5a4a8149722932f7d0b71fcdc9782db3435607d746fb0f95bb72d757e94ced687ecc08e513be03c54a95d83edd85e757d74e059f02f6f5421bb650a1255d49d62c4c45477ca0abca8bed4383b0589262abf7f31c54993aaa3ee142746953d086c3f7fbd119a561decc0e42f35821d98208ceb04774b9d1dd82d75fbed1d84f96ea9f2db2d357e35dfb1ffa07160311edcb21e4b4b1090443459baade9ae507f0e86a1ea0dd22b5b53689c05615cc77a8514d6c70c1739136d155e0c89b0236cce6b6d160c4e724258ec048de9d55debc2862838b06e316a87d10bee476077bda8fdde02dd1bf928d8366538c42b093d7350486e4ebae5e6e211d73cfad7ab0963bda914a796c531a3b86d4a146a65565d73f7c655f85319b4c2b448e54a451366b05fb07b03f5ba3295ba81f6739ddfb0473dd34caf2d95d4171aeed20915681cdae4a505cc71ddddbbfba31d6b2b688c31a680504ae8ff827b25b6442223593b602f1d31395c30b06f3ef7af6ffb1c5fea633ed4c37caa77ed748c2bb28a39b698e794ac972c22ab9f93e564d7ef74049294f400203daeb96a51c962cdc87c25eccc050201713f674010d6abf799bf9a727b291304e6bb19f32dd6b35cae18f087f572671dfd78266141562c1870f5811b69fd022dd271a544e4798cbb6ff0eeeea12c490793ce0b009ffc0dbef934df8c8e1d395e3bedeeee31eeeeb2218edb4022dbcfe7362eba7b74cf11e38e80ee3cc7ef73abdd6240d9deb88c3d16d68b5e8e99079aef8a1cb771ef2365fe6746c605c698d6f7243ecc473f58ac51a7e7841454ee8e0a9f33a8344e164545ca148d08000000f314000020100a07440291582098a7db661f14800b778c4476563818c6b220c76114c5188310228400030c21c618234435ab2000024e96d34835ab083a08923c5b152f05e88029fda1c00fb839c154ca57892de080791ddc7bbfd4e955b5707a88dded0d0c715efec74ba217eb6d7c934de876f42a065d28ec49d35eba0a574d2b7cfbbe83a3fb9877ba74684c49ed6326c2829ccd3368ccdb7b1ee85f6603dd47ce5cba8159c60fe4b43800b7280de94050aa8cdde407ac3100aa9ec8ec0c06905f9e68f376352097cd6eae66f5e213673cc9d2148d52549fc51b0a275083e2025ee3ad973f393241e4241389fc0d34d42dc90e01f48a38d650ba275922fe146c1bccd96a6dec4c8b0e0019e87b0407526ba3d44da888c59ebc87d6e41570867fd5ff858eaf708a92f873806a3284e4adc0c49d07b6987f27fae8c99004407a9844b4c1f61f86fd899cbf4ad5cbfe67d6b4ca195849983d4a309082bf7cbaa8dc6c7b91a5b6713fbffdd9f9274dbaae684daaab0c5c2b00f5d10fbd378f9489ad0a9644b89b91ff30a2d6455a6ffa55bd159ba273751f5927f8e5f3230d81d571f3bf040030ef4cb8d81cf17b47dd602527f9d0c8e1b8efaaabdaae3e9ef25849e91e0601fd480424dfc4fa099464a1eb90cd128c356191cabc777468c3062b0aa85c09b058712b1def0b0c073bf09e449f64d95836efa71bb00ec56ddd4bdd215d9ab25780f0d268d21766b92a56e742cf7ab069970fa85e231a00c771537a6b172b3a83b6ed5306e5beb7cb7c110a7d785676d7d0338a1ba9a1df139931223029196d024e9e2106f22a7ca6b4ece9cf2d0fd550dda0d312800c5e0aa1a3e42dbff08bda121fcaa7a7ba465920357f48ecccbaac3a579e63996a702207533b2402c21482ee7add7575db2ff15204a8c596a1981c4312d9dc785e935ee7a37b67aded854c9d97b965824f82b7dfb77a386b99d04061e8677a516209657f9798bcde75427b1dfda15524a5a649e81cf0d5c964920e0bf83c41127e07853413bc49b3c6f82d9478bbe9b5d555689fe94f266c94f0faac8749a020eed29883a448e4b52eb4a82862f3d337c2119020c8b220f34b1a1b0516afbcd9255568309472205db411949b5b3474db31fa2b086d9561084df5a6d4d8cca38636939cbe7cb1dc9aac8ddab454fa95566c9b31d6647b8058b07ad24a609830196cdf590fc93985b6e0bab9063b64326854261fe58e09c5bfde3be5f4fcf38199488f9ee03ab3172574d44396234dda0f29797a82a94b000f9ab886cd810af78fd628545e44c424e1e01e1982d7be914aa26661336fa08e5f781ae1023497acdb0d8f95d96e4032b66a5014775111d2e800af7bbc6dd4edd25279cefe56489bdef032229364f40833821b2dd96bf5f919e2184a958324684cc91b282c50b8505ff208abbd4d1d3e7be82c80320e82d1ed899aaf48ece60a93c04620106ae548559f4088cea6032791a7c9288e2a262b63804094557fb3bdbe01e46a2d45a1c3da0030e7a4f866179d9b415fb5a2cbaa147086a86602ca81f246e080dccfe3d688255b0793dad748906690537ea803859b8c1c2967fd90ed134353f6b475fe1705f3eb55463eea0cca48ce000f4cf48199846be311ef04dd0dbf9c4e9e0fbe6d2b706bb03802b43e95d98eb55e45c7108eba319547418c1899d43939cd1308bb9dc912117f2e4f9a25244633bc663d5963d6ed17cd891d21cac4c4da8ef601a8b0e70d683519f12c84b4caf833dbcc45a34cef0edc8aaef88d600a0da756ec820a5730f3d748d23de0db701c0ddcd97da4e553584534ed5238f72a45d832f0422da403e8a1f75825c57e531f48ca9ceff1de2253a2094839c3ae0967c2664b0242b827ee92c7175c17b6be18ff2342c4fcd3debd1d43272ad33b0233e4e3f9c4a8b5788e861a5ac8cdc5288994c47815763c150f02f7e0b2566ef7e38056026d7559b91c84fb0e14f07f8e350532d2ec9151355b8e960cdee6b105741eb7a6e19345e9970204f8c432041f31de1688e6393117390f6f8f3c229ddf210531b20ff907e798b35ea719854eee09944661d8eae3b447e9494776c9850ef6e6c77be62715ef432f16e4ed5fd3087da60d7d6c08cd89fdc1d3e77660a9c2dc1ee2988ec33ceed47fbc2e664a3d27897d0c8ab89b148fb427f7ad28f390009fb5d32aa3dff1fb91114924c34967ba753b2239ef9be16a41b5a85829890eaa96e7df2ca66ddf411060228a15a4ff0066500e440065582c0f480a453ee23a6c01d5c2b7c4079509742e4504e6b8ca65dfbe4e69cbd745811167770d2c8314d755eddc6bfa5e75f340431c24c6b92461b461ffed8c444158f206a8b895a71846de40a2683f689262e5edf73b64ebdfe0e18533b981925c19c27559deb8b57ee43151ef3202414bc48bd80ec91e86d9a2b06b353d9bddab7bd9388ac081e638803f201576c49ef1064c5ad336f658dabe84521bf00136faf28f639f31d2cb32e3122aca3652917578607be50b1794036f06cb8089bbf66ece029e0e89eefcb4363a0846496061c05aac440f62d292b343252b365786632cde428aa260c5eb488b583560004fb020b2bc270bf27c34f2347ef818199ecffef15c429e8fc559c00579deb36c4c2a05f23cfc016f90c844a5cbe6e2212c8f7d2dc2deffd36692219d964ac052a8fd39a2c61a5c6e0c1e24aa89820459d300da7865f34a903282fea9e4283448bfa92b563d6336d823180e281548763e05498000d58dac71c3f334c3827126217d126cda8d6f52d8e5654ff01066c5a81fe39db3802c1e6c8d1f444231d780be5297455a7d1c93209ab2038c2ae0a4aa875ade8cd1159b704c98a0a57cc832d57d5b748f163f40de9f729ad1e60397b96086fd4328702e97d0a339a145faa20c4052ef8466cda0dafbde33b913f7fd7d053501d822aa57e74cbaca8b1172806545e13e72812886a5b679bdd8d40b331760dbd73d63ef8520761004c6ab36a4445da9dec0ed6c150a8635eda6ac06d4f67c2357bbf9532f797e6634e9a1e6e82ad7e02e3e68e2cee30421595513c9445f34632c4e61d3e18519a68e15ca9408f4963ff6918b80032eb2707f5346f1a9e5d089d854d68e11e374dc7adf47da14fcff23f73310d8beed66f17b13f9e1291fb5d8d1abe9fdc801b02ecc2027844bdfaa0c3bf3cb40b0f9f5714d7b5be1ee3852df731691da354a95b83ba94171afeb4bf39785c4721f9e7b85b1151c6a6783be8d1c58a1bdffee6045754ad0d946c211631c109e0f3abcc948c08f1cdcb3be26ea467f0d1875a5b93f2dedb41023802264098c8b65ec43158ca6a99ded17ce2c812d3ad79faa7f4aed7a9c001331b1d1d8f6370b4c6140c5a061a2313d38cd9ef80929c44c64c75bcbdae59df36a6cb28fe19dc607b8b5611cd63074ea72109d25a027a2320c8dc6dc8f5cba989b2b541c4230808029d3dd3f949526698f51b04fe0a92d8a02865e47d2615cbcea0073e870e203402b991b9baf54c36557e4a45a0f271e04d848b80e1aeadc70edc29b912b0183a9cbce6d28e3b5d58930a84a400351e63454045a738eeb7d118b0101a97c1332d7ff171b41dbab2db96ac1c0bf56294b8ba90059020dbb9a28406d503b5af29765d233a9d2752aca417017c555ac3ba512d8c6e606fc6192a10a19eddf02e5d78b45bf8538f613ddef657478265d7f5fef5776e7ebb3bb118970d799fb2f6bba5f4663d3dbb49b4da24f6e24effed6127904ed2527a5a0bbb1c4753359063f616410b72b07d6cd0b9b13affd83aaccd2c120a407aa31b00f9efd04952877f8f6552f3064daed77f437acf524dacefec94ffda22b69778a2863fdc74530b61a107f690da111c8597b2e0e019d4376065a1fb7e8576bfcd33eacd3cd4db38ea1f6ebbfc5265fa9b227d4c7396c07771ae5bcb02fb434560d2c4b100f0858a1d22e94d3ebec5a820c62d9e36ee38f820799182f18db2d11f32e69f56c2eb3086dd3b5f526228caf8c6bae38d4a9de623b579aa47683436687c0df91dc9d1e2c75bb0c59ada332792f9e2be89dbbf5b72e3d479c8c0c719a327e08645f80062897a2e82885a9cdea4b7b9cc00675f320dbf312ffc4c9bcf3de1a89865418509f7fb9fb384def6e8b749bf4a2d83ecdb4a751f9a4813b1c280be2c39e7eb34b938b5e6b5d9a9d27d4c520d01fe29ebafc421ecd1652a477d18c9ede12900025c255a35885272bc0031e2ac5469842c491c34e218cd82d3a206fa8e67cde377fcd2f10fea917bc7d6c22674a1f1699af39226f9a3b2b6e32e0dfd33e1c03e7138fa2ed695e71186ddc66e011b3d680045237881a4eef907d4fff233fe31a44716a7f8525b657588bd14dc278b6437fcfcabc44451b8ec8656634f810bacb308060e75346d66d0f2b5aef52c3914c2bbe48514f06be6072871292b0332c14f9cf5a1404221b45aa637cbaa7462249ee0add36aa573751ff882c916c4704aedd1f0a3d4103f586762e7c0a183640a0d1fdacc3920b294f317274e64d44d06d302d6f80caf018565a7a0366abfa96ed4393273f7044fb16d746d17541da9969f70898298cae54626660ba7e81fbccfef92f8fc9014446b3ea009d429c13a19b1d53a7e39961523b1cbd4fb9364a3c6006e9c4b6a71aeeae9b3ee607352631c9c0f588585a0664309923aa75be521b50f3d2201eb76c0e38b36adeec3f51a001a7184073156f5931e73b9eb42b468e629b8f26cce07ea7a08bb445046f1dd0485b4aea68218f1e14f5b454052950444cbbca928b0c16822a867ddf3048c9efd7c65a9087100fccded140e45b1ce0ce7fb0598e8a11ca24ad6fbf6a9cbdf0edaa46244b96ef243db47439066a5aaea5e694b7493ea952cae9d91217509e1b5d2c65fe0584ec71905b69760230a8ac2e1ba9ada58df7abe45a6fe29f33e486cc72d6636eac2d743a061cf4b1fde1c2718081902932d30b647b5a662bbca57335bed668984e98f45a5d80261c6f4d8ab78faa8984b422618bdf595e705f344e14bea366b89f9b02aea78a836a7463bfffa5f7ce029d6fa7e13d0540f6c474146aa621254115f0988cbf6288f1a44ed02f44c0340d8547280b9860e10c62103a3e899b018052b98e6737af7296883658441c8c105ff04ed1e440e6bafcdf0fdee2c4eaae06db67e35ad728607a1d3451a2426e193631fabe6504738a752a7c6b73188f58cbd21b7ea39a511b61495bc8d7f357d9e3bb362157343812e7c53fc8feee020f6ab484c9b12b684ff1cdf9cfad89d50eb82539c55d7f53d7b8a88086db4812a36ab5fd723d27ea70fd71a1ff0bad58a01e8f8b85a304463adb512b4f3971950e74d192774ced8c7503041786016afd141b3e6535c2bd8d978c9151e59b48ab7b4d607c1918c19004937f617454d8dad7ed266bcaef72b52a00c6c6d38eaa74047931f93942d65b0a19c0b4e47fb482122933ed3dc81ec0902541010736184dc76644ae5d343e4707564ad975db78e7708d739f19ea7e4ac1bed733d9d1d4d6a42153859abb5d43e0ff1ec5836ba68af604153821e6b59f837fd95b30d0388bcd06f0c0ad11b1aa9302324ce0a2a8ffff36a037b4b13e19ab0158b6e42527fb9362cb195190c643d4e1f82a7d448539e481a679838ef9ec8660890c4fe28dfcd2805ed69354a891a8fea882ce2be5647501248fb5565255df570a88ca5f836bfdc0e1b3570b67a0c26130c96ea1733cf072d81e604ff21c033ae95fec5a0795920e2496a03c437c3223e27acc920843f409fd688516c1b9c0a8495051a9ae1a439097db2845083020dc336e0fd32ae7b1387d284f64a8eb5cc1f45eb825ffd2c6ac6896647f247eaccc92358393a14408264eb16efb10f23388699aed9b3fed8fa3e64d1b26fe20fe530c7c9c39156ef22de65991e7dd3bb6c52a337cdd922089810608392e63eae78b276ec14676787f632004762f4392941644ec2cf6587c9e92d2a27a3fd5f6e451b70b4c5cccb67f86d8bd9415a98904799ef8e5e774d3ccc49373e87c0fd51b57f1b84b60fa4b60e3b27856bb3c65bf56feae03c09d4b1d046944bd669a659aebfd54e2d5bf2c305522781cb4e8ba2a7623222f003eed302723bf37e204a82ffa6148dc23555087f9176e6e9956b0947a222fe5a555d60557bbe553e6ff108f226c90312cc0620f18f9e98de25c88dc40991556f3c1ed16807f7d368a3635611fff559326f8b8c5ccfc435c675cdfaa00a16b64850d81643ff98d1cfb69f180c8a05d6ca6d4fa2a3dfed4500a0b811135283d6210ecc3fe983712978d1e0f41777174aa162be7379b9b14cd0e12d00a522f272ae264f989d6ad487ce29fc85c79dd0d8e90bc5d3bfa3656cb88154726d4153370b04a61174d50c1a493d0b24ddb8b2e5675c6e0119e705dd48ae24a042bfe5636a23d0d6abc108a1f336ef063163f76b0fde44928db10f440820d1cc517e93328b652214b4bbb2b7de2de0cb0858ecd111294dba9d5a7b601ed631ddb8440a7f7de05b2c2089ff6c3d66c3710321e88e0df65a297e5c4ead8eaa3e54c50d9dc4343627d8729ed40ca75551827b25088264b081c5286580ab1e9268c6da03562883a6522a45fde5b10194d477ffaef0ae6fa17d2f49328be6b47d2a1f32232e99bea53fa75203db7de6b16281ce2876371073c9db6fa96efaa20436e402a8ab07e5471aa2c72a73f98924471ccb3af181ba37b269590b936032e5d1bd2eee88777f766ffca360e7596ede02760ad83bf68d3baff498f31282adc9f450e3bee06ac4e189543c68819d5f37665678ef2f560298a0ade895f8cf2225beee247fd206c297ce1215f43eb08c0b71cb66d08c7dbe7a2688d8dca3b6fc94cd6ffb1a6ec947fc15406f1a33b441cb5d65a1d6f8c94e895f63e2af0cc61bfc401dca9f7c2bf1fb7e1bee8df9be1513502ebcec60c2657c3dac96ed826d1cdad020450f6c383debc7250276ac1a32bfb39289c12885c640fafa71954275616813596990c4cc201b1a2585f05cc2d002ada4da4f042b1a085ae63471bbd47a970c221065d4c64d12f1237c25ad964ce1b87001126003b4fba6006ea090ed0a3904d824ab0ce3b0975fe9385247066f5425010cd369786b8805944b43e84566a10fc1eaf1f5db60dfd6447c7a039b984415ee21c42a1431c4e219ef350a87183a4c5bc80f40b5bedd6a70f9b038ba94a5a3cb6ce6c7865b556ae03bbb45adf667dff41156640ed3ec3af5e8311d2a370607be52ec88165a622b6c2d548ad40f7f89710196a663ffbfb0246c72e91e804239815ea76665c7e04e61133a27362986046a8c2795b2a071e77b4804a9b38a5b029487cb69424dc25e0a1fc568e10d9573ce60435f4961240e1d34d4183f3d202d9fb31caa4122184452a6669aa86616053a7adb2aee13da7b1649e74bfb638e9082fabab48cda0b33602846ba0fd32a3aac8dafdde20fc3c22d54d658940e20c3ccb3ae3752220c5dfb522d930f0fc5bc301e6c3e9bb046089d9a6c65594c4a55e095842d0f2a8c0f173dcb2d60ded5ca64fdd538acd785d415a69b276a9370fd95b90e6193a0e4d80b2ed80dab2c3521acdf129ee37f48ac5d5ef9d62692bf681d8e1b8b58dfaef0bac01ccb9e0ac940c816250d60bc31b2f709b78ebecdc252f8d21cb289e7894ef6a20eb30412c85e7555ea2b985e74568c052322d6bb9fa512deffd00a9ad9a87eb9ca74474eca7f81952c435ce2fb25e4dce1c7228856d663a82ab21443c7298e4d4e8fd7315ef449c68186468223dd63c57cf207a07135ff08d7a409c8ab6173b68a0dfc3f761bcea01ab147f93ad3dc48b6414345d3d952cdaff6b739de7d28c6137ba315397ebddaac77a4f38a7d59c5f42bb0d086eade67872ffb51c14040bff60f6b0fa28296078d839dd1c77c73326c78d05a014deceaf002b879613f4b0e77a2740e209af5cfbc4b29a608e95db1306de9ca07a4eba5bfcc436ea6f9bdcf90ed4862c4018e9c297a20bf5e82eedc0b5dcc2fac185659c7d850140d324606fa613a0e90481601bec93f7485da1277aa089892b2612ea80131647c89dab06dde0c4bc022628402bf834a88db4c60f584f28d110860185053c0acb4375ea8c0a95a64305174087a5b7b395a5c3552fde33935856dda4438485be90ec63e1e9279aa626f64ad6f1ec10df007b94fe3e7d52b0877f1205e67816e9ca5952380f8ec49cfd60a4cdc90a092556a25ee1f20d312b833e6611f7693c087c1e0c35d209377538c5f6bf0a065958776a5544f71e34862a5614705eb4027f72218a6bf2de4315efb0e8d87fa7e6508423ca616663bf71406ada8ea339b3ec774728f99748bea1d3773aaebeb5da901ccf0160b465643e7005f582bbe7a5a11fb6f6be8533670d74da448ebbff7a4ea7893adb69f848c3308c07275d2321f7a658a673b06a406f9d8ee315f8c14e4b58c19745ce209ca0ad7e1301bacd182bd14c4920c74b4a8e1aaa8f0d9b53ac57649b5e32b825057e2171f371237d1bb07d64d1c079a23f64e9b2523ae45020b322cb9312f7c21feb25ced51a09fc1ec1557942d21550d0249b496690d19e722554336ce3e2787be2ff04fc03cc6a7faa38f07a440c316650b6b478a6e283a9de8507897f3a724aaaaae19ea2e7722c669fa2103a3a45a86f6df0f5419445638fb8871f0dd007190a928016bab0a65ec50c555062e2693c3aebd14dac340d7f808ece24631312425de022afec3a074821f30ff19f3a5435501778c5b707d07efc830593e50f4ace158e2edd655afbd06c27e250b0df72899c2d80455c07c586139d106b26b2a9bfa996c3d65ca1ca5fcafe04243f1bfe02f080071654b6e8774f40096a892d95b6b43aa2bcc004c1dcc3c741fa49775b27b918314f0ed8edf557e267996a72e0be1de6b972e5a88e39cd0754ff958fd48d292313ae4df5d0b8934fa41fc60f7d16cd94c4e701c50e06e43ce88fd1054a75f8f7e04d076e5c6de51c4add013012c20a093bf8d25bb1d080058535964b659fc1074f7c432d7c36c98056fa98c1bc43ff8996b8cc7a014e79f825df1686af22902e5a020d60b8efcfb4f13e92787ce5ff1174b4c1ee87ace8b407b7344b3f8bef1e7ec5d45fa0efd9379b806cc69b0e38edf03d31d64805495dafa283aa415077eda63fe98d039089ab61031746d4ef6628fe98e25f9c3f9143391de0918c91b1886fddb6e64cd27a181005d52d62bd2db142599a66e0aa2ccd471a068071fdc13955e9cc95758a94b44a95f4b192862cd21337661f933f0586c0ef0dbf73776cdf2ef86f1c2a2860d51dab805385cb2359b2e08c811a302dbd342a84403b93f02640ede77368da2d249436ae37c73f3501b31db31f3030dadc78f44975d897a74362b3930e6e56d162c06a5860d554e000d807a208466d17879910cbe21c008917890dc9de80c04a33973ed8c4d0c76bf2743b5bdffa807d455a02c12497874823696523d2f1f33baa562d9f0e9f29a3da3b571d3b8042eebbbecfc39abc0733356bd5b0823a9f12a4ee8e0a7adf5f6daa204e77ab2394dc7662a610c9658d23e93193b5aa8add3a048440b339edd96cd9241032ffa6fa928c997985f05394896f46b137daa6640940ddf3157a70350f5b9fffd5debb1096857013c2c1e5659d628a9f24448196ecb3f31f1dee847f9fc563ae706d160b71a679e0d25fac334fda1b3db56bd0353c6a0a6ee35f43411184efe508cff4c09ff87773382080a0ca4d5cff15463350dbdbcf4e05a0af92203c315267868e8c44777607550e447fa02f64e5a633c00f83357f00ce160414ba8bac32874604142b50eedc906b35d6ee05923fc788f6c9221c2dbccfa35dc01ed847fbad53a62e45930276e5f6423b08a0c6cf024f76d5ea9425076f582a41a35a21656ac016ad474426f188d3ea73f3d022441a03c81c864f606210545a27f13497ea18133f8545540d22401b9d85b58c3c5480f938b86159b763eb023f34f944d958680c874ac5335d8cfc5df050d438dc6f454ea6d0998c6f34f21d69fbb07143cd3edf74067d8c0fc928adcedc69c3ef8975d983b9952051bad3ec8188491be151b25293b93d06f513e9ff3243cc557c7c48933ab192229651b00747cafb9cca0ee57341579949790f1879eef92274cfa56934a51898bbf0757628b01584443a73856e5216b605d532f8838ab77c9545e445bade2638b5d0178587d0683b926f53712ca840dcc0265ec7cdc14ff4ac3c1f5c3d2dc21369bb9a396b3d8d22a222c217c815402f9ad3c5057af02e89874ea10d861a179f2d6991227bed874ef76d0ef2bd1650aa3e6137783b937f986482afec61b0f3d08bf906ea0063f6514d6189eee0abf6f5b0308056d7ca17d28331889eb80b4a35da2680ff251f709c9ef84619d9fa6782e283659b5a9062804abe5c5919206c384850bf1714a627ced92bc7acf366284d94916bcfe8551878fea48ddeade1e07938daf3853a577e9a9c58fe7508e54467fdc2a74ac198bc62079b7bcc24c111f0e94773e5fb06387c94a0bd45191b74f364aed81c99d4d1298021e7eedf1151b64a03eb931b5a4ac6329c1f457dce06efcd0ccf2ca084a6bf7ae306cb474a9701958bb30b969f52b1e4a2c7bab874d5cbabc060b8dc3779a9c78d47d543c20862998d017e363edbc72e64ced2af291ebdcbb94735b0b832307ea5af2e0d6134236057ca0b1127346a9f547855c9219b32977538dd11930d074c385ae5c413f399dd34a0d99dbbe2024c677620b6dc1f7d5b0cf33578f1be8fc8cfeae641136db45527847dee4736a80d4a4b747f0c75185eb16b28a6f20ad0c0cb2e8131685618e252ca5f022dfd202ffdfc2ceabdf8c4967de5b5bd337ef169d2c404fd7b765f2cfd02cd512a31ab5f23962cbd7cff4c069cf30b855dbc2a4eaf1fede6efcb3cb8398957051c0ab7ba69d9e23c890e7a0941cf50e70c4c1a9efd21560e93dbdaaba5451c8e276c160ddde1a8d1bb9d0256a8b00ce4e1d0a9069aadd81cafd5b6f195fe8431dfb294353a532e960fdd200ffcb7ed7c8ebd60ca6dc75f23b750ca006324d78c3cdd5f8c1b993b8d44b1bdfa33f374314223f284d5e3f592dba428a995c5f04718a6913296cc10edda730f948fc8f8e3fcb7211ade30d9f0f9b6038e1d038c6359ce759e3b42391b5699f49244f3565b188544d7b9c63741bf3a7394d4e0fd148d43c83b05b2c3e285fa5763bbc9ce180e810b957261d1074fa642d3ac1d633519a6bbf758fd2e3a0b79a0131a36b9c7041a94115a5f632351de986ecc479ca094e43eed0fb9d6f29cea9fe5578f5ebd00daffd85dd196b0afe8a046889345e34b9de7c01c15f5ca8c62bb862229e051ca80c6ca11cdf3930c032b869e4b57381564dcfd36b037e59f2d5089fe32f5574ec11a3c558cd5511b14e6970d7e062a27913798d41407c3bc83b775f30e4730283dc94970bf8dae6272b9c296e2e7f06b2705a18b2cc8a1f8069a78f2dd1d5ba6012d9fccd426920a08a63a9febf1f535e7f371d5f9ee3b9f81e9ca626ca5f6dc2598b172668b274e979e94360cc88c4962f36501cca2f29ffbe7f7795779b47a886d3b7b7afe8619c531bc265484984c0d96286a8e42878a3e2d348b1cbfcc517938c11fc530f3b54d6d88b2e4c901221b8d58270862d96b157926cadbd04b932c5e0d661a6837319fd34282a8053c1b39674f46f9673899da66749051bf61b3d3bc0179612aac65dc091a7a6e7303bcdbea445393b3dab159bd00c68cca743bdf564580667cefc3ea197c93d4e2ed288755b6ca6f83fadd40262827f75eccaddfd97464a38d98e13a6b5251d05c4ad040f341cb193d5fdae97f8adfbfa57555d07c626387e67e37f7abd4c43e77360bbb12b4ce6689e5c798ea6b76b84e0c2a8277f14b74eac6d97fd60266b6cef33f8c699c1b8692ff82216980fc8aabd995863fc50c8fe73e0b8023285a4b4e5138a2476a1ae09128c3445511a61599151bded225810e2656808fc33370d32a3bb0dadc59b0ccf4b823d53abd5ca8e4515581342cfe997c8fd3f541104fdaeefdde9af33142625e1e95ce1cc9140c65b3102b9143d926a561653e6c4b0cc30d15055a1adb25128f1062a5d142f59645710c1556927534b6be14816707c039267fc44aeb89563ab7b2d948892b076786452f70313867c5927b980aaa050ea4857d7a924bbe4a278b9d268ecd18d1ccf36e1ddfb2ddd9bac6870e62d68fb295d6cbbffffb67e4a24fdab98349382b5e444176c21bc06b5c846545b28d646a634259f0823441f8838140e073845f03c80426e5141f98ab79c1707f82c70a978a0e2b151b599f4e44d2552905f1d1e88ddfe2e3db3254a2fb1bf216954e320b8a80e8591a2332ef3c554c003dd6602444d6e53ec2d847cb4ebbf2547f2b3b92dab29c3d50dfdd9a315f9945161a6fb1cdce8ef4d5974552ad750a9e95eb9fb9d925380cd9343a97f0d445bbf874e461f17b2d1590d1ad474851431678580464a1f22d9d94a9360f6f607de818bc78da4401a62ea364ff6b7034704780e9923534177b76b5c086634cd27f0ba46b8b8ebbcee6769dc7f4d3dbc5770da3acc47107948cb7f02502031ed82e9d69c735acfc76f016f8bc7a494d5a19206263fc866729357d4b42d4bafdfa568503780809f172825e92494fc77137ab32662b93a5e448139838fbbfd5f9bcc36e15aba0126fb764b92447db523aac391cbb915b5de041a9e1473672e56476c5c667e61f66398a5b80e546fde9b6b41819ba49b528e7b05e3dbffc85f78920d43ea8847bcdac3fc154115dacce2b21fb9e311ee8bfde35cfd692d80258b6cf40c008082ce8b1bdc3f405ebbb78b48e3236ee88fbc86b82c7c2fdac40f1eafc176e7a0ae3e6708e355e4e2458283766dd122520a843b3677c1e3b1510d6c0cbe3bb4792d00a7fc6ac4912bd9242e4126d59aac319ae7a574b71014d5380e035fb41e5e92c2eeb85c957f7271954961008f735882fb57c35891395dfc117d021f3d9341d5e2126356181e0d9417aba167ec9f0a0cbe5daae56cf2491b9f636f691ae18005fc621165d571a98a2b9c855b78effc1a5e8d0792fcd5faebb49258e68b9699e6d57cdb9bcec99118374e2b00d891da863210cea1deb0674540d8d0711ecb5832a28a13e9d543e83799e0e22aa568baba5416d78ea8817bcfb83045b97d4414454a46ca20516d066f6ae4cabeb7f4c2cad0c1f5293267e2a502c7946d43a0ca782404639c9461699a17e376099f3d6a05123e782ab9957478722699e8c5e4e301550281bcd823a0d8a752faddf7cf739202996a4f9f8c3322677ca6f514ff484a32854325a59e99f5a15366a6582b3f1ff7de0a34459ab555c482ec96c849dad34ce8b4e27aeacd9cd49ecfc4c91694f33d7d4d03dd45224bff23d245b8644aa5ee895236fafa63d55bc63d95485bc4f00da49713673623ba8a110e2228137448e99959a4e939b2a5cc34d5d6b2048636c47907f1257ced3979cf74037c12dd04824f7fbb653a0ee498b00917f4c4336382cbc5d1176a66a6368949d6db123d59b9921b5982fefc344884e8efee01d1ddaeee416e5a7f9b0849c2ddc57fe02480635603c38de6ba1ec88932415ae0da9a79c7c2b58ebd551524b13d51e0fec09513fb4d17c3fb608dbd581026478bd027b9e85fa378d8f27b549cd920aa6190c2acff50f2062d692b00816a8eb3dd4da3f6395115c9345a1ee194957aa686f17ef84187daf34c747803999349f52a37147eb4edf3276d3d48a3c9871d019fa2f3a4da74e0d3b1b0d50b411379c723d9f336870bc1d381e69c9f205c6b24290cac56758c6b47ad4413ab897928c249700e1986bb662a6e9281cd015f291f5ff08f39561c1aa7440c6395ac6a8a2a18ad55949931ef3392b7c3cc38f40cf9613a49d9441d9fb89195f230d03f14c9b25cacbe09190657f8def68a1d1131c12bef9636c4499bd7b526bf783dc112168474812b2023699c5e40a5ca90f549c224f5226150ecaaa438049a32dff4ffa508448ae6dc201e1b69d802aea263f2cbff08af3f070a37a0473a7b5b14e2f4b1304caa271c1f472a392efe636a412a8ac9f9ec1bf28f8c0d4674806b88b2cd7c1026112d3b37995887b5cecd0760336189e646e31c2255aa7da83fe8e27ba2690c5b464e64d2b1836f1cb47db4cf400d87e1400ad392685aff6422826399612133125b3201d028881e082d23325a1c2f797325f0343e1d849f1aba7e9c4ea2e2405344f6358dafe564d1aebd4392b0ddc29a663c10f7ed12accb1a06303e726591d2d46433bcbf1d0fff45951e465dccddcb6048a70445e20215dda28a31ba899ace7a48b64248dbce246c384c125a76cbb05509cbefdd2333c4df6a18bff0d75625930c2107b7937a75a8ac088c40cfe8c31081123cad03e814065fe0265b6df9bb84daf3f904ae2455a2b1c3fac61433efcd59964231e1f91b21313f8745e553d0068ea552ffc041076201d0c2582ef10bc2e4f0a10a588ca32feee6f29ad95c256fe748e32d48cef4f970c6612b31f70b4e2e4a925b301707ff226d52b2b09d24966f1ba2ba8ba94cc4c1e695c6c7e7bd9d2a96b1f45c7c15646db29b5d4116ee96ed4134dff9f0338d990adb44a30b68c9893b6b58292947db61a5e7fac701f9965e1a0f26684d6195624f230b55690a4e2a662dc4be8554f68b3c454bcff304063b780e4b2877de615c55482c126e19595157e34468c44627ab701a8ca0d18301ccc2fcc6639206864458885c092bc646c4950a896976f19fd550163a626558025bb2d24d97a25f6305dc312958948ee3e77eccb68e83d2376f80810655a86da9db843b9fad261dd3ec65482066dad0b861d99e458e22363a9b2c1804910405bcee94607faf9cc5a0a3db8254a873215b8aa1bd7091826c19718b227c3fdf6be235dacba4a34ad09239480b70644f61f14244ef64bb44365c3a3eb099e84a9d86b90740db7e8fa375194e399388c2288879e6682926332c6e04414e1333b2c10a5fafe323358f2b08dd187f2adf7e342976eb8446b4bb11c43e21fe3f6fbc0acb6ad4564a47c7732a22a6931eef1df280c825bad28c037f4f5bbcee6101beea325a14e91ab14600c5127e4a9f009f072b3ae6ffee1525c6c6e393a37a2ae3358477e09b33b982e96b9878851d94a1e6f0d7790bd0cf2530d141f6b2f83822a06932ae95c06a8a93558d00cfced4966d7f40f7d47959b182030dfd7e1ca9b01e0f418eaf5aed01ca99a7cc4d5d49bb59c8959230031d5cf51b541617653680f0a4a4369d2b5811c7cc0dc96cf2d5e1cc207c48381cc9023d4c620518bc8c1f75656448246acd0dd8fe6ad9218bf96183834dc9728849ce249633070b0d7e28bc95a6b0948fb2ca33728b5cb5f3e09fa3112e4d0a5053604ac7c7470e09d4ddde54db07253ca50630e2d92efffbddd58792febff184dbb3742ca640d72cfc6636da11118269a7bb8da90a3015cd5b52ed3c887eb5305827b77e6134761c99ee6e2143c1368fa5fe03ec45f5e9442c92a50449a41c161101ecf96fae2bdd68d6acfd88fdb19be9821bb93f2cb2356cc8c1cee62baedb86d198a2f834e83ccf743906db231a41ed9d624c5971e272135ea481f53f106e2e69ec381debc2e73324f1382b51d70c81013257361e29b164e93c8b247b952eee2453b241375795d770ec5f78c7c530e6a62d11b843fc59e4157a27c02f09cbff5568e594101a0afc7aeb4d19ca3c1408601cf5151bae7d56bc8b27c6db2df84d8205917e9c211a516b530761779fb823687c30bef17c513807569742868a4121e7e759922404aa3784ebaa40d2213e55605a253eea27018a970523bbb5d9060137dc216f300206c36176c519e1233a811bd24208fd9332aadc8ab25fd239bb56419c32817e9bac06b55bb51e51fafdfbec6afc92c71dd32d2b183a9c11405b14b2ef85b2a60cbb33bf6f3833508f1e0ca3665981966f953b1fcccae0410038afd588c2cd6e328dd95c0a480bf05040adb753dde7efedfecfdacbbf77ebf47462f2e8f0ebd7a5da99eb58a3639fda68f8c17497fe08614ca352f4088293f4637d995549f8eee9f5e9cc64da849e225a5cfd414262e6dcc4be194539968548a1138a266fd524b8fd8cf3a31dc4c7acef4b781a2dc8fbfd40db5d115afa7d4bbef2345968a64e26e2f00454fae0ec646b6eabed234f7cda503bf9355d282939f0a0b26cd15ea0ad460a7a5bdeabb9a2d0efc344abdaf105d7e34d3b4ac606a89ffbb65b3a6e34a95c374a293c6be73d472257377f81e7396023e757b2f5a67aa7766886f75bbb1a68eeda78b0b68d5d2b9d62cac863b861014bcad2622e87335e3ba2c5c987bca1c530d7b3ade2a0440677874572370f6a3310ae0734edbd7fba78c3b46d534bf733c526ff5929cc43be449e817971eb83f733dbcfde2b92f888497e4462d6601059f5abbdf9d8ecce04ab51feabfdf59c54773c08625caf6beb568f9743379187bcc92ad5127dcd2773f09ccce2e810f9fdf330f1332b24a328a8caf13c265e1dfdcbf4e173f74c31c30a8b31aceda9e6f021bc35fc1d7ee04f6f1a83a4b0c03ed957ca9474ca0c66192e4cb214b0856dfe544a0c133d10eba5480328e036d4dde97fed26fe54286b3f4e2860d724c51e711dcbb9f9bdcec7a9b638ad95c7c8be9622fa31259ce92740a19612ae4e511a3a9d89101ff3e1199c7c77ca9bf686a6b98f2d94119b038385814086a027eb1bef14a534bcdd18161457f6b24cc85d57810982401afb373d42114513cac3744766677e0bd8adef97ed43527a85ae270ec8b73478e1d1046ae187d42b2033308f612b940b24a9f1a024117668d5714d4fb15234d402c1893a70f7bb8923f914240cc1679ee93988f828114bcd2c74fcbbf608731c497b483e3df7b0b19914ecd352e12250967151ca0016ff1f71a6d603e7d40b35c970728788dd8e325a41f70cec8023a0c2fa48800dbe37c039910c2b64e26c0b9195b851477689ed3125f49d3846f81a4a50160a006ab9ccb85924c3c9bd2be20acb7931c1fad9b782b45720a8208e1f9ff0ebfb031f8f0ff1625d631d9eb370d4e9bec1c7e52f4c2427a1516d34b10ed57ccb2d8463207f0ad7ac811907a7f5158fbe5f78a10cbbf795f51b3f84def2e0a4bbffc5e9162f917ef2a6216bfeadd4511acc1df4d812213e457d5754cc482d2a163220a4a878e8948d03a38266241edc0311105a503c7580fd1a075b0f6df77546dc4a06c05311cca66d54d3530bd293cdcd2be30b5197baff87042440a3ac274ad4953714a04e2866efb137b10d8a025d0e4dafb551e64874089e89a7a094d7249884f6800833c8f876511e59c2550b8a403b610c75288b486f82dd61183986430b15a422b9bbf9e6700d97ba0f55877491885f753906ae18672987acc4310319c7319e581321231c974274edf430a2fe958292c34716bc12cb15f4c3fdb116fea9832c948bdf70753bc0b0bba2f02352d65330bb1821d4696c769c50ca6931dd4e914d3a096234779eeb3b459535f067675e8d34210de31a9818b7b183781de4082e77787e5bddd5e0f8d0ee19abae165a49e875ae59b7fe9fd753e195860d68ada678768b9811a4aa9162bf39ec27892e347961f5a3e0f85be4e6614f949f4bc9b4c1a235ed370be62dd99b96737d5e2bcba52589ccbe9743952620be7722cdde0038ebd43713b9907506273bc414ce5e30c7d0cd4db42e65e37c5ae3f22204b75703ec38b2b1b5f12d24c96880dee4fa3e033ab3bfafb311d5ccac047119b8c9abd0643e13b2b7a5af55f1bae372770eef21dddb88c411a3a360b522806d4897bb8cf65aea1273d3b58fdaa45e140112155a14349f82b90d3f832549933f17fe5300005acefcad1733c23227a826475a60359bd8ec65082445f076925c0ac239b2327c3eb62296e4414f6955fcc941f3ee2e9f81ddb8795ecd1747021669fab27025b208fb976cd62b288c1b4bec1b78acf8c7e81c14d04b4b8baf183cc3814c1a4b2b9aad974e55cf190164b734e00e20cfa449f0d0e4e5c557211da2dbb9e1a661dd0220beefdfeea5d75754b817af7c07b52c2e8ba48301c3f5aca08524f2ca8d3f528c12a35432ccb216a881988c71d26435f7b6c16dd76d076ca20a90f89abd79a1ccd29b153562999343c7aea37985e1a4d4bb1759c4f61f0ad3ceb8eaf0ce5f725c4e60677a92b86e919334a3492d4e064a355d7630cee58323f1f5ec912a5b7d6a6f438afe0168cba1196f7a0154e4557f3e6a4ec0be92c1d5cde65127ca13ce975a297d3abf1ab4d1a30a853b0b691c72b23b8ad83296308445fdd4ef3038d910f98b30f1c6dd59d488b0e4b64a4a3f4eae3c0ebf2330f60a0f4345e6097536feb8770d3db5aef84c6ca1d9bcaa8afb3c41b606801f85c71b2358ee80ddbe3e0100ca3cd7b0397097526784d05f8c22828ab3f0cb0904bc5771540e063a4629fae2635be35c6de7c51a4d7aa2ec3f7ef5bba7cd44f6c15fa25465344972fe2e8df17936de871bfa328baf245739870483de9d7e3bb41b5bf44dbadc79618f9d7d10f13aad22fd406c99a289e82cdb57110fdb658bb7a29628f8f79ed9358cc363698b6e18fd09f7324379bb0d99a03f2962d661c36c5920902b0287facef203ebb874e0e39e0408e83db3f65121123a8959fb0dd0a077a8a0da61c0b5541416582edef3137bb0edb1fd48174f304e42688d5b1a74c889a32c156ec3341c34be3343c24af01565c54478dcba52e686ef9f84782bb1229ac30963eebe4ba7470aa86769c34f03f1fc2c91b4fb1a2260905db9160b9c7bd722241b1e26974b57f839616914601cc2307022af9c25cc878df9e6c103b2f395b06d1528a0d83e29b50c082ba4871ab2da1c11469418ca996019c20ae3b91a0b48d1bde19110daa50ac4d2642e0fc3cfb6ccbba507ba86376f0a329d644b09b479180fa6ec863ea6721f9d6a2615f69dc04521d8fd89c8ce2a294e89003e1a96708143b00e63c8f447f0fe4478a2581b5ae7853d1105044ca83f0cfea8b63d11f5568065c3c71c0acfa044fb6424d4883ae0022d63ec2c8ccec3e98cc4923b95ebc0b29431ca446cd4b295bc39214bbe988b95ff414179098c767c43106db8ac6a5a8f809affec41483f5525fd8a717310e289df135a8b832f3534ccfb0b9d5b79a046126909504f5b72c26721a472c0bc49ac9dc260fb56bc3ef82317f20c4d7f0e6eb0bf578bcf45700566a8bcac134d765ac943e8d850b342d09d12daf629b65b13e1574913bc3dd13b36f06481fe3557688d4e62ba736eb2498afce79b121f62131cd6c15ff5c6b9f96b82e07f16c68c78f261f0bb3e25a68e2b0bc45d1f0d4cf4ef78b183d3f5c4ea2511423a2a7a048005d0c4a1ac4fa2d4115f2ac6997c94a003f90b9bdb0d29db92426c99c9ede87d0f72d0c969d1b5dd0fa0cd188f187d15b10add8bc850d0904cf01d14f0467aaae92e7b57d827e289515dad52ab233829862ed9126cd4b94b7c10bb5187cbaf2b9d647be23bf01a76c6bc73a91ec9d8c679f96cad338e963f15767d31ff4948217e2e5d814f0b2d17fdff285ef517ba36c99ce72b3d63a74892625b52ff969f680edb3d65c1593562e036b929badeb9998f5e293003cc281a89c30d050aafd6f214bde9cc0857dfe57b4e896941e1b04e45de3d6fad52e853ee2b48826d2b5cf81ac823f4fcdaab8d9222f51bc62df8fe1f4c375d676e0fd3d776c0b6c401f574455f9f0a55c8bc42ae14d41658c33afcc5d1ae56c8699770ddcb7fe948c26c0eee04d98f6075140324cea958904f850163ca70f5352b1b4534244d863cfdff9099bd1508978a3ca6bedc67492a09908a6556c0b3b1af3284e7a93159f8b199b8270708463f474b12acb8b86195da32d356b51e24934a320eca1c60caaf3b032548ba00f4652c49f51ce206af028dc7e35686b2af3850c82b69268a7f57f8a88045dcee095dd7644ece1dfeeea10ed4c3d176bf8c3b519b4f439b4c51a2183449dd33e31ae801877fbb252309cee927f120c42bba116e370130e84d9e0ac79db3998ca27d6a8d6dc449aa4acffef73a2d1b00df3fc06150303e8e05b7b31b972b33a2123ea1952879fce1d22887cb9fc0045c6138c92267e02d318439ecec9346d44f7a6a9733c1dc4c62534226e782237b927200fc3cb30370ec8ec005fa46315b9219fbd9f7587148bbdd81808dad694a21c14dc9d38343c430d2fd95add476273aa027773da0246e4c4ebbfb3183234944934dba84f10b6454796c3ab6c60b36fa378c3fe0026a0be15e4f4b901fa695aa0418c6bd461e38413302db071162451525cd914fd9edf86683d3aaf6789470e140d7183062db1c7237c27d5425c4835369797fa4339a935aefe1ff5797907c04a39a271bdcea7d57a735c4c907e9fc18871893725f8191f6b03fcf9232033c93469462c11450ab86475170d2b9ac3e455a3731847b273159a2c80d6e757178db790f4b4e83ff7089a2740698ff710ebb169ef82511766258a9a5228b5583169b5239f99e932e56b867a54862ef2e8d159f9248077a6c2aa27dbe5a6c510139308e82bd0b8a893c16f8e1fdec94660208d57bd05013bb2699b4cd7131c17f3503a8f95e1056ed745c11b422396b00219e57c89cf6dd779514fef182f2b995a2e2b64016c03b3d42485c71e87bf1204b6ae8bcf07bc7841e322bf1a88ff55496621905dd2f6fb9276f4e3f65c1a55985237287b563b374017cd24fe660fd03c9613874b6979955519e2ee5037e4d7983f16ec9b1f13ab2a451d13307767fb4bdedce44d38bedb7ad6bda4aa515817d01286cd81607ec6f8b7f43578492d2739a2917a1b5f6d7c021a6805546cad0b210428ae4046cd4e97cd2dc26e428f1b9bebb8de481e3c99e126e0cf632ee5c3c090cf3a492369de9108142138a59cccc0c7111272f13b015393d4470e418ba1934c1076819ce0a1b4fec92042def666580dd33b535fe44cf4135919bdd91e216aed6c57a511aa053cc88cdb34f802f8b4dad1e5e9c072cfa5bb3e59f1b8e1f96168eb85f65f6d4b26c22baefe7a5f06bd620820743e56fe84542c9959a96d135c157710a14da1c18f2cf72739f64c07d3b14225157673e9a40b96f410dc0b9b7225a380a9ea6444954caa4c8b3be7c873d16788c21fb0b65c69c98b17f6aa844e2387a2bdb2a0ba67cef2ab0543826e1779e30d8cf2fb9b7e9343e60c22dccdca1c5b281fbe0a67918bcbce23d0e3d5d3342f770a2b72290e4a321ec6018497c0ed58e2940612f2047eb4618da46b32b39cc5dbc59024d9bed1566e53232c6e2756eb00911ecd71b77d4fb65d6c52b0a4e77b9846519631dbce1b4bd1f3e25a2d5cb63d0630f1eaf06d688023769a60d7aaa0b01d269a8fbdba718509c53ce6cbd1660eba8ddd0b38ba7a14f19b9a1eba05d03d8b91744343a6056b02589f1af89335d76b6c2a0bec348083c4dbd6788b86a54ba502508ac110909d90143a5ac73ebd52bbc578cdecae31e08062153ada9df692de56f7f2df6c528581b96b7dceca072c1ec3d44beb5e74302998b0994a8c1a8d2a6e0314be2e5462318e80cbf5cd107f05b74de2173702db638f472357249e6a742e390a9fd071d3eaf445cb499d43b8d2ce75ee40f4884d1b1412ab870a54a37ec24c8044c334df6ec8c3c7ff3b7a6f6ed3141b4ed23775a0c878cc86e6375f567d91be968a2f145a29e9ea9a0f7ea7c567c2968f7aa8708e281109a8f0815b7da1e617a8330956e1f5ba9d7aa71da3a25af7c3b9efccf78f91e2d310ba7100998241e8270c00b5a75629a13324a29890df303ab6b9807ba9a13fae5cdb8848370e43277e64a8dee11d115db25d77497913abc2fd31d83a4f230906b1c10c71b549f539a267f0ca422f0e8ce34605652f5078184242eb53b7aca2b0a3fa9febaa9355d84f4d92f738dc621b80f49e91ad8080204aeb324594e7b6c9f188625cd9737de2a67bb99dcdc7b23ef988c43c02d463ed794ea5eb46b8796bfbc94a3182cf2e267a77f5a3582f3a218669ef5500105fb8ba64e51fda8667deace63ab081740c43f5bbeb445bd3e6a10e842318bd518da52a2c0ad2cea0d73db3286be6fe148f3262a93e6aba9a7a2b8575b0bff20a2ca6a70d67ec0cc1ed71b0a76ac74c71b1bfba4d2c1ca1b0e5f892e65c1bee4966e2557bf1946f5fcb53d30485685a8e0e57b79ce40f32bdaa27219ae3291e9098cd7638d38293650d70ff9d9118172f44d8dcf435bf3854ff09dba5817cc834398feb57fef51a1338014408ff9d890c08a3cd84960fa6f3fd814020d5d7cd52b6158d9bed225d2c891904519c9da1a2568eb19175c0184006bc995acb4821274acf7ade2870a1803e720a841ee4fab1aca61086726c23ef55a9e443fc0ea3b3a0e7cd4e570cf390302fc4a07621149dcc948914283acbeb97cbb92a8a0f3725843695ee6bc62e693f877e2ad8f00101e210a0d322cf66485d3f26c800020685cfde380f879395734d4ae13d801b25f86079c1474b2faf8717d7071333815314a09cf05d9e9dfab4034c8c576e8d2cc759c509fe31541a59433bff1b701b7aaf6c3b53f79fabc095bd4e1a71b3b47f457c80cb60b3b6e2b67f61312dc31a497eb5e8c2b44a1c412f15749f33ae0cc3a7a6decf16f57835178315791888e162cdd519f6c025e71f0ca511499c41c2f9f61fe56195cc1ab0a76b86a56b4202b7c99def22d1bd3ebd00c445310e79742017c5fdc7afeca213c18164efd7e52d3a636501951799f9a9459c943716c013c802683459c0c9c58fbce007c29559806ace0216961da9203e0b901597e6b1cc30842f7baf5dfc36c00424c6245bb638c4bb81b25ed36ef6f3a43aa28e8f602d1d4afe9d668e29b5538d39e56be25f2bc50051a80bf09500ec7936c977fc1638f60c5ff5dd1762f5acea6019ee7d66abc05ddaaf3d6e7d360b2163c3d9bc7edbe6f829464572efbe9385d9e3a981fe6bd649f84fa8376a384ec437e14a5b97ccfa4180bc28a6dbb2150faf12a2440c76d26a0a004e2a00b8e2ae4722cd8412d02d4ac0c53dbde0af3ca2fcbb1b68cd8078463a6d2e1fc42126a8bb42dec06b33c12776c09958e2b161c3d4477e833ff7b30f85221ab2102b0d434b04160dd170e2745d5ace26537bcfcf51dcbcb0b4dec2536b30765945bc32ea68d26719ea9c0567d7358b2977415e18f4f23212116d5e78a2f6a4971108e3b44351f986a9bc0f69fb891ac852e9a0eb7c996fb2c361780afd4a1889b8f319b84d260c43ac09ff1740b11d664187f5d9c8e84cff6066a588dea5736be54de41f325b948c7ef4b0e55250ae884b6f43d8324563b62f02ea3209db283b898ae067e700916764041db52093de326abc8d0e20256b4a5ee154f949610e5e0b4f444f108174168d1bb64e7aff334e9653a577bf9816017e58e2b90963bc13fcffb34e10f64453ca69fd2d202aa7b06f9ac4fa5af06b0705587282446288a6ae6ce90da024371d00ef03f14bafc8d0314899392f340ce520be2eff0737eb29417062532f5c0f2a7170a5c81ef5c09a4d02ab15450996d2abf3c7d381ae63efb768025e72f9f33528a3d969dc4962f22c85bce9dc05b21ca29ebc7a160588dff56e2b9bd558ee11de8a69ad4515e2ef3d80831f787203314b855666898b2ca4e5e0976356f3872efb75a9bf0535e397f781c93bde82c3b550920c1f730321a2bd4e33bfca09767ca5f7333493d92a419b6abce6300bd55001cbea51d9383bc3c7e7b826db458a1f56e6815c90e163f49271c94096cc1e298766926c4ea499305b497b62f20b1c95dbdfea047ae8556f7bfc957a7bafb1a97a2402ee2a2d96b9017daaeeb81cea24e87b26d9655d2bec4dc4829a75bbc883c5c05109f2dcea4e382d7f70b16953d674a66be495b0834f9a00d87bc91e0ea512cc118341867c501690b8589362dbb807399ca62292146ee220ec095ce13dc0fe50c37d642abd28ab2b33221ab8ca028537148fcd0370752b5f820075905778927fd42edc107437174ed1b8f632351119238c425bb6db81f67e50b8b28a39cf00612f9c56eec876e7333eb562fa9e7c82526ebe03e36b9bcbf182778f394668c0c52df25132252f1e354f77bedd7658490ed66cae5efbf42664ed75046ed15e5b5dcbe8fdd8f3cd70f8fbee248263e589f9183c67ec328678594bd20cdd92cf20648c6258853e254acc2a6715be9ce57514a61b3bf9e737c5ea75132d8aa8e36473e016eef632c5590f52e3ee6a011745533d46874b730339b73899001eabb13281c74d923f0c0d2ab93ec8d3d344a55fefb8beb35e0a893f9e423124048f4da1c93672c8f2f2bc09cbe1812614ea6679624593a9e46d1c1b2efbf158e7762d1ee873bb9112f52a1c5865945cebd7268ceaf49dad2d1a31436860a63cae79a9aaa18fc067d5473886337e53e9bc628e47ae204e08562ff829aa77be0de96e8776009f7c053d6c78683d32d964f2d4f1012b3602d4f5015f994219f53e958568386b4540c9f1290e1393e7cd9c3846a9d1bf10101e4b9491279f1e29cd9f6e201c9420a854c56207e4491a0b2f48595edb6fc765c03c6db08d4986f6141dc95d971f9edd2e8b0237b4468ff06e2a6034aae8d23c3567964227082cedf709f829a06a1632313c1dc3b7d683f2301744c8335b07204fc2dc395f42f0cb3df8db5c7c14601e48a2ad204e6ac1cf7f0a12c2ebed4560d1e348328b7eab8f2acbe5db6ccea12e809384dd35097f8e1f0d6349331429d712c76afce197514050f10b6da2f8bb419fde3920aa6766beb29a2c97a71530d76b4f81ad858add6635a374a7ec92ec2769b3fa3d3f98cc9e685d406cbed10d23b30aae70d113d4f8a4e56554e54fe3a2f70524fb0b0099293d4710a7baee2a6e23324e1e010ef1dea48e60a483e4bf8f1e1c04ec9774bd02025543b7d9ffc448254e094902e3692baebfecf918baaa0a2928ca6092d846a3dcf8b01dcb04d949c037d45693f5a51521627e5f3014f3f1441b0e61f4203c63a72e50673db0d29a3889fb6f4b46e298fd3b5cbc58f55168c2b996872d56ce6fc808533658c5093ae068d19400a66d0ce4ce4b6087aeb007bed9064cd35673d8b05c371e0081cd609b65cc118ce450c0c98078f26bebf2637cf4c83714b6e84159b1090a6aed9e70126997c5f7ba51353af2b40c82bc82d5a4fad3ce816af7eb2dc860d933377dab949e7638b89180ca94402543ceccac9d2fe5565fe146bb057f918e99e300bdffa1c75160a702e06024dc21d0ce45c9074b52f209229c407eb93a4390212c042e083ef149dc86ac3b3f47aa6220ab306ec36d9e8d04559d6266e0da30cad8bdd5e82e909dd84aa96f21dfe17f2c933e91d6b3ec58c5f2a8ec3dbb3cc95d24a5c6081eaf4300cbf676cd2ae3d52e8e1310296d4f1e790a9190792c7e20cf71d6668af8673bbca71eb90d265e14e9b1a9c2f951609d054df42052e504371d6e5b8bc63283e704f22095df010d670298c1c01838781f4b0bb6944d223af27c403ce2ff7118c8660c9c2a3850f236937b608c1981b1c28878e0209e239efb179ab7be135f6b3ff1ae55439474bb4bde91092474f6ad0b728867000a7af1c2d8d5461748384b496dea6c440a98b3a5e12137c9fa4d657fbfd5223df41b443a183efbfcca6ab2fe3d72820a609135690fef9addd8110c50d202c0e2a34197b2d8bf56a386ed35b2e5c485d9fbd2030d1c126dc1f44a733608ef1bd05cb516484ecfaa84c5360ec3a875ce4867930a2e1f9698ee8489a0025e794826b67785c4a8c0e7a74fb91ad25107ba2fabdfac02fa2d8eef335948835c0be7d657174024b6d49ee3d493f1a8d9eefa565d9400bc48db345f3ed7d45658dfb3af2861ef57de65b70c1796ea2d4fee8c174af9cc07fecbf5b21b77b85354946147f065f7996b0e6649bbd6936c44c5a384d7f59c120e96d578e8e90e9bd0c7f98143fc5ebd70eb343eb46be395bfccca3626300a293bd982b54db6b5142a4ab29577f861313400d585439624816023f27a1d5709278840f33c9cb9c252fee45782833b650396cab70ab6cd14f929c2aa2560a6352d658509cb51352896cb6ed844222c4e81a282131ae57d0409d9c80e6af7b9ab98314ef48001eef0c54b1458f3cdffb1862834b2b74cf857f0ba6012bda370601946dfe4543f2ed43b83517647e6809fb00d9df33bf2a03af76b1964f88eee8af6fa375cd7ab318c7142e7148cb8caa5d65d63db102f94bf0e36aa6fd75f4bc5473d43fdf55e90615001643aaa923a4da6a4427330a0e4f7916debaf4593c8a5804efc5981dd261e31a532583dd0435db27ba00b15237edc85f185aa503e64882e39f46b92511728859007b8b4089c52526292fcdc18a7c7d9d0f5c3436ff20b73b821d57786cc6dd8d9b1ff0c536efaae59903069c1d8f5530fc7f36647a7b5820c4c331721e650ec4986d7542b21794b40d849e916e0cce8ff0fc23f322de0969fd884c82cece76c328bf3da101ffdf79730db19c0547c8bfbdf7497e55b5099db26219d6d6f671b47e979169881102d0e030d2e2e2062ab33ccf1c593cc92a490626cb1fedf22b36f342828a34587078ced49c74ad0cec41ab662a3a8890a320d28003e0dab0eae58636556518360705234c5c5fbffd9613ed9cd429d45067d972df9896781c322e51ffb856867a0f7e3dc9b1f10b6de11a8448f649484d8d4a443d5d545b2a1e9d5b6c1404ff63405be2c3365c58e251192bd405d47e7b2c74acc50d0839be442908bea0729bcb2a914339117b9f6ccb4e7d9a4b88ffabd507cc3691129e0b84f282d5624d5f17140216d6e057253c1bd0b331e06f0ee5bdc9063f486f90c2ae638282540b056f57268c0ecfb29907a1bc18ae7d529ba7b16a58a31adf004fae271646ed66534059dbde14581e7d519d9c5f5514eb9e4711a79dd23de2f5e9e99c051a641b036e76531cdaab56c82e1adae1f0f41bc21dc14b107bc2bb1c6b94429a81757cbe2dfc81807a011983037d17ceef7c8437881b3f69206570ab052a7c67937d52fd722c1fda3357998f10ad993ac8ee22426734156a241ae713f307e9592061d9ccdeac31c4a66bedeeeb159784242c91552b9fd530a9adc3e00e07e1c1222f4a2511fc1c14449b13f579087aac60d9d4201cab7ca66005f00546d97472670bd78c6048f8b8e85390a3cb7ed4337875101e173a011e73f387bb360b4e4cda17409df40b926885ea1dbcbf8878b51f4549b5c09d7031cf4601231cb4230a2ef8b45aec1a6b514c471a2637960b6cc2ec17302b003c13d5c8605ed33164a2dfdbc546f439dd6e8051c652b5b0fa5be46993218fb797b7a014be685b5ea5869908472f8d0091c358bf23b43590a84ede081c75c5568c57d4be917173a9f46821bd9f5d161cad5a7712da7122b526b13eb809c4a8758ae6b77bcc2d1cabd852d9c0f9f185f64590aae2374ec1a4a313d1dee9958737ef60d0bc685ef090e771bb7bdcee560a57dd3228a86a3fd6960a6b5506e1f72ed1eaf91105df59030552b03cd56ef35dd56ba151511760b9a80962fdcf09e4d4952b3145acf5082ed44f4241cc741f4959e84338022354ffe079c9e840f73d341a1aa6811eaa618f80ec21f721511c91a0b0da730139972048b538f53ba47a6f85b57d4cd4db9994e5c4c2f2fc3c43a05bb2612fc149ec5a8beb1077cd47f844a2de5eb53ca311eca39c711aee60c78f89b34289412857e6435c323408e42578200418a4040799f8bb6e334f1f96eb8d0aed30353fd2076123c8637ef28412e90285a0c7bce494fcb693b1eb2bf179c65631489ce7f39d05a89ac98c66928f1485109ca5e25c6b506e4c652be54431e091f9c44d3eb278ff5695947f1d2e56a351dfc89b3ee3ef8ee9f116097465330b702d3576c3657447be696bce7a71dbed735c907c530ce2f5ec5e0a3b3e34d731cd0aaac46dfc2194418f6b4dd908da5907e07a9810b4faf6e1b43c5b12bbdafa22e5e28651521e114af088c1c899c20937ccb8201e9bb9943e848a2198b6468c8f58ecd06cbc34ae946e15dc50ecbb60e6796ed39fac6d4bf87d7b3eb7cce58771f0d92813c0e6aab765910859b2f9de342e30d5d0658514a88822581c86f2a221b96a871613e9b3b663020f5e80a7b9e3f71dc28052c6b037a5256745a30561aa5031ae1de55a22e936e9d989509cdb30497cd06e9dcc166189616a0be4128b36d01bcffdf19988881201f5186c7cd7db4c7d7a18e7d82806aa1b2b967c17b2254e37adbdc678d1138b3259f4842f6de7b4b29a54c49a60507fb0629072f3918a091d8ab68a748a9b56f158a8d5f238c30c2285fce3861e3c32f11429fd165f418e375c4f6c718638cbf8575c4ce7d88cb1297a58c01257559ca183adcacd4eeee5356d831c00babaf1ac036f1a1b052c4f71bd6111f6bc2ba17bdcbd15e8c3350ac87432d3d3f7f66e2abc47da5bff8e2bcf6ea9bb81cedd554975dd7755dd7fb35755f91cb01195a551f489056f5d71fd2aaf9396eada95bffaba995c1b895b9dcfa38d7d7ab5ef5aa9fcae1e1f89a8b9b61c26aedf9b7e967a25853866198467acb99c03ecc75d96718fb2492b57f71a5ce5e6f323d8e5f5cc99a38edadf6258df4ed618fd33517eb1b39586f873d940519b71836b64723861708617745b967c6e4b7f3ab9db3b3da67c2f56b0cfba11cc7e7f07a15610ad16e8d3b0f5f769b514021e3cea094b2a01cb9483963e7ce1566d7a58bcb976f069b9b846355211ad937e026e0da5c58069debde6d98b6ff253be7201cb8c3a313c4ee50765422e33282f042872563118730a802c4ea37c2a241fcf3276be1a8e5697c4c9afaf5d16872b9fb523558fe2cde20f5fdc563f92377ed60f99d83572c7f73570a82e08c5818ed84e1e5c227a171200fd954ab785573c3dda7e219290cc1ca17222e633ef087cf5d0a0ac1da78f1dc451b5878f5f0d00f598d20d40082941c0d0b9235134d0b700616674bdcc94522ed43e6f6fcbe3d646ebfccedc7f1ee05fafdded1f468ef44d0f785f92c458c9d3b9d153999eb1c4bd9e2e4363744e6f2d3008d741bc1fc24bb2a9da552a984b2512fcfc602332bf91080c35a92524a8eed59c7762108317875751aa7943352774c0095c686dcdf5aad45a1ba9b8a71c1d8ee31566a66d879a1bcd00c1b2f34c3fa856f866dc8e4f24b33a6bb3333fb0b007e2199618cb131a79b2865949717163a33333373bc89d1ddbf58086f0ad98df0621d25a1890d61216ab55aadaf9ccd959c056e8c23b8f05d3e7725b701391d8b1e2fddc2b7f04bb7f02de816be856f11b12d9801d1218410cb328c0ba8955a1e77c50889a972468a65724619322239a393e29455867af413f9a8d5fa69fdb47e5ab0d5f1c2b8669bf9dab591289d72be67f772dfe2069286ed51ca44a36e9da239e77c7f03dcda9808babb95d3c0f518639de1caca3c721c0012a4c86fe458f89101174ec0518187e44cb869af1ff32ea200c56a426408d7cc0099556e4a9a1d8b5b15253dd5990b46cbfe67a056bab81b5e6dcf41dca294d64a6badb4565ab51da45bb337d9b818e5360d044a29a5d7557b547ad5cda1d7547b32f88af4fdec436b57928dc60505753e413e413e4130080605157563f20d709d6be1669cb38d7c6debf1d73f0bc1fdee3aa781bb6d3dfed2cf48901bbb1f0df7ea9a75c8f719b61cfa59965d6e55ed4833256d06bbda86183c6bf75dcfd7754de8a5272520d4784b4c2e8425a0f6e00dd904a2c6c7d8a1da73f778b9aac0e3049c2a1fe4d1710f82d5705b906993b491362ee6165a0b8315c3300cc3306cd29f35bbae2e87065427e31a0d6ef1db83d4f00eecb3873c5010e072079fa1b7693559077da4b0f0af97f2c49d0c05ecd67ef8cd4d2ae579ff35ca9d1c8475cc3aeac31cedb5e7e4ea79c6f5c587baeeee343d4ad7bf74bda3f1018ddc2524884f24de253ab1b36ec3d7dd7ba7c4351ac38f7998b79ffd052113df40fec44687ea6a7433325d4cf7d54afaaced3e8beab0ee09f427de4426c5bacd6c205842774ed448f00ed8391744b004189d3bdfb9e01d5d3c3a3a3a3a3aaa3fdfe7fb0ce6cfce8966a992ec86a2b58b3cac63fe115d2d6ace49d302fdedb76e862da3dc0c36d0277d1152472a429fd46d3f68f783dd0636a199444e1424c3aef9f546cbb0596f9ca7da7a7957a607d6898adce828ea384f0931bc0309fb3596c6e23c468cb0dcc77fe63b901b91a728decd4a73ce396badb50a15a0a00ebd60c2aff02d10664cc12e7c690514404e22fc0ab51f58a142baf09b6bb5f539865f218441455fdc8aaa42bc4bbbd9511ed979f7a16ec3af59d50a495364381e87f5e8b0b36e44c635193750030135500335506379ece3bb0f581571608fc75758e744b45e3972dccf9d6cb56ad63a11cf75a22d964ee78125d0ce89200272e01db3732e58471779a47c4e7461add6a250ffb1b1dc78d46ab55aad560bfaf8401fe8037d82c448e5cd55e99437d5a23ecaf480a8c8e828ea380fef70f2c4574658f13da8e52b0e8aef43efb3352f1e2cc1bb1e3e0c70fb3f9e82fd67c2652da68b930785c2d67e59af13f17cd5836dfa3d5f312b75bb593a5a066eda3105590c4fa45ee6e1ab46e1e2b07f203588af683afb16e66759f633d06f8eabdbc89f5cdcc24ad4953fb0cf9a51b012ebb227363ebbcd8ddb30053a0291714d06bfb1dbdd4004cc9f8ff10ed8eda8af657fc3256e88dbcc6eab55b33643a1b0ffeef36a1704d6c066720922e04fa19009b1dbc4eb5e5d5c62653fea077dde7ed49fad3b6fee8448d8efe6ce9b56f584512299b5eb03f1024040be822c1b03bc9f04f234acdc4f125d47b10e96acf96e51d5c6b84158ad45792cf18e7e7f3a46367d00dff45d96b8a627c5904349cfd1ad5c98dfb0e8e2c3d57e93d265ca3dfd73d02d7d46e24c5fab80d194b0f2e1fdf84fcf309db6f9806ffa6ceb717a53f791fef4bce59c7e522aecc741319fbd3f4e46e2e03d617e4ce56f25d94b2291be6e39a47aeabe6a69a9e34bfbd2ba14b4df52b097d451eea35f22d2f7f4d3d47d42aee9b43ddd2600b33d8e939edfc4bd40ea84c45c1a371ff0e99f389290181adaa1a0fdf67cb13ae79cdcd79f7d0cf742fff6bef9d07efb6cd366b0d8f92ff49ffeeb8779373de9e7c3705b8ffed3df52a9547adf724adc5f53467a7e6e8fa4d556919e495cbd6249ef75e3fa637cf46f343db637fdf6a6cf4c9cfd8c3798ee337261480fb79cd39bb8efd4a1c0aca3f4cc3a486f82e1ec675033d98ea647e9b52f915e237554eb30aec28f816fa4fd645cf3f94fca6d68a034a454c0c310ab89cde6118650440302bc001db1b819f0691712f10e8f757405aa46b908603ce1c38a1cbc459a5c5fbf080dfef4af4e8706ff9453a0592626d67db81b12f5c32268c4d3d390c5404e7cc541ad216f2cfd4dd4df4544aceeaeb119003f638d2f9203768258fee3d8518d31c6c830c02f177ac7e442246628c12e4bb112e5e26a0954e80ccd2cac6d2f6ba6a287e59d11d6ccc2c68e52b21a4e11850f47603c898227cb3dc0e5286e08ba475e7a2381763d3d51f0f073398a244c6e2e47b1c3960bb79c991a58be313563030b677aec7c1c98737661690ffb5aeb67240655213bc9a6ae18887550497b3f3336b07ca7252228cb5196cb528ea8700e2e4b39dab9657029a38bcb52cab0d23b5c9642c6949bf1963333c5f667a5191dd8ae31467b5d97fd138cbde216adb47fe24c70fa18f7d373cce9ad3d75a7770ea63b710cf327f9270ee64d8fd39576170edb7dfcb5e6ce6cbbae2be612b231c374bebdc3e77274aa496820ed415804d620b1f046efba3602d39814183dc6189bc866a600815062d725e5c54d5aebac9c3f9d2e6184de70c65e229bc9c18c90656c67c74b7bbd9384edffa097db3b124291711fa1c9b0318bbddae99ac0cf362d2ec02e01f31bd7547ac80ad7ad6e1e5f35d0c683f1384f5369a0d2639d771f0a4683a194cbe896436360300d3e8651ad72f0ce6eb10ef9df9b674e9d561ab78ea6625fb19fd1362ebec6c13c6f996b1784626db7629e72a7cf201679c59c5016626920d3cbcb54e10f9456d5eee1517bf2617b1242690fa7b9b48a2f974d834157befc121703bc5d94e8cb2d07deb0f105de1c0a61d4b69cdac9c7f16c4b01de21d98d6b95056be1d39bd2469b56ae2f047529b6e5d05a2bed62ac652133911e05cf223d7fb139d5eb1252add775d58edb667b120348684f9241047d78a4c955ff833e5c39a4e6684f5e5c6cf69f97ddb447bb1417092bbcf9e9cac510ef7743ff1e690dfec8b754a4a4ad56418f4c20fc696f46075b1ffaf0dd384844e5e08f13b65bddf2d58ddbc8ffaa454922a8fc236e957fdd5cdac11fd6217f1a5d6e43493613050c193ec4f884f58fd19f875a5b2d25d1a71ba5d4be0aada2cfa355f6e97b351f83a2bf4161fbed556bc5b427712620bdc95ad2b389f49a4622bde536fb24fba5d2e3b8e5365289c39eb4615acd2a76550a94718d667a98d293b89af6ae87e1bebdebabaf6ce7b9cdf557874161ebd38f8f32e1527d5e8c0ad7f55ecdb78aefd57f5d8fa355dc8c141b614e303027181818989aab7b818798cc9797d38f94fbbc1ba3e4d1e1b4e7272861fb2fed6e52ed45618bb4eab3914853cea3ff94f3680a35f12d0ca53bbbffa80d1fefa4a8f6fc8456d52bab17d6d15c95be917b8d53da3e3e1b236324026f7cb9bd5f625815a527f0681595f4fd81b48a86564c0556c78c2331c62864a3bb7bc79a7846c635992c921a733307bbaf76c3ac8b113e7139242594549b79c2f2bd7c7c7c7ca44fcbc7a75b41509ea4b2e81bec6050507429af0e06b517ffc98d953a8441ad6238bb5414ebffc116467fa855f4e5f5f1b1da753aedca4cb79c6ea160ec9ff6a212e8a30d616b7b7dcdae8b0f5bedc59fd1c1ca06c23aa251e90a193b527aec18a37429a594524629a594524af9d81336be8c4c8424e31e036634f66a677b143c4b7be634fad09b2ed1b86cdb9693fdb6e574ad30cbe096835d5b0ef61786f99693fdc51191a1c836c409deb0efedf2eb371e7cb18ec6dfbb1b39d81a0466cba91d07da8bd7895b9081e078c6f1f0d57fd4b8ad6b02ffbab2ebbaaeebeab48ae335c8fde80b41ddab29a53ee4bdb48e6fe067423feca2b5861bec505cecf743a21191567112fb710bf250e1070bade2f63abe688c4e58ff2e138236b302fbd9ab659aa6759a1536c29f8962b3f747c5bfde5e0f35ad8b32c6581fe34c80fda669d8f3867dad18f61a777518975dd8356ba471cae8b1092bcfa5d059d6e18f3561fb3905f910a23a45b03f7878d18ddf58ec0799e8b4398ea25aa91685759b8bcb6274196364960e35d8edee32bafcd16a920c6a57e4c8118506f6878c6b8468972347e47a754e48173da52ccb60fb33f739f412ccc020b2f203f290093739600d735d7df6ccdfa117414eb0597f4ea11b822e7bb92c74821e6e9d4b60788bc17ee9719ad212c7b796669cd8d2d7fa2514e413e95b2a1111822a7d2d55dbaafa5aaf22ab769f10d4b56ffa4cbb97762fec868d2f5b8e256d3e32d35f68dcfad7576e89bd84a06ee94d3de6b59f9134b8ad40b16ec9e949a78e0614ab49fde6cd47fdd267a692bca6528e083a60e496ea9bb8afca5bbf245bd52b98af5f6d35bd10d4bdb61c539749adc45d6f2d57b3a999b8128742fd1d9847c1b360ba26f56fd8277db66d39248dc4a4ee0b825a62e7b2100d8edcd4652125b2dccc35dbf598a6af1c89a3a90f6f7d188ea6761a0ba73121fb554d2b71f05a4b2271f56d0501bc58174be47ef68568975432d96863be98cba40e0d5b5b85fd7ccd6275c6dc89ba31adc2d1aad26b4734adadb59f025fdbd198fe05fbf56d57dff4d81bb91747e23e229764e2ece6244e8876b70ca7515917db631b5f6c7dfad715abd6252e7a28d281c9099010610b162c6a43152c18172ea094522a191635cccb422740620446e5d2cc1c601be6a05aad45a1264f9eb3e67277f7ec3927ecbc6377dc41d3e0e5f20edcb9ecee947a7bc9aa3cb1eda0066951965a5a854a86450dd6c65c97854eb0733ffbb1283ecbafbebac0f58d752c5c45faafae56e78ccbd136f1adaf30ef7acce53f722483d25d051c54c0706020adeabe40381064480c2a07ca06971e49346f7c69e4abfe288f6e7c1eee3691d2500a243e07e20759dd10f2c35ea00f74cad82cb8f153fc917065cf1ed9d48a923be78125f29770bd5dfbb576015ea07558dce9c4e716e01796ec746247d3022b76459a54805933d0c05db0e2c38f9d0e77c1da5a80cf5d5c7884f885edcfe0f3f3439a1ef3e3cf4f42c965a1248edc035c169201d1adad4ac2e8fe65a124866e1633dfa42627179f4a806fa412e03b2bacffed60d983003ee6f7578e6b4e5e587333082f4d8fbefd22e88091cb17a727305f981f2777b3a8fd177f72fc78fdacb992e39f1cf691a3d431ee9390fbe2779cf523379f72cd85ac6952d817fc06567295bbb8d1cb914512388bf70356306aa2e88c2c2624a3555faa8bfd78a8a80e71e1c717f64ba54eb88131c49b4a55ebf5d017ec608c1e184d9c1b1770866e62628c71c61967c402f610a04fa0ebdc85d7a545b58cbdad8e75cde2025f72104208218410c61863ec48bbd968c8518b52613fa83395b4eabb891fb9f619349b14848c6b5859c6db05aeaf5d11ec0b411016c4427dac2b92d5de588aedff206c36913e1234571fc7e10af1196ea9ab59a9d63c44577ee6cc4393087ef6f2c868165d39b1649f316476d212c8a5dc467610bec79a3ebe7221faf0d0a9ffcd27f30994a12b7f164d2ebde27ee6942756aeb413cb9544d7480561dc6f8271e563579d3e3f2d20257232b972ea38e11d3357be0cbe822fdf055f55e6c95856f4a37fd69a8d94708747c7e789ce94567d42a693fb0ea51f409f044d1fdec13bf20676b67c550570e5cf205f390b8b749ff088e4b101ea449d9d5e4d286d23ffd3ee077562379fb4278bec37e713b9823a5f952172ed4a26bf7ec6ac2c6b6d6642412c71afd73e9b4b2ae933a644e2a573077a6342ab5ed0fefa7e2d85ecb5a79cf67d339d949baf5dd86b9ce48ac8b75b059855832cb7d74fd302cbe767be14eb88c47bfdac34f4e3a53f2b4bdb6279e0547f9616a99c41165f3e5766fecac1c0af4161bfd6ae4d29a544e4676f7dc9c1c029afbda649ec9020ed696137544c7b192629334ba6dd37e4a79ccf0bb0d5575af6a7d3e9f4339f9db8e7505bece93fd44da1abe9f9349f16812ced65f095f67db29425a4bd1e52e59105e282d6f54ddf3798ac7e197c05f35de3dbb76ddb1ed52a1b28af675ee64f9ccc49e6749ae13e22f1cefc0d99e72d476646e66f7030f0ccf39633d3bd4c87b2717aec847a0c75c58be2687c5c7ffa53f782fdab05ff1add8f22f1536ed39e0ccc7fde4cd16d55ccb77c69b758195357db86c670a797dc672b0c677a533743c4714bfa98713aa2707cbf6f13a05fbe7cd972eb44712288b7762fd04e6a5196b091066d24b61178d92c2065c2a46da09452aa5345c95584900ece0863072a5ca4e0e2355431e2aa9244121dd7b9df4d0db146a113d8904568e78b0e294f14b1451843280103567c1ed15e16da09e3962e0bed106d8912254a971b325d64bad43869a971d272d2420425acadd2c9c2b5a53feca473bc71fd5326f48a01415a1a508270bc47028c41adfa647fb98e5dde7cb457b7fed2c7db07c5eeee32a8c6e78afaea5d4aa743c798686966de6280d78775c08f60d80fba9075400ff5b0bb99dda1945d111a58b18b5bacb0a2c649cb494b1a3c3764bac8749146fc7dfd637e6e8deb8fb35a114941a3e793403f4c18a101c5fd78ca75094bd7df3b8debd865d29e03b5e7ef443a010ab20452720edc7cc0ef238a80a8b8709b80774d2cec640e2cbf07f1c31c65f3b017780486d151174456b010516941096a5d5f82880b819a6b863d31461382ba93f3e9e9d1b91fec81476ef75489eeeeec5ee3ccb263b7d39e2d1b5e3fa1617784dd9c8d11638c1e63843eff6366f6f8c5bf8a504dc8c0f58f1ccca2e62a82775c792c7cf73803e85dab9c54461a83163282408b186324c09518b8ee49f9304a9f3fd0bd1b3017660edfa774395512aeea73fb47ca36fd0d848d1c0934b50268adcdc08db1a1070d6d4975aa8d90b841160b80cb4248281103091b482cb03197858e308210ec8dcb424758d952d35e772fc9d259f70782bcc4a0cb0ac40032623992228870bbbbaff0a00833ae34a28925de9f165303032e574c21444495228ab83006163ebc10ca1d8e0a2aaaa882bb3cb7991f8e8e73b666c56acda01872c14fab66188f1f73c70a10eb1306085cf859e420273f46dd31ce239183d00808a9586f8e23f658ddba332e487b3c4fea7850d7ab5536c461cec9908775cc20edcdf93cb0f4dbe341e204f2f08ee71d0e8b8042dc097fb8f3b35510d0932865180279e011f864d5001de843ab58e8082278422877cef9d483783a37ee7c195a457f671eb91f73c131e14e0fd9db8c34033c847e94b8f3b5560de0cef73ccfbbd33ab9e0490856b48782a92e324ce5ca18589e5821c60d5e58adc30bc280b1a15bc0c510fc42a80540ed49e003806601f7ab19828969082786f8c1ed21c8f01a5e21c4157d95512b934e09117c64cb6db8faf45024840d498e5a42f0d083194210a1a40b8f10412af8a3a3994d791c48016589bdaad85c60672e0bd5c093aa0107524054534a5b5896e880095bba2ca4848934942cb1c1c25c1652f2431221253c4a7458c08396acb6ca56f718a37bb7a3628c54898f1f63aceeeeeeee3e49342c4c567fdf25dd159981c5590ab2daaadaddcdb015abeb7064e3c68f47b4dc58631955c4806268092b8a86c8618c2b6a3eb058dcbefd5d6b8c110d237e8c10c517317290032be260c446809f245a3aac569d8298229da8351281c3130fa2c90d764b9145d918fb530a8182a82a62108312d241e20a2baed891c2fb8c73ba900120ae8021170ae2c8f5677719bfffe1ab761e1ebfe1c278c5f6fb7bbeb238aed879f91d765508f6fb1b3f035ebbb0df5fcfe28a2bf88a6df98d5db1f0d2b8df98a0aa71e5076915af562b00e34aef02e49028b81f09b700bc23fe8d1d96e993935f79070eeb902fffe2d5651bf9f2c7ec6edca6eed8cfbb52074629326f2dfbc2c2e7a85561e1bb73ec28032fa49472034cdd3b960c1d42e8f013c2ccccec1cdda38c57c02df6b4e703ecb9a0b4ceed0a81e095573b541296b90538aca30b0b816efcc0f8ac60cf8ed9e31cb9bb52030f9b63ece92ea594524a29e5e46070da52d21e169ebbd7037da04e709430ead423dca49ee111d90e04820938058b9438aeacb9523e2f71a5943e185df9c31357fe27b9f23da9822030abaa4ecc752fc0fd725cf7afd608d71fe5423dc8e0ba0f365c4f4193ebcc2a226841c5d36de8d5f6011a44a754ca6b0f7aa9c8831337461eb4dcf81b0a260d2b196b56b36245cdedcf5123090f49bcb8fddfc3c4edf77848b24397db9f6d391dcfc022a71dc90e4528c0a2e1230355350660871b6eb47fe32bd46b415390d1dd7dfde03f7570e3c7d8699c51060e4058f1c312203f0110b1bb6d1c001218dcd82587189d05a1d144ab56b3aa2c03f88101aa0de3f6a37670fb5f480723dc163ad2ba3d83db36061595ca6aad4656bb2c84c3119f1e6a108a30fb314801b5281b836a5dd5891d26173e75074148222a445688b250941748621078260e62e0e03ef22d0e6724e9f7bec6db22dacfa107a805910be520840f660001c8e789bc2c04c494594a23d52922be3871c5972ac8f882e5c9172e22b0821e20a61002420a00dc7859c827e87e9fd2224a952c51a240d8d31efce9037d7a4e44d18afdfec2b6e197af6961218c3db047cef9df759fa5dd9c5eae5cf9b1abac8367bc028b962f82a0e50b2d53da6bd88241eda5523445698a84ae71bb770cb5108f97dbddd1e10f9cd22a38c50afe637f60a05feeeceb67e4cacc1777d31e17f64b41e69fcc83fdd4843f5460b4be4a40a75847472dd6bb20347c52970d57aefc3f951d5c396163f07851ab1a7aabd8bd449d8c65f6eeeeee3ded451f6eec9df64c9c094c0f03a1e9d9f4b59a3ad343ce3b138426533c7263ebb8cc6a77d55eb6acb2ca2aa594dfd815f6ba3cb31bc0ebeec48b7807c73bfa4614cadacd09cfcbf800865e50042404831a5c167ac1d18def4c20701d70a1b81b755e3485f59d96af7e003da1c3323db362cb6d224d0ba6877998ae88919f212f3a2c53075b9994a931332453a3868c17e0e705539c15bfc68ccc10162227565ae53b597c8508af7652ffb5a1e0b73b29ece745b7c80b0f173f6a95ccc7af9cef749e85732cedd1d4e8450e46bc71e3479ed81395c48f44f48a002ce4022e37362b7e75273b4f5aa5f29d1bdfa1b4ca4db04204e21d5e656add9d5b696f088cf6945837eabca8bda11b9f870dbae18b1eb2743b8faf623ec29c7c27a63be140c1c4d81a496fb92f66e3be88e4c69a0b9f7adb43a2de69157cf873996c60ff3d6cf9468b7e7ce54388620315e3113a847371a3f69cdc188152375ebdfb79512c2a7230629ff4a8e48d9754350b118d0000001000a314002028100a0704c270482c22d4a55dee14000d7e98427c54188cd324c9619842c8204200008010202020224034da06013ab6791649bdde8bc726ff07a33daf2d5f27f555b180c75706989bc85753fed3970b4b101563ee722ba8c5584b0b1af3be4ef429d3a785b73d046d8e419990e8997b1896290e4abcaa40b6f3425f370d9106d843f6291e1f33f95c353f1f1eac4b2622826c0492726f0d69539a728d80c86e629f440251aac0161d2fde3f20ee99104ef9bf1e219f077f22d1e48c8b3d816872e7543c5a7686d9e8f6008d3d402b1a9f0496934bdc9deef362d74d51985fae1e6ffc07936c703c8d58fe7b39e69298c7b32fb1ed13c3861c1060014cc722938d6c582a67799186b9be9e75ca031ada3a7da012492df292d1245ce35d0d45d7494ac2c294af6c3fdaaf452f011ff09f7b37053e1c8a075dc4cb802f7930c1a3bdc957e2a4ad2e228bb0b9d2e6eccdd6e76d1c14bf73bc7ad3092c4ccec19da2d5ca9f549b575b7b7174e4551d7dc30952da720a9d726a29472ab5fda0f48ce0be043c449d6e03158f278e74b63dd0ae1638972b10962be00450b17fa7d64eabe5a3d582f210594baa63951363f6a6b63d820d228196b7be0d0723a80e869ba6ff11022c41a56f50266bfe0bcbb195962d89b13ab6f9b7209a0ade4b51630b4af52f6a5569b11f0ea7e02f0f9ad24c0a4ab2c54dba51214e26f013807fd45748860e2eee5ec21542c3f1f55e5d2d67b0b343cfeeaf334515bf149455512a93c7b172300f3c3bd7725962778beae059b3e24db837e8123802aedb1371c45602abcefea6813de0fe0dd4dd74467822e519b9f79c515246edad8aa49ca1a4875daac6b53c8d15d3e282cd4233d74ab386b2987fa0ff4fa466fbc944681f0583938333b4900b1bb389b251b02867335b050f7f827d0592a73855b5bbec882fce3ff57ab822a344ce55a02ad72fb3f9ba67c3dfc944e25dfd67198f7eb49ae12d728107b425fe1e05dd93f55b29eb36a3c777ab23eaeda0e94c107d525d320868cc591ba7501779feacbba9bc1f810f17dde8387151ed9e3e221b536c9da11f99bd05d2577e03314fe9ed806fb9de8b8fe3244377d1943dc35526579c898a389b287eb65cf998a3862018c3c03871e2639547f514e7f0beb0919aa95e3a9ebadeded9c755809db32b7a4d12566adf8bf6b07fa3db07804d8133d8948409e8a86952b36ae61389f3620f41eec30fa105329b061840b6a5977ae8d785ba3bbd2c7ec7d4187c561af54aac02f1d56362ed113a03db7e3dd499058656f461f895735a705e8e09e76dccc78741255653fbe8adcedce281f70f50914b3f7b5f3b9a2053ad6be7266d7f8cfb10d2a4895983797bae1148ffbc199b5f9f680462b4a8136d8a5d406e7a7794092f75498b288ea223a271fe770b069a64c2a7bb9e8e85c69dced6cde3671ad07b0fdc5ff467425ba60691a9afdb4136984d3c985877c2da491a1e489109aaf84da9835b6037ac217947cf2228ebba954a33bd1102caecd07fac8ed8207e6f7dfa1af54263656ad10377880951b09e68291ad85892f86fafef063e3ccace2d6481ab1b745650b75a81a745e927ac2895006a8cf47bc602bfb0b3e3cc30c17a757db15e9f42e4fe94283c0900ca2e9acd5500e12b49a431fdcda3802908bb43269066b9ecbfeca6a70673d3a79e2138803799c2c33c6d71d79fcaa2cf2d6533c0f5ba1605279d14863506138eb8a2c5265e3d0cdeafc9a5ebf8bc82895e98f58316977f2646cb01d2a60ab9ac2637332951e0d75b9c42cfba9d73ecac829db5243481c31556540dacff1d090c138dfff58100b1ce0d04c6d2d4bcb61872db652151d52e7e6d77625828c1ddd1ace6ad61c6dabc77e06af56ad0c1c5f162d73126da77caf9c509af6cad45d0f305bd7bb432b876a0447b8c6787a0f02a27b8aa8398258c4c2e7822e043fa3bc81eb40a23f789039023ecda5984ca5a096468dda0c8f212c2638c63f1816c9ba8cda483cfe948a40869b902ec09b582e057167ee8df93bc0ce6fa7c3832806257c0716410655761a37b42d00dec68c4316bf507450aa32d1e00887571179ca381b20e6f077a6bc965b89c79467cc5c59d9ec75df1b39712080b41a3fb1c4ea1670ae3980df77656f8448a3c10c5c925fa5a5c7bacc48630a8f5b030f9a032022014279b00e679171015ce3ebb9d9793530ac39806cb9a53d0709f372056b9710117ae74d16a97f1256239c1f8986769562aaec48ebe4123c7fa03a5fd83009bd28c327384d507b6c0cda71dd8e308a3b7d007a8e083c4fcfe92383e5ad681118f6c69789fc29ab0b62efba636c7be9e30e38b57e85674775b25c46bc4da81a66ddc58f31675cba9f45c13e0b845e27cc1f370ac019b492a9fb49adc522cfaadf0a347f50f0bcfa6ee6ddd18277a1642475af6fb211bcca33158cf92023cd21e341a5137f4ab0c3273208c067d1953847568ebd74fae815c71e73bc7f491438d5577383b052e3ef78fb6df860b54038b65c88fda2b0e3df8875702045227d09bac1bfb28cecd6ce0bee1cb4d091ccffe36325c72c7ee2bef4882752358e940a47476387df813ded8749a0d02ad5ffd56b5faeb79fe1f94225d29746029a69742c152562e1712a8947c71dee4285fbdc1f1ad972c38b95c3aa3509e743ea5fd7fc0a0599f80ee77a06270be37c575b5faf8bd33a548e616952a70711ea633c518029d6d2dcff4d97b5dc2066b2769c90b29fe37feca9b16fd0c2f71f7a9232f880869bfa2022202897f5b117e477c6b4f6e6940a975ba6c205a55f2eb9b576042d66444adcef63478633481e9d5aa86585f4d2455c1ba06f813c7895d115d1df218011d9f2f8b859efebd0e77510f773584a45d7e4985778e035da8acadb5dd90380b551bfd81ec0a61f2d07d253cd9d0419067948aa0949727df434d657ae13a056da6fe398c011697b84a1aa922d3fa9878cb2c92321d04dccba9108311ee4a2755c5b4a45fc55ca627c287216428dd269df6188c0f2d7b8ce23c0200b37b22b997179ac3c757b96812f936ec84e05bc442c33808da84673debf33ed1b8a9c0e81260717763bc66d404806e9883b7d91df3b2f2fd07aae8525d566a1bd1c682a62e44e67248ca83336c446118013378ced6abfda6bf10b5d4f10794a75ab8b2d25243dff7c26a90e4b286944e61b1610d551e8d9dddf91eee9f2830baddf49e56b9ca37c67a93c402840fc52251c8c5c2f2386a075b6e644bbfafc1db393a59c9495ef8a66d963e6e84ec8294904c109762e1b374cbd354cd169280b16ced44565f05d5ddf42628506163da6c3002ff4f26bb45ad27227c825c997ad7f781956ba27c67c7359b92fb8e7cecb0db5663c2349eaf66b3a95114b0616509ecd9a66452e7c4bdb5defaa9ffd801230b3e205a57ef7b8cf888b6609e89963b38bd08ef4665b498f2efc91ec995f874597840184cda3e6db7caacfe88c52109a3c521332c6d021a0a2a3cc453e1114002d9541b1086b7905cdede7c7ad710af81e171dcc79d630095b3df055da546b95a06bd2fb5014424ccebd537717f2af723c38db06df884951117d01ee09e9285a7376cda24a1830ea92c26254da908b70146f712ec31342e08539a1b08fbbf8791df074a7efea292968652f4c7f7cec0c6a35a1fdb6a7bf50bf051498df3ed3d70b581751f07c0d9441077c0cf3850ef046d4b6941618d6054d3deedbad2fc4da5e020cd0017b6a10eaeb12145a5045f5aa4fd9fe0be3fcaf3d64a814b2bce9101f3a9c671ed1fded4729c6dea56a95c01057ab74550c8cc6c1adc9bc8f096f6c35320b098141f9f09edea33dd1992419e85c02f0a39d0bcba2b130bfe71b8d0d4e0c5c4936622bf7629ca1694ee483f94235bef543a20357833c7420ebb4a2b3c00678fddce9cd8012e2788c8018a1e6706272329173554a8a3982397b8111c98f3a8f03dfbb6cb9de9687831768ab3030370aad13a45253efa38f846215173e7ab39fd4f7a803b272511ea4806acbcb209320f117aa06ae36fc255d6ce7219981765186473e2a19b97643a10c84e0b0077e5d838d82089d6caa59f88dab7cb26b82e4ac7c6bb022c99059f9ce5dff41b9df8a5904ede4cd2ac89605cf350e9ac51fb53935aba9225c045a14c319d2c2210605a3e503618802ceddff33417e0769a09848dccf92007da94a85125be1fb1d09b4130f129ad6ccbb681e0baa3e287eeae6d292337b3bd42d29844c2967daa4d49a3a9ac544e84927cc9d2611c0a4da00d7ac7397b10bc2488ff30ce35324b5b8980f42206a44bd10fff5d21dad828f78048afb59d5b93ca8e4ae2613608b8d651d662c6a5a60b5564bf8e0650df4a3b1e2d77c9f6d9a6163afc729bb19de981c0f76e1e69199cd78aee47e55413c0c049d05d73aa50a85b748fad7607523eed95b25e67abc00e1efae080d13298a17cffe7952691996c9f4af7d3e0b4c68c4bb40e9aa6a904e6b5873b4d8f7108a620832f4fef14c80e87c9184b472a3ad4b680bb3575570f8d03b3360d2a9c2bc7ebf27f008e22f8ccfbce786c30bbaa2b5eeeafe153366d37188103bb513097287a97c8c75897010cb48d95e490d436c87af890f3d93e46f13f6b8a00d0a08f026d8bc2088deb27cf0cabe4c042284c32aeb58800dd51a8e386c5d202305c4c59c9b80c05a26f927139f677afd1e5f7f20cc88c3372124a5417e372d95e7329dac7f8549ad05abc7dd033da42674b97c67e0c9fcc1550cbe420843d4feb2dca1e74068d1b9d00c487549f8fa5133e2eb32217035dff815bd904b07e70c9fc8b15e38ed452a927a94ce05450dca799112bf819f566727605d167c794139e4bcaf6f35a6a564fdd481d067cb4c481b5d1a28732062097bd751e0003fed8b6162bcc5d12231d0aa88cb096d5e8af52784ea692ad8114ca97bd30ba9675ccdec2afcd136ed684233ada28d63d233fe9cd658dc502d97d4ae5255529ec253a2e9e13eaaffabb21cf174bf57eac1158a7765ba94bd762e93bd8b513afe9dafd16a40aac94abdcab9a7bd80c9b91ac4c6097d4dcbabbd2b67a93b85bd19b69625f74d8ffe44ad1a7c33afc58eadbcd9bb13ed27c0e69559a0a4520f2ca09bdfc4449dc2b447162c9c06bbd3fb5ce00cfb8adae365d77c5d1f0457ebf7f4409ba1595311ef18415570f00ff6af7c190db85f68da0bace1951c2ed3c831529e6cedbea969da2a61d1fd6ed391d7f89afb2bef1a5a266aa0de32739e07cd6f83d0b575016c857c9d18686245929373ec44a570097cd47c858fa2ce1da8ab876578f032d1ffc715f8d64e32a4bed5bd87553d23d832e705cfc5e3065eece938a15252d39831ddde80e19a47b899d6979fba14bec1ae39870773ed2da65b5750467bea248fddc405b64275811d9be39d112c862025d8da5453db4944c806bd426e23cbc22786b92ac8fbf8233f9e869067a5df02d1a2c33e0787d5659d132911dd1ff0064d72aa68d5f01aeed8272de85052a12535b854bbba8ac076a67e633c72b82636ae09389d4d28447fc0ee8968781e91bea30ea531d0e68fd6ddd65672a58e61d2afcc35077c6fb3472faf9224c890119ed31cf54bc0e1dd62244b66c79c4bbba0b37037533d2b48a1fd1002de1b6421d02ad7ad4bd34a897e9e12475bb8a68fc929e95bf42335e32e51df8286a610101d4f69278fe22b50506aefb11ebb675c933c14d83d8e6e040fa96398b56113e3d6175f329f1191f97dce2e8edc08bb5a174675eb06af4e5b2c04693259c4fc2f9ee9bf22200287728a71e41ffeafbba2f64bcd8de028bf510e6fe0a300904c3b4e89ac4c783c5a052f64586e6c34c6fc8dc608b1c797ef067ad46736a3faba12aafc0b82b7a932e51d3b49fb290e4d8ef3e074675f4372db0a4840c3ea09cf6b3ddb52c7c60206eca25bcccc8c344022eb157b6f612a4e9c30256b220a7902323641441b2c0a0c8ddefe8adee0c3013fd908ba0b80bbb694c05c212a23100e4408151979af39a878c6dbcb56ca66a4ab18e84f062acf2dba65eaafef117a7e90fe2c700ad90e75cbd21a8859929e06721cc14dfb532909bf12927b926cf5921c696e9797a1f12af24bce9e94d1d4b3d241095384d6d29043c7dd5b01c642ff588d541ad139081f2504a6ed474e23fe31b05e0a4de9865a92de16d7d34d23b623a2bf21f55b52c8b6c4531057ea4daccdcd04dfd3c4dcc26da6e8f03408b8c457494465b9f13400323b3a2b3637919ce9cefa5bb5ad683fed9ca565bc1b3f098c3446354ccba054b4967c922ed28a282c70e3cf0e51fa14808796022c0b691fc821efc350763ceeb3a018b3dae5cc51e93eaaf1664e3e0695f171f7ad4fd383fa73e6803eb8e65008b4c7aea11d00bab10f96758d9e4a42050c0483d54c19fd51241fa7b8e7722ee58708d8385f2497a9a9381b6d82d4928e8ecbf5cda08bbb846c5bc2c091a82a772fd91b44368d0f38ada0df7091a0a251bb5816b0ff0531537e9a83835f1a92575f3646a1e801114de589ff31468de968f42f17d4edb3e7cca422589c386892fc0064252a0ede23e240f8d1f6fc1233dc2ddcf075289fe61badee4f6d29b6f8fb4596c8646e3c0d89c4161d888516c7a66aae2e5852cc8a25836e2e13d7f0df2383c95ab8e51d9adcd2c951a8fa0dae1e592da472924c7d454f46f6776701b8dddbcd4b9ba4319190276e8ecfd27820e10dcd50ff508cebe89ea80793bc625a4a9d3fac1c686d6f0d4f9573bd8fc56843408432ebe5ceba3ba0f4b7404040a7aa288d55e5a3a057235129c04ddbcd0e5b6e0a7fffd98aafdd290c9973e74996522736e043e0607709be0b4c806036070dc2ac9c87ee56f631d09f76fabe979edfa66fa9c69f41fd40ed06cb2771115af9ed4424ce7c423c2f2f593dddaba6f8dc24abccaa6bd9afb3ba18d6ed9b44eb0c833a39d653c023492fbdb8001adb25405c75cb4b17eab0242722c297a3bf33abfb9dfb025b2bc0d2162be1d30859759500a3589c1b4847da1e50e91e8360c457d893549e120d6e42182db3e96b9107fcdcafbebf8073b5d6a276616836ea3d0e69f06eddbd511a155ccf1fda2f64cfc680d05bb231cad91e054690806ae417c00cde35fa0a99811b3f1ebdfdf5f87a98caa21e84416d0947f250a72482719276106c967d7e51958dc9e4e22bc100dfea214ce8ed13b1b7e225b6ffabf7798fdafe3e79af53ccfa1ab38024b332926b47e5e26b178d1263ab26829a0f1116c25538be48996bf35d290483386e40ff56965e73ac50583f077fee3b56e366d3e65ef8e696e015e651ec9682edd97f6d05dc361b6cd5114ef230e00f52952ff55abf60e6286023fdcbaacb7358dd93078fd0ba42af6da5c57dbe48b4f585fed7f0fcda34a496e02572581843cafaed265b578dccb48531ee8d99feb0f228f6e68fd61d3e3d3041e7122d73eec1a7a4f87f9e1c6001fdcf56f9f6bd79b19a20b54029ef2d6b73204f08910aa9e6b4e0768a989481180738df24e368b30f8ac15cf42c183b1a3a453a0e466333400fbc5135cad5d68d02c40b59ea1579d6b072c330524fdce5095a4364f1f64ad0fb8add2b29ea93b80381de27503fa8f701a42f0bf06915175a8680df4d33e35c234e78c0dd37e74f5ae72524490f697ff5df4d69ef3594613065d37cb50f11baaeaf36709a7d023983b5881daf4638f6efe2cef283e40ea9cdef35c50a94212c680e8d9deb36b3d87135dac5678439a92dd222288b33771486467e8dbc7364221dafd939978fe9ab720daac1c152563a43d71e76970e718dbb26e4e24170e9547824d7a6c491da30816d1cf3a63f106d083ae2db1e65db8326b4b78101c0ca616695c7cf37e6ac7c25719ceb605f6a57ac6c733d7171bf1ed5e1fc417e320241439c3341e43faf58342201b5885c6ae49425b4ac4c7afcee4aa0e8b56a54b2da91d9e25c9359ef61010c754ff0b9368298c46525c887f24ffa8ca7e2c8665fb600e6840caf3d019adfab82f66612c6b94ffcdb509a4deb4753254f244e4ffcba10b0a804fa14de31fe4f788ee1c9bd293a786cd2adcef9ac88aa59636a6c2abecdded71909d388aa5d54febdeb835a1436016f16ce2dc9d818cb9610266b65984dce35ce45c113bdb2072b9e2be75344ee52304927102c4bb860168c014c11d4e68e63b2a7edd7947a28e2ecc843ccf40e9214a42501690114b0a494c77190b5f20620bbdd08faf48a0773f4f051352eb645c5ebb09480b7dd3b2040970b7e4f62ea6ea8da1efb6fafe437486802a4023c35ad1c01527e528b9519878bc0f03f2088d7a7e98ec85d1ba10a2c38a92e0e4bc02d34ae5fd8469cbdb299c1f5ef8c48ae14ede6792a45079010eb5164cb9e5c22cd742339b2cdca8d50f8209cd7b2b57f6d6aea31844084da50cda92d0fbbfd5248d78d2bf366d85ec238e4307b8e901d210934612e90135cb59f0b109996823519051e0b87d99f46aa421c8ba2900ef8bf65141d99eec9c70db869e244a022b8890d0c404506de82ba778b9a811bbf4954ee6d41a96800b15182be3080e3914fa85e62b56d7e67faf0ed56fa44a6aefcaf41a5cf11e3716e74491857f8e2b06b8601b5713570c618566e7f8b4e390597962cca4e224c92ff412b71c129630573005b99d1139c5f4c652905b5b21c19ba1b7cbfca4077152195abdc116f66f20dfeb50ee6022932ff361166d6ed4a0efb46cac887b3283baf85a08294747119dabe98e5fcd4047da1be01b8283af2da57952aec2082c1cac7380fcebd3f1625417498b6bb3258e7f6c1d95bb8c7a9c130d69506aee2f5d4577ff2476e38c4b27700052ddfda4d761a767ec3fec366a6e6b5ee2ba3be78b682741d3cde6a7125ffb435221da98a7985e00733858445acc90e7b2a97be3713af75f6e1f5efedee8fcbb248813a37f86253e49245bfb6aa3c4fe495d58fe9b69b9f743339572d25a1007ee820442f20d4a7aa01c608bbaca321b9b202120a910af7e87406bc3705b4a0190b6028fb52c5c8cf8bb76b5a5ac00411ed1603fda16321f6bab11440cd291d3d542a86eea4e9af49301b736f0e654d465d7a3b425742dfcd0b3fd2fc67689ef0569a66f2e7756b8b32e3ae82d590008833e007aae178b93b3a68efbc96f95238d276bed03521625b628180ac8862beca86d99b336e4ceca48697fb2a08eb421365a80958f676dcc013ded75aa113d4e1e9a7be7204bc591c5a705db36cd9954d12ce6dbaa90203d5da1cbab0f4c57eb1efcb8a755d088646378720eb6e6f506bfa65169556d18bc4afba6a706f2abef8f3997ab5a6ea96b44cfd0400ffc6b3703c4d067f08a7e240f9d011244da30848a03ba7a21dd917805b95645af0d48011ce73e3e8497b685a6da1f9a9dd796dae3e4224fa30053adb81111e17ee13003470e0fcf38061f9457179fc537bf4d0936e48c1154fcc05d7395923dd94308950ca2a850095f8941b66283ba9aafdf0a2992d1dbc5ffadbfb5e4c1251173927b850c8984dbc802ce5e107fa4a01ffa4e7a8dea900380b6267ef1ce0f874d9ceb6b2036dbb52d6dea651ada54ca8a4be570e513e5bcd6f5abf149e9c8a02d81bcd7f26cf0e815eab230fe96b1524ff94db8617d639e0deecbdf1c5e7d1668e5d0ced3172d1f61e0ecda901699ee04b154ee2a37347fe071bcf075f7c8f73a9594376c960aab28988be2da4f6687021472ad29bad8f9a36eb5effdc55117706ff1af67b1cedf8bfb52094634a1b237c81e8ffaa9c1a8411e634c564f91c7e2c754467538ae87aae17a7bb40d6ef9e30ae6103a1f31783f8b8c6f275d4ba3ffccf7a5ddb090f593032b61aec37ce05bf009a752ecc4dd233c27929fead2c38698c21819ab248e9f9acf33e38863532bfc0008d5d19e343a6aff808eda83de88e3029a91d2e1b8ba0c73fc126da1dc3b9d4ca32713a9abd976b650fc3bb84ba35b0003eb193c5bc691e8817f250bec9eb6aebbd5d8efae06035f7f5e3940010023b34abf71430fe147e788bdc2ba3d128bca473a813e4485eb4a3ace3681ce88a9991e89e57ca1e6009a977024f3f9ee9ba0e24735a5d6fd7ddcf5a09e62fd7e57d34bce33f0efb41b04682d2cb81c91bc1ba41d4d9d0da0c41cc966f5b3ab1fa9ea086f328488ba3700d4b2419727a6a389f3fb3f809f31a76758d0d6c3a7bef47234414820db43b63a1e0fedee2c8a38fef348574527d6f7a7b061f3f205ddf5753324518a11284f8ecd50ca99bd811070538ab5a5ff7f5764d81d209f78b3246a0df39aa74b39ed727fea0f6f4c753e950a73863b65793a6c6c35596752bf6def3484c51728293ed947741852470c95280c456ea27001ae74e0c568d047e4d4c72e1226b68940b36836b6cfeb6a7e58bb3594bbb59dc45bc31e8ce7ad11999cebebd57d90febf6d04bdad52dc1e03406b3b347512e94c30033c2fe7b4c26a13b5490bf0fcb057b2da6a80fabebc0ddc04ab1bdd9a9277ac0942d6a68c6371ba22f2a2e5acdca1e074f42a440268cea8c562ef0248672e7109c3661db5c52fa97d3176b4d9639ee2a8a37d245b8b4bb5481b7c4419f17e05c0e5476bf8ff30389ff5eb144c3666621023ef24120a1733243bedaf6dc77cf66123eade2b7cbc02183a59708aa83e5523f6cb4a02508164f9eb507286d2159a97c1b28545eedfa7e2e7d45ad3a2da7cd4c3a6585ac96c5f99c26ed9319173c855e9c59ea680a13d578efc2a30b04978f7f50008c60d92d810790e4aed8fb4e0c4ccfebc2e7fe03a860b9795c694a2450837169ace5dc8845ea6253a8ef0d02846348102e2fa187044d768de9a3df74adf7e2d38030af794b892a9ffee00596ad09edf3f45fb7e699eb91d35a46d5e1cdd0151ffa60e988122f75c326a963e041f4b9a19f7c07126b743975974c1b96c92106325de129b14848d71efea425f3776d70099e06bee8b49ea50dbf263dcd5215f67698bb9fa69da70963b7d42a352126f04254db9490299a8572008896608dbbd5833fb233879dfac8e856cd60ca158da5b90ff60cb1886e73626561e73d60cf77c7ce6e8b9929f0bb3eb89965026c4001de32ada8ecd1c31d952bb9482037a9b7643f28de1d1cbf574f7c92f550c167bce818c25a64658e5b598c1b11657fa22fcca90f50f732fe537e55655aecb9d95785b4f4097b52e3d8c8544e643c75611559e500327fa7d86518c093054847a6f23b2784ec13de143f4f97b274e708bd306432281702a3fee283fbc455f7781a97e958a89b67f209538b00a5faca7468112e2505ebd5f3074878d3eeed71b861d7dd67e8c121ca8564aa047293bfa648c6e49935677786ccb3bd95a56f920448ff79bd0cb7cc3b3f55e69b6b7abb3ef134db49738257120444a5f9520e4529a94514f0cb37299f949046f24db8f76cc7521226ef309769fe5381b2b45847124814c98132b47889513a851be48da4226148c839b3d2f96fe48e90843458df75de794cf2ee0601ba417e32cfab23b124004a942d1e439c207777eea81587bd279bf45c89b2e08a5a252aa6142a96544d338eccb0b443f37b5b9be0f38d80459c29335c66423af70efdd6dd5a0a39a64a51f474d879173a97b312e26e4900802bafec8ace3d01eab73aa065801b211e21f0d37de258f8493f1517ae9113c08acb3c504e46af1368377b50d5012ddd4c292b7e522743acab80f3ff1a4eb9bb8a396bae4a9dc3ca1751895fe3ea9ab610c16d0d36475f16bba1aac9cd09edab6359f541ba0710cc6a2ea4d6b51e46a9daed95819fd20567e0360dcf4d9f6e287dd755c4cb0f4eae7906eaad3272b88cfe965a17a92ddf08b09f8bf6ab8bb70ecbfe5a3e2a451888039237367911ac4616f1bc591d803957b6907fb56c59e7674eef22bbcadd018466b49a0969a648622bbf9a3622b7d0cfec9d4ccb9093f61de42770359f788deb94f7d905cf9f5e59167eb6e0b36e25c278dc85a79cb0fcd9d75e0cb90d0fd0e2047b67bbdcae0191f014c3ef95c5e91624a64031554d3db8158ca90a9b9a0ac17b5b544fe55e05be92b891e7db1644646c8de6c95c68d971f51a9f282d00fe192efdbd752a93b06f5c7569c62ce33291ffcedfc7c36c62d92e1f53dcbe48d99113ad7a6408743c0ad0234457714ee9c313d53a8f3c6f069039f158083f82ab42ae467c30d98927ff26e633ee515b62bef16c32e723113118ef0a8eeda5410680a1dc72f8255c9a0e2723bb42fba82b50622888a3345a7dae2ad2e69008b7cd059eb1c3465ec4cd11f8899c87a460ac3af82872a35ec9e5ff72812f65947decbdfe5420a9dd712de46411463fcbcc1fda5d8cc389a44df1802ba73f07bb54c48083ff5a6f056d2494e703ddd64f8e854b527c42066f62ba05deae21ee9602665e9c3bfe6eb4ac526339ee7e6a797decd3d3ac0877a9a88ced614758f3e46f20c34286a313a9bb06500f261bcc66d87cca11000b41068b8cc68f4827a25abb9abfb5f1f50dacdd4d2f0bacfc06571db7ade81488aa3e961827587525f279eec90aeb88dab42b348a6d5c222a16f7d480a16e15a61c6f4b9c24df138af618b28e0c01f197024b2e0b2b8fc04d0d61006f3872640b6e8364c987cd230014a719868a6c842ded8800754b4c35f85749453b47625ee5cb465b2cb61b320ba486e2f475a7b96b71fe4da18b46412ba292a2c705228d10e84b2b83980f1314d97320b22145b44f6e4459714153a2c98b7a4c6f392ef0b95b2a796d41ddbf038cfcbe354ae01061475dcaf128a651fa1d992909e820293a18a58b7696011055b4c6a696437511dc11c46d8e9a5650409e5c8c820bb34c6ff50341940eb8ba2c9e1753316aa2bed6c04c6e90017a3ea4799e85446cd48e120857c47b9945c0e3f560fcc3ed984080a4b01fb6ba1b0e58e898a0d8a3c053b2a5aaf9fa2dcca7da8a5b1bb8b6e4680aa2a97e26248ffeda3f6efd25560ea70a15b58b75db07ea596ad5b7589f3000431d4a17c141a98aaedadd00da1570eab2a74473206bc291f30544cb4957832ac69fc114b2ba14a55789ed0a0f52f0cc9f8609b8f5183878344e0182528cbd58e968bc8b925a59a61b8e06ab4cd5dc2ce6e50dd31c4db0dcd29966ef545a2b4d5462b25008e6c88eac0a2d1040408bab78a4146dbadfc28df9dd71cf2819fb24878347bfca9a34c8f9b4d1ab1e205a698a50d1c987482dcc67813c8bd728e435c968313363e490648def1b5727517ce30ff6d7308d39e37ae93b7e61127538678e82919884082e2825048725b0aa98b03bf75a42fffba16bdf80da6c94c0b0a408cca3400e599fb4b6ca6bb46ccc3d7b3cd0175fee20a1b98a808618d0df504583ec655ee7420a9c0893d20bacbd8d30ae829ebe8b321099469dd872686bd3b965ff8ed9ce411c41d0780171bbaf4f5e87a880688cf0f1e221c967b01707e45cf3b3850e27fe8b5aadc210d400dfd63435709488166112f0057a45a40156a94485a3c55332cb4dd9c74860e59e6aea0f03a412f0fb84c40cae1cb0f99860061d22b4708bbbe85c705cc7fe68208043625e157e95f79f76eb9181b2b3a983f4cf5739429284220205470f8f341373c298fd9b1f6fc24089b57cfaa6ef589f24c8baeac475c8ea51e09fe7c5e84093e0ac5ce7adc9cb37ddf92c4682f77bf5f41f8013ba63f901b2d5a3a8156c0dfaffb366add40569b590e85d29bc2f449bb7d94ac2c786c2b35f2a759bcc586ec8973e77389fd8a358fdc4adc8e9fa67cd172fc01e5509eb94b40e218b569deaa0f78871230bd20856a391c1044b79ca2b227bdfaa2dbf5f18bf93ef7304f49ba552b9588d962c7d0855dd3f3dfc36c2e2b550a60dc94c156e848f4c06c5f1e590072b73cdb27b8c6e838acf153a5425133c4de47c6398a5c5702695a251b994a7eb2d81df018e941970909b5a5f4854d153b2587fdbffb25209a6c1aa9ea225a39acc23d679f83bd03ea5dfbc6edcc7bfe924dc90cba30c038ab6de0ef2d325a0823148e3c15492730835e1830ee518d3b26d2947dbd405a03fd21423c2c1204244808a00615a1b6860af80f7a573b1259a313240c4fe460177422f1b61eb6e4b25175af62ca444f94888b3ee21f0f6b417e4e46e64a44dffda1c7cc090158c21ff1ace1428a3082397f6b200d6f63518fee5fbfc52527b0d9ee3324acb976d3756c68fc246c0e4aedc61b6133a7ef105fe1b96539b0074c809867acdc6524d26d6b556b4e6d027d1f373772b5031785fd24e82fa5cf0cf278ef964bc1445bb0f327fd8d017d39ad5e59793845955c28b6aa81da58a86008bed86eb3b4e4135aec04b404e7d29a2178ea162217ea703a039770c78b947ebe2d55951e4c3b7be46d18bd3ad349f33a43fc6d0da63992151599d3dd141a6bd8f79db528cbc0db0551a84a782b6c301cdd7b54730e06339611b837a2d6a7ab8b8f7fd139ef9ace625ee2fb6bfd712e58389b6e280a296307d5d89e2a9a4dbc314e052798e5617d52bff509dc1deab4944665434cb097a887d817109bc3b32e2bce2777421eb3b53d19356734387968eb19e9f76b032805cc47e3bc7290f7679d5714de63bd83e1468ff63fb500ad1b466e0d807678d502b42e8cecd200d41fbfd457f3d4cc80e88a92c20fdffc45b3c2fc2a4472a5d39acb9215f24653b05f0bfd06e1edfdf764828326ca3663376a4670e9a133185c6d8c85518c9b1fc6345ac0f391991a5d032cc1471dcc143e71ad106353bba13364ac86813927d1913e042e29946f267ad93efcf973352bb5b7e0eaa2f0b0d21e8d2891e231016296d5bb5ae00a1847bf96ed416d3221245a0034d6ffc162bc1439cce2a876fdda03a46bfa5aeac799e252248c599080f60f5c56cb1a5d0a7d7437b9713805a095ec7e9bc3fbd78a5beb20510d515449946d73d6c55a0bea506b387f5cbce62cb3a79188e34b3cca2a37b5368f9b5306fd21c6466897e29cc41ba42a41173edc33aa95e55f8fe8fe1b4f2a9a17655c2ea1421f60348e418c67a10f4f2e27b88481c5dc4c6f14b99d743bd0c5b93839b77e5ff327d3f919a7548c8bae054e2c81f9f13d7a455613e68dc70489fb7c6145f652729ff89e181c94fa3d30bb30e7192b1ce006e504b97b1e99648f0df068d0e0275c97a4e7e55dd5a9a255a0bfa9a3ee64a9b9f54ce7915fc5ff2b0c0a6a76a168ee5c5d2de8c1cbf56400175b7991b402edb4b8a88374564e00effe04940cfbb710796e14cf29f6c7e20318263099464342cc536b77bb80c7e3f95d538d5d599e03859ff453de2ec62a3009ae64f3a048c7079918158aa2e2281a8829d04955972ce4216dd58fc19b7c738ac6e3d14627809c5e14b3eb382840e20c0b039d786ba8f0b17ecd13f3090dde3762d74f16a4f29c9cb9accb1afdc189b54e2902399aa88968b994a82c16f4010ceae8dc49372a0094bf0ad1362244a2744f013cf7889917a660a4a5a0747376cb4911f55c01d453050cfbe69b4f6900b1dfc3598e175fb4834ab9b82542695bf2e01582d38cc160834dc4ed233e8e040dbfbb6d3bd4cf41fcc02e5b0bc33ba0fda101ecb1e4d5af6f1371f17e2736815fa6b79a75288b98280483d1a222b8ca5d150abf7d4f7d4229fde1052980a17ead2544d46a8d76ac47624c99ffe521b97e2a1caeee2668fcf3167d0088ea6d469352d896ee60e84966ac00f3d390ad07a9f7eee19c0a9ac549f53e610603256a13cdeeced84341314ea230171bb8ab12593bdc18f7efc6964718e83c16a87a9803ff679f67f6ff5ae66fb58adff01d9d3ab1164c965ea6456faecdf9eb1196a6b9ad19d89f6715df3dc2e81e26dcba565e1fa138c08cf078e1114638489d29731e23be1e6107975bff15684ac67f92a5892627ec413571d2dccc1cd2c95646b9508d042a8869bfb7e4b61dcd5cf49aad55e389ed13477b4c787374d5d2952aab9612fdeae2ece7749fb2e3d1776dddf1b84efb167249ccd5afea9b29dbc13034df5a80e7499900be0490cffe63afb8430823152eea27622b34fc9f4b658dd1ef02ff04235317988868fc07138b134f3f6cc1b2739f1dbdb17180c18077f80d98f346170cba49580247e8948432f9c37c52f18f61a46492c18d6c3d508ccabd5a7c7a67d4d9d069b5259d96587031f39708b9da1bb0becb38e50497cdf6b73e29dc90d191dd8883c1dd7f90e03777890e858e2dd12f5679eabcae3b2134305483331cf2fb24b64fca002eddb887d775ba315a58381b8276e704427b040c86fb7d73615432845e679e67d2f96912d6dccca280e484a7e3008c76c0109572d0d22623ea2bad2affb41711a193870a07e2a47387dda05ceaddf30792e627cb8bd8d4f9f9e2c4a23cf3ba909f8cb7c444ff7eba8e83cc98a2b69e16bb0c681e2b807052e3fb2f0fb82ba04b2b35578eb729115cfe050f720190ecb3b17eb2c778422050fe1774515eda701b9b19ba496fdaebcb81be816834c037ec14443a599a366fe6fa4e1b5b0ff99b2d0433d8189bb5ceeb07f017e7ff99110d77e1e7e0af88f7b706f5c16da2b2b0952e360d4e686b86b209e4db2983518a4336755eb56b400786b7e97ac8849daadadbdda9d9926e586c19c2bad409e87633cb87f30a0554d5c8d00c051446fc1216c67c4a9879a78e0b2ff21260b3016126db64039c7102948543463c3b26e6f8397f4f5a058d611675f012bdc51827be7d54801235fd5da478b16567f8306c7dbb1ba3062be161d7f5e762a010747498f1208880f3a1669bbc18d77f7c16c2ac74d4d16106208db986ee317598b6599dfd89f9d8ae5e88613f6f2eb74c268f0037fc6792033491a6313b0b50c9c31c95be1a24cb9412f013b5c62717a9409daf64358ca8c455e7bf0053204756e0cf4c1c054b1cc3a595863d711406a70702b0e71e6ba5db06c36733f9dffe9586abf70158c6ac2224ff4d6e78b7ae15976eaebc2533f3595f42a7ed075d5078520db8d1e28fac1bfc50c5fb08995e3faa7d5227c4148e12117ec43022ec5f2f986809674d4f185555c8865f23593d1c3082291aaa0f3885527673271fc8547f17a7c1f49f451e0a3c6cff3143e73a813915777bae1f6d5db449872a01122ab5a402689606c7b496e35c4e16d6501153d93a450e27b768e8c769a693d6550eae129c796c8a65d3da562e0a59945f036cded1bd8e8a00df2408af6dc384d29b93b788123668d12fd825fb3e688d81be9f3f6cc4ab5426ae782a534cd3270fa966b35119d2966792a7f71e96c5aef30fcdc14619d1b1664ba702c7a6cd8034bb9b167e99461fc268189b25b8cc2703910a78327cea604680a1715955482e2e7ab5761fcdff435e904882c85774d9f65dbb518720114c3aea9274f98c685155e3c5af6d1f551d9729c7337a38b8d1940f4a62fc54060b8045fa6095a7656120df3e536f70fda29511ce727b1d5265c8e30728784c2a36337a2f385845bd9575fd6574a5b6fedde179e74074d23ac090cf93a1f4b5b48d436c4e5855276645e21433d151d76c727e6d735328eb95b61386619fa2bfde03d1846112727558018f5831259c7f5481751cb117b7057f90823e83f09bbca1ce3797f626a4838e5d15003263545ffed59ad0831a65fd9a30732d65a809c3866a6f11a2a82dadb7abb2986282e0668a9b395fe26a99a3f71516bce49fc352a1aa31c070aa0d583e0da60327653c03451772fa04fe6d107bcf62f94947351a9853e346fed9b5586235da5714e2e2961e3160a39922d779ae40bb5dee1f1d539584ad48fa5d0ba8b618fb6ccbc8fcaeaad6baddbfda1a3970591eea44c99a6976d7a39a998e666cfe63d0b2236387b919ac2a5512a02b75f33ddc9e52ff42c5a40ded24ed4bb47c247cea7c2d103a09f3670d7db7fbc0829486ec5c73aeb5f8592c3fb0df7e9c843ecd2d215e424793745f76cfcff95d3a5937145a968be6f5bde8b713db1c88c782376195d0047e04cf9b54d39111361496ea6f8bdf05c00af829e7f890e9f5857207c6671c584fad2a540daf27c90dab9390fb5b5dd70aa5e90de410706d89f972668fdb08da5bd93e47b9cdfc24adfcd3e67f368d8518262a3d4d835f28af8c829479663efec89cef9c67962836a02820e8d2ab2d7ace5816a1b4881c11d7d307c3c35fbdc0efe7309f13e2e5eed0a4e409f9078270515e0172f0c7ebd3e94895503728eba5e480ca3b3d2eee24dd1b16eac0eb60a861e424c1acf2f07ae7a102d99fbf005e9cd1fd19d08ee789e0bebe195162e9085c4d45604e4b0ad1faecbf123b731ef23e059dbc5c9229b0f4ce54517ea35bd223f4311cdd8abc90d0b5a5eb110d26c3be3cf9b4fc614ed8afc9d18b20d4e862bb03fe0aae22ea58386520c3ff88efbafa7b477eac12a6e8b49faa9cf1d7412c64b900ee83fe8793aedc8d5e5c3b0714667767d32aff850f7147750113c34eb5d5165fbd90bdce76100d97ebb6ec115380b58013fa68dca2aacda6e04bfd87f2c1bfdf2de4c14eacac90c252abfdb00481b2430274c76faacd80b96bb3e32a06c71636b7c1201a69b0489365b4c6a1e8e0d2d8a154253c90676452cdb78d95a17f5464a0cc494e3fb5a3a9c740919fbd3eb9e3da7ba5cf81f576f81fe84e1526b2b30cbc65fd43181f90caf34bd97b6066ed194471b8e8eae46836b211bb2431b2c377d5181c9c281d37802f7df9a87488241dda882e88f2006076d0c8c0d30cf3a296586d70d9cc4b67e6b3cff00b77946788daaf344e12eb9bba7249c6074ab0a2dd652d2127e85519d9b03d539fafff3bf513b8273492dcd6936815ccc05787e92cbde9109945b93c9e14aa21b62282e2f859ffd59eb686ebd26d629f27311c6fa08c24b8d1cb9f056f5d57a253f053c0e70ce81254ba394f3a1924262ee7c13d29d9a4d2e69163ca0eb16720df0dfb7d407afb5df7f14ed444663463a8ca41dbfb5877f2784d2bc64cadc5be5fbab3955ce8122f48fbd5ba65368deefff2bc79215fc894d570b929b5c9050013c1cd9a424cd7656739a3a504e23ac40698336ad1d8265840a8f45b51057b0c737f0e5c25bb5cc7bb2804be5ec891d237f52196ce51ec045eff2b401748e7cf4c4031634d97781cea3f7ed69bd0b7f1709331deca3cfa66699641f5452e7a25bed42deafd02864dd0898e93420fdd696045f1c2b729d3a4b7c8549c8000618a8cd41f6635ee99413d6847be3caaa2923a316462ea00b07d8040f66ef1ad71d83a2eb58268d2a85e539288e52d49e60612955241ae61a3af8b70b6e18f2a8aa49b628f0ac4b078c48988b5a3e54ed817a10d90d055c835fa0c39355d7680547f224d162414564ef764a60f864acbf4b5fb2684a491ca99f97829f6629235a5f9372bc36f14cdfa1d5e30c7169105962dd4accae08608402963f923a4ad60cd7d19d42c128a56e42f855b2d436404338a18c4b1fbbc4b6bc73611f2abf169e0eeeed31c721709608e2bbced3991eefb8ee0ff2f1bef81621635c9f87cd0a4da25b8e8b1879c5055526cc9046c60c5e44b68601e312337b8612cd82ec78752a2166ac690111509d4d7be9fea1b5b51ed3e85948353b05c9a62ef72d987995b984af0b5bf7fe3e536dd029482d5061efb117151f38750d04286702df1dac7c5aa2b29f8f9f3075d6c93d77e88c8f54848b010ec6fbc08282636fd8d1878b2a5540c0199004cc14a99ccd417e580cb898cb183b2ee643ae70abe608b1a8317b3305a667ea68972176b16d0b4a6c23ad7ec74feb8ea76eaa409a24cdacfe61f75bb6d6119f74d21899ca3b610d5dcf7094d133e74c53eabbda05d1a291d2f01157d6ba3ba011a50d9d05f6bc430f3950ce99e4b4f50125c3a9ca06e157d965a932eb23083661bba9c97360770992c2024934a8d2bd04b0d3633d81f2d49516b8c8410f1e1ebc487691629cdfad9588c484548a9dd65249e53f11775cc139b8d9c22d66914369ac3cfe18a04f183d76e62ce15c1ed6cecf7ba7d7ca0c2361228912e9c23abdd3694144d0d91725ca1c3e1f9cfcf53a8f6ffae6aff69aaf1549447dae4bbb0122c4521b8660bf164d45d962f39eed587448945fe7afd4425da513ca97ee5977146a56d0c5c5d94d2c657a0bfcf7a36807d3f90fe0c3df97d0e960f39491a2aac47920b3f15f2868fb9a5a65b871822a7b7ef6a48fe8472f9d8ad04861c5bb1ac78adfb182ef14afe30aa235dd44b7b2e00d1db06f1806b62ce53ff9b68fb98ff1510a12a53c155c26ed9c9e5fe517b8ec172a66ea8ac11d29b48f6802e622dc273012b0b306eada4b50f4d5aca6a0cce56cc8b325c1c318ce3b64c212644e62ccdd97c26a93b92480fb3b70cda3bc67160f65b6b39e2aeba6d2a2f40f62edcc5e3939589b71d82b1f21a4380c41a14cbf070da558dd9d6c38983e03c0b0aa45dfafa06087066f638b56b1eb9dd755f146c723490021dedc6185d84aadb4c6685521be0509f2ba17c548f0b0079b3dfbec999b59c5b465c0685a4a162573dc2c6b6e6e450b535afbff1a7614980fa3dfc9276cea299cc7e34a3f5f2b3e018e0a94ce93435e5d0f8832f229a80afd8586eaff97f77ad2e55dea3eab2a84e14a2354e42ca323458c46d495040dbf6f7fe17c67bccfa107378bcb25a7c3506a03ad7b48fe66c8dcd650076b166422b9df75b4ba875012fa365fc34de07376c9e885a6f0e8f7250c6c2a3d6d3ed14bc8da721861d3386753810fa681c4e74c177f229355f4c5c725f401205d3a977328839103e277da9b2e35c10d1276889a50c968bad4d736c8b930f8b0a26acc152944a89ae885f5e91a62d9d99d203841d50482290f4620a9423abf3bce2cc1ebaac280bc12fcf7e61aa70c811b52e3478202f8cdaa2706a78c848b409f811881d21071450eb569bcf2afb3c897bd41c2e476d21c8065f3c160982fd0e28a4ec9d5939a26005ecfc26ea848bf6d109be2b0c6db2cf4663e571b5de386d32501febaf652ab7d0d6e4b5d7953cd9640b7eb0b0a233b24fdd42992bf875a87607226597864104499c326c4ec3c4b901649554afdbeca8bd298e14395c3593518bfe9f8bc4f170543d0781819241ce2fdba67dc3ad023159dc08ec1d4b0cf04632fee2a4338d7c24b2bbb1b53746f8a675456ef81ac981357102d622d9ba400db309cea757ab2907f1706b2df9d4ba8e05b3381ba4164259b27e39bc99f66b87cc0a2e4c7a4921d5f46e0d892203fbdc104179c6e1108ae91117cb3a7ffa68af6b25b5694c4f0555b68954741db59b420d6aaf5da1494d4a7e8d53e8c7f88183d7f89ea9e393aad28792df8e272a765e012b60189b93339141fcaa8fb7aa9eddedda5999b4f0ba130e725db4f82a49a91c62d282657b9c91f81e46ea8b18a7ed161adfb98198a80c22e176c286c3379f4683d1e86046afa35488145759df70b840fb876146f0e01fa37631e800b08c876af3d254395359e6247ad9a2e4ed59ef6be0b1a98e16e8b62859417b9b38eb2022ad3612b8288746f90e54f7af520bbc077380caa6914c45c2d30ca40e4a3505bebcdc5299894e4fb25119a550540dd2296e6ee929b7eb0c6f31001087539f5fa3fe5ac79879fc22234dd5dfdc6cac91d1e3e7b33d0c37bbd237a44e3c94ace7f9ce18f0f7d8de0470ee3047921e1d9cb0761210f54ea757c3d39de480b6f83d54004308a3c8fc57904ea5311ef1fe84aed1ea8a179572b05194ab5471b19e2b04b3a07fae6d04edbf6decf3207d1d67b8fd2563b7e3d1c6d5999c2f828f30816574a13a06a0125247c6d4f80e17721eb10b5012997d8f1406e2321ea4e44087a57bbace6ec329f98f48759b0738000873810ee98303073b68f7890f671ed86abee0ad139dcb9217396fa676d844dee06392393ec39e08f28193f9207239a80d61d91800e04047738fa4afc983c047f6653880d39acb03311e4f47e029a363189c902557a8fa5cc00030f9739fbeef3197264c04003c24c48b7b90fe8f4b0b928c321c1926a15ed1ff8ffd022740b8324a21541c28188647e99a3678bed792a23d9ff00772fb931c92fc4a7482cb28fa61542b2f4929552353a5568c222266cc429658fe5ad6b6a4440a00b808ae6c1cc95b477a801dad23739dcd9b7805cfe5a6fe72be5795e9c96969c918692fb633b23d0d087f909cb84553e7f25ef9d1a3ebb04822ed8258d6f8308e38d051586259b101d070f69f82493eb16fd021bc7ac737f6a4de5618c6428e2718480a42eb512b009368ec61dad9198371bd130135f8cfbed182cbed89648022ea545e50227700b5f8a56788ae8a1832453cf7716743f184bcfd84a002faefdcaee2bd547fabc64093de2789c7b31c2a749a27cfb58f6ffc8e07364a259e5bdc0ea2b7f29141282705d20b78c216ec179086730cc89e551c9da02269e586600177a2dfe098a3ebfef70744391cf1ac5e1c7126581d4c93a61ced9334dd37b2f63a000352ec66a4f4c020d2f01da0cca789e0b75d8888cc7d22b31dd187c0ccb329d1f8f4b3234a8b0f59fc7cc44fe9266f1da8de1573b26151626f0a0856dc626828e4959a0c60192fa78acf57b24b6cfd73eb9f4461dcb412a09224a5bcb7d52754e1bdb56b472db6a7e6cd7225acd35da1c2b38f49d416a7d989a663f81c64d5d56405f416bb9c94cf27ad2543300111626365776efe402334ac476ac89b108365131ee44d26a07a68616d12287893518664402c47321fdb774a16cca8386b90892ce61d38202941dc1311000718564817bb5c0e787cfc951e22a459af66f3a707e6ac88d63bdf1dcfaa2d89cd168629979d47e55ed3d7403a01372f4d486c501dd7ab83bddb47c287027fc20b55a52afd40b6bc5c3f180e46c95e3ca4d0897d7398ad5deb40a6be340c14fd46ccfb6f01626d89af1751adf74bb89a40224f7701de168a500e883672823c31fb66f57792c6a8ec3fa0626b96f7880519893dc318086e3e830208fc935d681381b5c6fb817098684ce7186364aeee0c91f5c641b4037d43741ddc0d304dd3c634a6175301b301e155573ea3ff964378ba3ab1e3466108d871d12989424d5cf1f2c71e0c94d19d38937291696c3b93a3556e95281d60d011bf4baa84beb2e7aa5ecd957e448112d0cc49e31b9970f4c1aa2e395f3f2be53fb3e5bdaba799db65532f3c538fdd32642d96bb22b62a74ff6773b4e86629bfcb805cce57e25df63b88bf05d55ac859914b2d57844f4e3974dcbf21a6fb2f41f128f461d278319ae8099ba8107c0520357c0a509459bf32fc9836953274ee21e77d163e7a07facb80a5760c0fccd6601083dc3370335e82a1741b4f0d95ee81f8b0bd2d99bf3d3020aed0cc094715257ae54876c86857aa76fecd031265b733741a7dab05d231ad378c1edf620bcf4c40b71ce77dd1d64e76333f5bb75009f2eee39da9e650f7796abdcb6846e845e85e74131f342bf816ca6537d9c3026e3ffbf7d79dc591d4cf40d410642d08db0a406341d05c205a0b40eb02c8188a5c515270c202f7d3dade1e933f7be49475ecb46732bd81d1da199acb0326b629828afd3366669a41581e30db11697173647ff2e9a0f8c400ed4185a5e63456d831793121d561d3fdfa897e772d94e5f2f3a15c1a015572d581dccc13c61909e13cb0ca51f720a8695e76c02ef47d1f7de6b89ec5e968f21e14ee8156f3ca75d6d3e7d931c45aa0f8045b83ae350d17e0f05e22d84eeb3deda34ee2fecf993f1f07df41661c3314fd4e8b04a6e5d15178aea1cb60944a2f9f36f2fa820434eaf62b184360e2f22d149e78bef5329fa5a19da94829ef7203893a6e0fe2adc3d4544cb9c37bdb4821f52444668032efa602f7218c7e06ab853443eef8da52e6d22161857b362b8a0a451515c5d3a0bb6ecf80db93e6b9e6e46a8cfbd67c7e16b96c2a5f36793dd72b99f89a9911b1a0aa27e4de7d21ceac8ce0e4fe62ac4d65368f1d106970667d3b402c6b0b1fa4cab38c2ebebc6eef4d006f10d1a80edf182faca38007551a4c3552c2cce967a8b7b245a0718dbf8f2f965e607665fc69af9478ff875e19be031484ceae7ab6ae4defeedcdb25bcbf0d81e892f7dc5133786653a25611afa47a7b3cb5975c059152c1e9640c05777950c5133f76fc734a9228842a8640ab0994676de8efa6a883f491d7112351b8d81431fd46396f08d8493cefbcb43ea1a028ff49d052aa5387b3692a00e6c1c5db02cfcf904b0b8ab4df39322e1fe8123d60525c659c5e8502d400edb51b1fa76440d3effaa9740f1bcc5370e0da6fb6708d3b2858b531f742cf4a54a244bb3967cf1af5e469c38f14c97efafd35a7f97a1a1d371cd52b0d4b74d2d4cdd29ac553892dc2e1622ba3fcb408ca334965501850be38a902e4f14d422e65c7d10faf40fd98ff751ff63df6430aa29770f31e2d51f10d337af823315dfeffd8c77dc07d90c15ed932cd84765b9f479b570502ddc9c24393b5965b04d04f0ecd6e40da22b9594f5ace0863960f2559083ec9b4e6627d6ac0713a3624efa6ff931f90946cf8cc23a621769888b0eefad43f904dcd6ed3e6114184b9f854bcb434216998b8f200e2121dc575410a9786e08a0eb4b6c6f78e5a057f8f3e3fb4f6b8ba71cc0517aa7a9ab01387ac4e1644fa2397ad2c1329fae422a87e28aa8f95a37b76bab32340422cdeb88aebd4845e05fa6eda3b7ccefefdcdaeb3824020b3efa1a6437fa2052ab732d0c8634e37fa4d5e32c3eac743baf895caebd663fc5f3744240b9f2d5dec7ee4673c521efa532d92600d40abe769c566647e0f37aac86daf67d27cd393637e39a23a694c7377e8ba87dce197d653c0ef155cf7a575186cde1ef2dc93ad472212a2af05fdc82dcc64e4b07ed4da0871f971e19057f3c78f7372d0bc043f998acf0856389c84334a5fc672d823c6cc41db2ed47c8bd3cf20e8c519044eb07dfaef0dec2659e910dcc94a927a0005ab9ec89c5075329cd967702cb1d7b52e80e95042994e02af69bf408d623d4aabfa9a236e6dbebc0abd7d97d068129be05043204151918f6e5fc422b2a1a4ff780a1c39818a461a10e14626d8eb1ce54ba6bc35e5905b4abe8699d35180ed491aa75bffff75103d74f61d2a0883057947769d66553e1c899940670cdd4c4a63d08554cc4735cd5708899b66853dba5c57eb1a19be715d868259ec511bbfe4c8277391fe44545b97fbd2e4569a08f357fdfa6d0903526486ecaa0e25b665fab2c753a26e8b205f2646397be12039fcb7aafdccd6dc95d414579a5e3aec91c4c4be57777c35ddaaaed33fe2fc02ba05acabbf071a81dee6a235fcfdd911c379c57c44db6f1189ba7f3b3bc0ba7431eeda6d235965a165666ab20fb7acf114b069388ba48931497e8928d6369fe6a59d3fb3328f469dd063e034e5a2250b807fa5759326a713a98a2101eac4a0a5186b17b686d67b430fab3a1ba98e9479989d2a4c7d74a9bc85cc214a8d3d4d4d154d15bfd529321b29b81ac44f8763ef5cc91a0128080a8f776778926e4d5943f2849f6edeaa295753ca6b84ed77b814ec531edf676770ecfd70f67785a60380bca750c9406dac605dea8a63d8ee4b28e5b1addbf7674611a6c26bdee6271e47c5499d39de4e5e83f8c255f1aa833f07cef3fa04e5eb9933f6dc146f17c48ea380e8f7e6a632e161865c78f4f75d03fd85a660c3b347a8941853ad4ff4c9ae6123cddbfcf56c3f114010d74ae8d9aae7457b60654aa26386a8895f7c9d9c6bde865764ddd4044a18a181efdc328108341545abe93c5d3b9af7f61465d9306d7d12813ef75c40f05224f0e739909d5ef2dd5eadaa5b9c2a0798035269a353bac003d5b5c26a75b8a4754c2bd5b3c9212aa1d27275c56a93d97698f626e4540e935227448a07cafd9aee62e1c3a53bfb13781608605cbcfaee925ca03f1180ae16a3cd9bc923dff53b018eeb0826caf58bebd889653b54c86058268eb23ac96a0436882b09ba3c42a356699fe97e7c6e573827df18b91d26a4762323c8e0569ef622c8a36d35cdf9855143d0596871a3bfb8e707a56632d02ad25f84cb7bf7c470facb335a81da2fd14786005a180ce0f91bf490f4eb3e1425b5733ed232ca3f796e4a27895c52ee8e3974a4830e76662020f65a580ebbb035b654581183d131cb0c2b9b7df606e8ebe2ed35007f4ecda95a0772af1e4ad671e881bcc80f2f353f6af149e9c768280d9f2638a5c7a27e8f12cbe257a93062204c34a9f45049efb5088d12423d1e260b11f44d8d0399ae2e76b80d8e19b1a95ca75d188f666a91e5f244172040b10c2dbd2ec55e6b404c124a50129a415ec34af8f1554ad97c9384c638bdf69be9e609771894d30985e30eec57129c27557cd655938c1ad2852df9bd64143d78487b70040d1775957287933294a71673c3dbbcc4e21a67aedd8da6989e678624e6fab649aa6bd0e675cf179f48b555c7266a7aad821c4f031c5164a33bac8df677e3c7a0d825b77eec8c825b2280398936d183de8b63fb8820f5da0fc84ba0a49f277761bd3f79673cbeaa65a36551eff73780c884ca0c343743aef91f2aabd7015f0e96600b855735757212fbf41448048262a004e854efb886ba0c9cbeeb6f8903834d80d15aa3d7fe02d21de0a5fd6c1f9d78f81ee1a9a047b16d85c70ad1b5a3bbaeffb001ee92546ec1716779b157cc206dacc6e53c30b053d211059f3202a60e35bfda7e50c7e4f2ecb983a2b7708f7200d0770900d5d9d43c4b097368484a36624a3ee8b6a9d13e50a6df02c0c16e60637abc743d0f61603bb118a34c5d775fea4aa56eb273a585e2991321d911d0d666de2170d72fa37543a60e1f21bf07bfce5050145c41f38d324ce994f7f4fa29a27239ba8741a2285e809fe51efe3b13252a227e20df70f11887537eb8535a78673f1f5fbf9a9d845db7a79c5f36d4d2b756e380ad00d1ceef4465a84c4528a31ffc4677864bcd0d7def4b1545be845636dff8423146c0a9326d4e3e410cd3d478f3415069b879f17f8fe88c2a3859d4fc9ed231596bde568d47d51feb2c709d424d16a37de0a1e4cc6fd80bb71da1a1aaf989a647df2dca58cbc4ba77865403ba804159a62622adec67fd3c439e397f9248eb67d1ed30b46b841d2d7e64df76b615ed690526b829f5edc8f4a8c690e10481c1f4752cd7cdca7cf00f712997f3c37d0676c0464bcd652ff4dd7201129a0cfd67b1a6d22e47f60470f511315fd7533c6ee8c1a8c6bf2ec434ada2f0024001d868b1f070b866faef2f98a70104ca449282995fceb01ff72831fadc70666597c5fc4f4b1c903796dce1b23e6cb75d6a78446c5a30a17e6d0b952bee182a46787a782f7888bef1c9c00795930201af407978e37910314901bf911a314984901c71dd2f00416fef639d730af82c48f0113811d412278cf3e223ef01d556fd0bb8e0888b6e8a0742ab6335b3420d930f4a40d8e0807fef74e03bb8c0c31e767f5614a8ca77944ab7f1a806c8bcad9e3d95ea5093fcab8a197fb27c1eb426ad7572b3dde9b313075abdb10918a973d03dd92a9003182e2abb4ed071d0b6ce461a01303e0934ac469347fb8020726784e47747a5bbe1e92cd29faabe0a042edcf077329b7021802b17a2a905e1c08e06a08cb138ebc826ce46340748b8d128d947ca10fa91f7a5338aad8edf878532ee5491ebe69c28955b2c7aee1839832c648aa9d0b419fb1be4d546d7bd9995ab9e3a5e3d214a525749993d5fd340b59b9ae8469c71cc3478dc6875f4142adda372f847ba9d2c1c075c169f947f86185f79792a89be2826edf0633254e16bf87fbfde4437623974252823b5ac466a84d91860e4c39faf97f6d3cd09c89f7e8995760b48616b0230bbed383333d1362c92f91eb9774b766cc453c194fa36bb128134e547d271a8830c00868616bb9aecb374f390a01e657d2a40d16ce4809f259b00f53547b2d8248f454c83b87aa9e3e6d02e5114401ac370c63315fa3a34f6b4a0428495a23c61d50816324f60de5803438d0b068ff57f73f904c033e887f2418ee491dfe564bbe754176ec8809903f0e9333c18f9e672012315c0228dc20db5a65432796320f10fc79e2c8ae584a2748fbaea4b88f1514937e82d1595a6e6904cb376008a8ba6dcf164365aa4acf5701f43440b70895b3c7381a1ae0ea1a76eafa96f2e454a414f1104bc5b4917d882e1aab705ee0ef37ffedb19c3136885877c06dd4ca5e0a52bfd9846a9ac201248c1ad67ed6cea7cf43809a50ae4280c5d2354ea40d777f20e33a5dbddfb36cac35f490825f85913a20bdf403f8e0d51d00b028b9c584052140e464e194c5113b0c321104a218ead76bcec144cb1f58f1275b2cb4edd096e5d17668a1ae0d9cd0d285ad7aeab226e77128b0555f71abb4130ea8b350dafbe89b1b42146b0f18cb7b651492730fa13d2860ef12d4c459e255584dc11e2dde9b2d5a64a7532ba28b9c35d1740950db2dc35f54f307ca97f7558f63f8739dbc1135065ba77c89f9fdab83e1cf6d577f47a6471cb9ee3641acbca5d5ab3a94708460f065a27b027319d87975392ffdcdd90250046ed894fd28c3ece9cc8b139f7aca966dbead74ff7f6dfa80841686a8fad4c9695631d928618d262a6c854d586b6bb247f614ca1551fa583865d0ce2e1ed1aefb201142d33916430ce27b4b0f9957a37eb42c4399c1d259d47a1258a258761d48064d792143c5458aa9e4f0a0738fcebc90b051c45e0c921c2ce2e01d376e5302a949fa0aad91118955131ed4c682a9a91ade47f1edf317a1d9500f0bde6b790fd766c0cec49ebc4e49009741e826c78179add1fed36cb07b19e93d3b8e4117e7a680d9f47a50fbeb9b685a55ff876dd215d03581c78d220730ff1a1cd63e83e97c950da39b146d39bb4b4d3d408d929b26aef744fc33f31954930ea1c59722495570a8fbec402b95ac4a109229ad86fe9a5d135b76474f524fe9f764e1174c7e200bf72414ed49a507c30020aa7395a5a304ea99b0ecaf40a3c8f7104c0106c0d1c2ec4cac0a3207dfef6c0197805186723dbf01a507fd2c1c35d050632005c773a10d6932aa802c22040b7a49a48582e95f96eb678d011b4ef9b39b1bebcaf977a08d95b6e99e49652ca8d05f0056c053d2365c89c962e768c6d1b638cf1d40f2e47faa671b4360326d128f38bceac1ac836c6326843483768fdb5496559366571ceacac2ead0de2272eae91be691cade71716409e17a535b82054b6aa27638c11e35f15d6c698e862273a75e454af9a7edb9b80f46e0880ce18d26407d9634d8bdbd534ea2e20910d1d9dfa29f12aa34a2df08f2234a02e1ae8739ff33500d51b474d5ac7ab785d1255be682a25669efc68f4808850adc04069c8a885b2f6b6ccc09e2fa17531f2422686c80c93313dd6b400a1c614af4e1dd96c3885494a52ce88c1c3fe20fadce7fc4b79017dfaa671f4d4378e2ae501e2dd30c370da04a8ebc6f8e165513de9882ad2e3851d31d080c420d59b048e3e7dd3385ab751c306c7950d502aac983ad24fad140e72c0a00244828a56f6ac273c2377d0ce38d2d36fd158335764388636b051e454c3da12327e60c7182b5f6460944e5249f606895acad14292a1b01b8410e18103489493d57115629dddc0eeeeee5c64dab868e252c585a11d9ce3071322ea43a01f3ddd335d67bece809d193b53e6397cefbd370f651287adb085adc0186322bce55f48e190bbbb238d4065f807749634c3cbb9867c3ce65111a5717e4131407dd3385a8f715da47a06872ea97055430883c9ea66b841160c309e55561155a13482aa6ef6786828f92f4acc028961bfee60563741b3f2c80d345ec0f0c28e5b1cdbbb6cd9f8b9bb43153585eefe3840376e497cfaa671b4c6805819af183b987a356e3c1d6930c584f9aa0a7a7103900e6b03885051c6185f8c313ec3647573df1b8e6d8c872678e64b8fc180dddddd5310234c941896ac1043851a67ceb2c88a8aae2fe7eeee476355378fcac252f36e3cbda4cff1be2c560b594347b98c3187198221d908366641bd116d943cf780212c63ba84812aa56cd495a57befbdda8bf8a6bb4b6d77c718638c7d8e650bb30dd3ca615b39acabea727677332ec8e8e831609648355d1d6181fab1c5fe209a6384924081c2066a852d9606102dec73c1951f971d463d2aa819585660f0df0fa4a4d701bf4c1017a0e6c0aa14c41762b529e2d320a85dbba8c3cb2f0b97ad1a943cd9707f40aa8cb4d66fa1e09b679aa0aa9bbd50493fdd7054dd4c435575135f5c4ecd9d2015e34a500c45d610c92813d2a0cc8945ce39e73046931941babed0b8609dd21011d3288901822008fab442695eb849e3c50608823af57a8d33c74138e9d501e08e00264869fcac5ad00082d5c9c6a323e64927cd3327a79b21c0c7b971a06393c051a74e711c47912441bff54ab6621530cc17b468eaa5d142f8dfddf8b49146a38d22f64b6fd0c9c9e96688fba2a86387eae7af6f458cc3c1246707febbfc22297ead95c85f7f7c91ee4c9261fd9963d18c5301fa899397bde3838f73033bd8893f921c9c69f1c312e25b9e1147b748e619e61cb0d21014a4254548084a14f147f1a728fe8e8338d2eea53735929ad59cb8d3d9e248bb17577b352752fab46b86a02f3a597b544bd31eac60052b582d06bfdac794acbcf941fc2b453b2f5f3a3ec69da037b813a0f8167782d6c40fc72935277e4512ff9274abe6c41f49aab502d5b2a1bedd2cf1773a5bfccaab20c40a42fcdac3b1c5af4a55fcfa5441f88bf845310c4551b4392269a25ee12c41415a52761cec2c8e6f495a4f63ba6d8f71351b792302dd378f71b49f37249d527335211d662fb0d61bbbf1873724f51a29146dfbbf758a006e4cd2ae28fa6647bb6c607e8a3b41c377dab5a353327daab9f02b52f836fc1cbeaba0695768f5ed6f56f821fd7062f920db61183e15cb0949133509ab18e7d4cd1d0a45b71dfbe00bf92f20013f1b301bb0f32141ef4aee54000a856f5fe88642f6859c2cc16157909c3301f36b6d043a774012e8aeef03bea520e9c3ea707382170c68adb5f373ced65a1b6207a7b54e4f543d333331312f2f1b921d568f299bd6138cb3da5a959c5cede8dab6b905520a82015867468e0c5df96d8c5bec985aeeee3dca9404880d4d15686becab2c4b16334b8a6b63bcc19414a20c3606a7b2ea0df30b1552244c06d8565996bef9734105566df8ec18d2964ec68e29989998a011627afac6e30791bbbbbb3bc61863ec2b7a2280fd41f4b9cff91732b554bdf0967424b97283c8660dc155498ae6a885c516ce37e79c3fc4c939e7ac556af1b492b498b4a2b4a6aa6eba8e8c9b6fca9f8e6c6592ca3095892a33f54458672a60803c2bb6dcdd913c54fd983851d384a33988484d65ec18638c795637431e635614a950866912a5c90cac195b3d3ca3cbcd98afa6a9444c154acc27005fb24f4cee3d8aa95860491264c4fab67c5dbe2f1f986f8c727965ec18638cb77ca9ba294636628bd97d9e145108f7de7bc7808d191b53165686f1c292c298700828a8518388113434aab02c13b1273531ae1d62513631204c2d6090a46f1a47eb161af080e14a1ac41812b44307638c6b78dd1a5fb4989d356a10b1bbbbbbedd9344737e8908a41680cc715cf9178553791924224268d14757b1f32d3b9d9c3af32e7523a179e7349722e4cee38e79c7376c718638c7d3e1e638c45ac1aa836c6f94a11819924b08029f52002c2c28927c31483654cc4a28685bbbb0d44aa6e5a8db021cbc92879793bd55b41ec1905197bba3eb0a9f5a246162c4d5a886161976cdd177cf52a0a0cecd1cfd76f0d71fede7b57b0b2b2b2b2c23a2eae91520599608c31eec2abba99148ee8c2a46f47faa671b4f69dd5848c2210c618632948ac6e86b627ba40f20232418a2e13a6aa9b514ca63493aa1b13249f9b59565f805c31caaa42831217387871617dd448cb488ca864a74c652a28a1bbbb3b550ba07577d7a25475938496a7502fd1f285880bfb7577f72a4a55379f421c7a4415aadbbb635929dbee32da60bf1e667777b3a76c8663a2794199aeaa1708e12083d346cad01603d8c018e3a5295537b9422f4d62e9cbed718c20207dd3385a6f10e3c863b400aaf118638cb7beee1698f69e7e30485795c53f881a12e6447d427a403ea8617536768bb48ab68aba8abe8ac046a0c7186351c84a44cdd7875cfb91354c1b4015798b5966ccddbd68da4046036fa98d95b0aebf2164c3094775b050039903f02062a5e9a6884f2a09bbbbab505675b30c793ae9c6f451b9a9aa9b24925485d083969f94a19155aa774d4eea5e230f14c8afc88c2ad7e747628b15a074689ff3b9cff9f751104c076632000aa588a48c4ebcd0f891850b0b5b4a585802d34c49c395c62bcd97ef5112512803a822dbee41d58c560082b66c497991e48465adc85c602d614445b60124e11a41553df0e9480a0cb2740345e43e30d19382749c8889161d4a5668e1826507afb0b1951739a0367e1c102fda60b9a2424d95a6243fa08c5c46334c1256c0b6c66846faa671b4c63d34b34c878e9565898431c6582cdb13eb47b5312edb226a6602f46395064a688d911a244b2ab815199a848cd8dddd5dc691f75bc7dc3137ec86ddb07bc52c453483322d6de7b1a49554966511d5da332bcc96d606ba28b548d3bb02e382244c4c0e33f303e4e9f1e523e902398cabba03461908c38ca753795637c31e120661aec26885d90ad3858f9072009e8eb2784947786940de1aa6706909060c1830b63b908d9c1f223c7efce86a01c310216ba4ca05de07f735d5a2b11fa42d339aa86013830b4d11a3add1480b8bad7c256abb971b4b94c572a3e80a88182b9489c95163ea8ac82f8790cfc66760c20d3698311b6a12f365086d4bea880a349a3a9a91b3dfc897872acac792d0c79316e3907e4b7600da6309979a5102467468b11102c272460810164be30b215978e878d99732b132b2116566b7c72b3ee78e31c61813d1b01968b2c285191c33a4f90ae243065f53453a32030d474361e08c290c6654f5fefdd6d68055dd1cbb23d694dd1166e56359b162a46f1a474fedab208b6ae4cb08989131236547ca23bc1ca39cf3e7cfa0520e57d282939cfd54207372c6a20a0dde325bd2378da3f54f53d9ef890a265778541608b855caa0e2eeeee5aaeaa616092f5b41d2fc909208c9924189fd11f6956574860626173fab348e0f217dd0060ba96c48c891b2301f555f4a34bf6bc1f6ddfc4951b251d02db5627ed9c193f4999f8209346ac0a0c2ac0c2acb9df93b9fede4aefe08d2aefd5aeb0002b03039db993f6b1de0c0658b8e364067673bf345545bebf5acd18220c5e1bdfa5eaf7e7dfb5673d7631841bbbbe9f8dbcffea1dbb73700dc7a796a51e6e04ffd3b74008ffc2f765bc98cf3670e3843cc107433a0e6ee9f6d1dbb0b6d56ba3e0d731e541060cd857f3f8419e27fc4e9aa0b5fc48555170220b40eb8555df8f76f5f009821ec9ebf2866db71bfe72fca94ed41cd85e177c06f77dd1d30397f52805484eff9ce0f73eeab21e3ac20ac05bfbe0d8b9ebf2a5cdb5e60047b50085aa1a2dc3d3b16ac6173b6fd4ff8d94e9e70ffe24a088356a80077286c4b4248ee50b83c20d9e3b32bd6887f7cdadf0f71b59f338408e2ded13ebc3111fedcf317a56cdf891bc79c5fc44dfaf9336ec4d1682388fbc7175145d891d2c6df381634edc717eb2e93503ff7d0b33c3748a3b6097c1dbd25ca56ddb910a8a18a714e3a272dc13833075acd8135377178e063ecbf0b0183d885183f04f07ba08298bf28541b3f08bab0bd8fafe8938debbe41d918073f6de6606aabc66943a2e78f0ad8de853f19bc76ad6109e02dc76b5b6a5140b7ecfa736ad75a629c1938691ace9e407d3bed6b4b8179436e30520ada9cba1bed6b8dd99dbaed161a8d81ccc6902e1b8abc6811b6336b1c6c533f284c0ea87457af4d49da9e3f28bc4d6d394e5b115d435a01c669f3a1ebd36e648c7bfeb492c8aa70e9521ad172e66867d646fa965632acb5993958f16f15041e71ce98a671465a1bc16a2177b417693814b64e6a6fe61671bae68a7e6756dd0ecc79607f0228eea634afcdcca186ff411476cfa7e4b536675f1a6f93d2d436a577e1d5e23d417271b79aa336294dbf9e30ce1fdbc1fc3d99e2fa39a9029f52902c41df56dae4e7246adf3d7f3fad3d31a0a2e40ab4a5940789253102aa273b6725c39f2626d993c29ebfab4f290bbf1fd59ebf5f6f83b0e72feb6bdbd1af7fbd16f0efe0a493e19e3960d36873d226e8efec578aeb4005a573040b5eaf6fdf84fd2aa28aa8361b7ab4cd0aa2da9fd59e361b93b65fbf2785d07eb576e2c699938584b683400e021e6cd6d4b68f63a7ec1d03e68395f6c3a2daf369ad3e48822d8cb356ac2cecd741a1cc239479867206dac069888fa5710aac0a0c89be4969a254c4071594145596a58b30694fac2a4492ac0f5eb652257a11f342e6c5ecd605e429c3a2e451b605c66ac7a36c2f8ff09adc7b198489859185991df9ea994d1afbaa395c108d49b4ace8c754d6d44207064a8e646810a4288b08099392fe6acd40a03eaeaa94e17861f212e565ea627777f7230e5249d8afd3a99cc1f82d7b343538d450a921a2c64a4d163eca39e79cdd1d638c312ee2e351d47499906a9b28126da6b274c0e30d8dc05156c368c9b775058a568f4d150f638c87ae13b0aa9b63978493b29b55025fd0231ea7f2cb0ab598bdb22f44d644958d5122a20543ecc22843f42098d5cdd07b68b0143234635ab4b082cb90265762e60fc7540e14344d54b8aa8059da71fb643bf8c331b6c4018ac897f465289422b23c9d7befbd4b94aa6e3ee1a02232628915d6b19265e464e316a2b8b802b388f006102348df348ed65869b2e1b9bb0f6d4065fc56c821c6c37771c618638cf10c65604a303c304960983c7773ce3967c718638ca588b05f7777f75de5ac0544793a496559b6c813cb8a6963ccdb9f73f4b9cff9bf69fadce7fc039101444626e3d2c21813e99bc6d1da07c6ae996a5395411b2c1ec6180f8dcd6003d25041e43a3768bcaa6e7e4123a6d190dd9e36e1b9bbbb4b11417970a483c1e700cd48fadce7fcfbb05f7777cf2bc8b6bb2b61bfeeee9eb35e446df7af59be90c822a689f44de368ed1863a2b1aeaab22c8fecc49ac2d43504e5824ce59e954e5cfd91dab8747d7a2ded82b47bebdbfae3ad8930bd1ce76eeaa693d43567806d5b18a74d06db97ae40f6fc6189b12bd49e3faeb29ddbf3c735550205d0d89ebb15b47062b37d69db64cf9f962e9b66edf9cb42637bd8f30783135a1b41a4f2b57b44b0c768ec9e146ef60e45972d37a8bd4331053576d93b14475deede9d0043910c8090646bef4e40b2b56758967b9eedf97b7e5d1a7be8ae324cd9b88dfb81d9da17cba6e4eef755b6e9eea70309178b895d0a0d68ad650047c24f0a0dcaf6effa5836fd4d02cec4bdd6fe08f42d8962dc3f19c8368aba53d06dff07b72d0eb72b8a715b1e8e4d6de4b64febd3b74bc08aa3d8f6829ebf2a4a3b143fc4edc437913ffcfbf8ed8f37b80ebbfa1d4a88bb923db69d8266c375b879900696e51e1fdc768f75849bb7e1f463dccd579ca5d970e044b36d38bbd76c1baeee11ac745e0cfa0ded316eb2d9b67969056f6a6f778de6b569b8fa1a677b11d686bbd384057e96843f435b8296c01f76286137ededa691bb1fabb2dcb8f1475ab371e9f16f70e3abd8e38f96863d826626d93e01ef14759be004dde383351edb0bf4f8235813617a5d4b29580770337e56b962dfe074f63892b59ff38338d69e567776e7a7e547e15bc48db813b45aedf920bf983de4ff4933874b6e7b433158ad46a3d56ab55aad56fb995363338ea2388ee3388e36e41c8639e71cce10f935ad619cb5d68a6bad389c1ff0539c6d896d89063fcf103ce10c01364edb11db0873e7b8b56eadb5d6f13b8e363fe00f6d4af4fc5129dbf7f1d366087ffc35af3ac671e286f66d126db2466bf383ed45d489a3cd0fb6af389b127df3b5a7fdf836255afcfce1e3b729d16e6db6a7cd10f56d5fab362ea3fdd1e26c25f0e3c776560e5fc3b7efd6864eb70dad257f87a489f00221cd36b4d6862fc2f6f8d00f6bae610deb535ccdcdc775b0958ad8737ad8e1a7e1b7b8b9f1091434156c1154e0c703c6edc23761df92277ae658596e14186cf1ad4541db620654b0f3899e09ec3a3fd847e1357c144e420876fe0f7a2610e2c8393dd8fbd6e2b094f1fe7833075d7fe2a73b1f3ce79c8f5f84c53d3e9bf6d83d6bf37b3c5071b6534cb0e6668de7a6d0bb04b054b177f57bb0998ab39d22dc6513e0db90b6ab95ec3991c237fd14789ff0b329c933fdab53b30df6d82b5c0ac7b93fb9ebf1d9984e9f5a6bedf1994f4ff8d9f32b9ede63c16ddf923d3edbcd48d066b459e97a337f5488504a47a0ad683304ddb419e204bdaad95297a0e54e33d21f6d73832174769ee2d39f1f74c012212e0cc55114213fa57b9c359e99f38fbb9f4d330ebca5a07b9782ee9c414af6d8dd81fcf9613edd212e9326c21f1facf584db7fac1c680fdaa268fb2449ff678b54f4016da5d303fd5d057e96ec393dd0cf2fc2f6f86c9beddefdec1ebbadb541d1f6abfd0fdb71f729d9436756596e10b6f7b0f80db07798a4bd9a9bbb578219e284ce969aff33c13cfd39c94b060d7dd8b9cfcb60873e88b32b61d3de0c1a08ba36267a477b336cf07de66bab43f37404ea48d55759eedc12ecc1e2c60a1b2cec040d7dd8a1cfcb60677ed050ddf1e7496087fec5f124b0e3e38303f47d34e0e34384d581efa48f0f0edca7a4cf5b1d386d20822245260b0f2e2e7450eab9f3b33485051a3498740939b33377e6ef7438f065b9430d3043ec74ec8bdb41d284f9c5cb0649df85197c134272177e0936c8d87632fc1d68716015a41d92523785bda36ffd4bf879daa1091108badafe3b90dca1b0ad09b34ad2f6dfb56083a1935b534ac81dfd1f151cdb833d7f5486b694cffc146c6767674f3bb8dc604b10203125f8b40c802f8752d409142a985fb65489002c4dca141108ba42a19c5592766695d4a854ec5a6a52a8d0d48c00000001a3170000180c088603922c0ca3246a363e1400095b864a5e443025066391401448210c032100c2000cc2000882400c00811c4f43f604e2c9a22acd41d6e453e6111736f9dbc763de3087e9cc304ca15d4982b23872b0ee37c0b40adff7d1d9f7ba59703244d52a94d8b272d939964333ee35a9a9fde57ef955ca7f6b0424514e288a1babe80e0dc022124cd76934895faec09f4547c8276b99ed3a626f105ea62d766bed37b68e21fefa567d3c6de24da90fe9aff077f5d70dd37f960fb88e8f65d7a435e978a1d06ef5ccd2eace8f380710587a7461cdd1ec9458df291798dd4b268e39befa11c7b73d220a6eeea70a1e4cabb7f67de162fa019e2eaa50b144ae2abbe2c464f6630d5641df37f3e1f9da9bb733b450d72aa7d2f37e092e2407734b7a36d6a11762b562df6385edeebb9e3f40cc493179b3e407c6b6fcba58150a35c6acaca6eaa8a896ca7d2f43079d3be6cbb564c65072b19b9b907be78b6226c8cf61c90ef6d42b81735c9c4a00e2c5fc81ab1f4fc2a14ed0a6e61d5ad97b4762108c9ce532d379b4b55fb9c3d6fe38afa74eb17aef86316d0e54e2123ef3048ccf742794fee50a4a0ce2c6f6d9017dbbaf5fa25671fd8a6d6b053e369e2463bc2f05253e22d1a8a9a16656346998b8a28ec3e746066ff0a3c77db4b8eee80346472f7ae42eaf0da61a2b18a1ff82f4297ca10766acd47d69f9b998d1113e379e2048241a39cde64d079affb442a706430051536063e0bcf9c347f88939c9ff576edabf0fb8285072cf8b6a9f2965649053ae64a1637a916a842433075e03f78c214768a79acb5069a149fc166554b2ddcbb4b072ee320120dd9491b60258ba70c2227999f1d3f93e921c17f525b584605f99a8310443f0dfd5b4697226ba33e419853fb06bf6568515f023efa580052ab6bc2076064beac0b19082828b90e3b237063f212dc00846fa7c9588b34e847c92a0054db76d0743d8016aa7fde4e790202d7d780094e7b136b7ba824a847ce7509346c08f155647ccb99e984d9206b558f4dfb5357d65c6ff424b38cfa67ebb90ba8d74874c78dba91c63451ee240edc6d2390aae4b6de8ee555bf8c3cdf36e7d8fbe1b7026b81275ec9f45298225ff58429ae2cd2262b898f1efa7d3ed2c477285d85c9aa0cdadeed2314084b43fe6399feadb07a3d61cba6407165a7171e6bbed2ded4b22afe238c699fbd79ec044d3b97778458c9aa1edf8dde8f10270b0c3ab5f3409c94fb28d80f4529087a935d64f96d924207a59b38b97ae84bbd9c32412afce933145e891d521d205a6bb6ef4d3daaf94e3e7cba7c49ea8ab75ee9cfb0dbea953e160bc5f8b9a1c4d14afb72bdf48ddd44b45e76efb6f7d3f687663108a84582413156650bba1a6d0cc77c70865501cb3d0615726c429adb4a21344e7bd89fd3342f173786376d822bb1a3d30f756391b39f42dc04d4731b3e514c40a5b22f94b88cf1e0fc7952caf01fad6eba1e7a0091c43ba37ed873e617e8323f0f8fced5e9d126a66693d9e0906102845ee71f22071068cbce4a59b81bf117b86744625e6ec1300c4779995b7214fac6517db19fdb4344a08054e0815b492476527a6b0383102ddc1a0108cb16ff4c4ae2ea2dc9d8e5200f962a5a74be25667076c448723185bb993bd1bf52020dd6984a82992f1492a3396bb2cf9d69c9d7c8e3a0f1c32dafee66fcf9dc382a4a28b78d7a3d8bb0d6ae697c73698c642362140d0fb9e04fcfb7c372d2d1799b280fb6fe3617e85ffa8e105eb1cbadd68e0b2ed578ccafb574506e9bc0d43b47514f2bdffcf593ad938c4439dee5c3d7b18b1d2b4d6a12710545cb170fc63ce4f4fc404666c12471449ed72889df0c4fdb86b2982913e20efe53a6fad5acbd02db773316688862c75a9e77841454145f9b9d2f923b9cdf52c229985958e58f96f99ff41cc5ac5841a40447d118c86a3b5ce4abebc1c941faad568889a95c6225e98c8d6bc63cf8fd5c5a3d519d376101f1f6734ecfa3f135bfa71c6ea582419b66861dfc5581f31492395c94e6901a95cb79232c300c2bd780da9ef9b48573c1a5097f26a1c651b2a5291967fde73515c395bd9eaef8f17c174fcfa45c9726d99f0ac297555b7a68b09a043bec6fdb478780d975ada98d0f2fb8102419c21de644f85da9a5720af92c6887031dfede413eee95b693aec6171664fdbbb57e2a38cf75246deb3b33d7869eb605f9b3026301357b5b1a88f698ef9d251c6cf9ae95592b25fc72ff8fd3b1d6691b363956f15569fb00742f3a21c3f2c6553d7fcdc27ab944fdb83d8c230c485b4890b94fb38da32a898d227f2df20f20f3a79df65233e28bee05befddadadde77f3475565274656d601abc78885d34a40c0d45e530a25113e0bc1045a0f07229a67083528d365e947115d784fddcaba1b70f81ebfcd53a7cd8974d0415e649cb58b2ad675c8627447772394a4758e078a5d9790bf7c1be209af9f4c9f9e5b5385cd6a6da88f85c58d169ad2d09db3250a00a754f600a928224d58bf6eabaec8626b8a50acbbf9d807e768fdbf1cd607a922e78a7992887dee32daf9993a10b3aec133d46e1db1b8f364df56581b118cfab844e5fff74a02622c420c14b358b29edd8c35bd10e7f73c18d2502406720a3d9dfa374251701fa8104d8d8fd502d68dfaaa198612e1e00beded7394bf768a88428f97f1e888a6cbee61aa83e38b96cbd9574af744175b06958aaa26f2127f1e606e6ffd84a55af86b940b59e1351756c4af75fecb4b4ecc0c78c4298094b2592792c011327ca92658785eb614859c2706cee3f574dd0279de7d55f1bbf39b0157e01164b0b488c491632c022c0ca5e3220e9b0f306fdc0bed67e11f35fda5e4102a1cbca5bfa81965da758f9a721c84e98af8ddbaebedba3d8a3e5dd40153f25eda964670c79dc70d19f64a0281aff0e65168ee4ae5e60c60b5541e5c7c0817b7463dd0608148614595f367a53b04bb7b4aa1758ce3d87861de5703ef4aa7b389a92fea463b362e838bc2f47bab1e63915cc8bc3ecd93427a8bd14a93a8a4d2a1421e86258df83782a8b5fea9bc280f7389864f4e8d2c9d0b62cd1a748080716fe73c4a8d928ca8610ceb58715a033948947a3cf161ddaee44e3b8b5635e6a28fe2f0e619f69dadf68b2e54b2af13b3621863aaf68c50915b200b4a0f73167fee58ee1098c05890fd00758274aa3dbb9673dd324aa10549d64e4e376dbe8a8d686588fced65b75979f78529ea66103421c5ddebb76587de245e7a7df6d07ea684df6aeec2df2f9297ec56beb91b140eace33b2efa5de3168ddbb1f229bf57155a341927d4b580077dad2046773e1c09a1e8f0361122ec8395b575f6f0626912d92946568a9dfa8162cc0d671dde934fc99074c25d6c9ef818c395e5a89870095b1aed98899dac2fa39e2b0420723c564d2d862090fbd28ed5d658395de6a8366d472d558a4939161b89b8a6e51ffc59b56b2785d70185fbf529246034a423bbe36767799dbd6328ea4aaa2f5769f39badb12825d068ae780fe62ea4956003d1b2579413f4b323734be2c7e58b145c2001fb41e9c8eb7b8d4eaeba3e4789cd8743ffbed29b142ac66cda4a4e055dbcb351a8c1aefadbc385c278afbbbea5e41f4d8008fa972b8f2587137ced29ae8facb6e71f8c91c0bbaec459246ef14548d91d99badc50c3e39638fad89f912665e00c83b396c845396fa16e2929af792b874998b75c995de639a14cb5f56389be0ce52c95350a1df241a3cce0f179660082b72ae52bf5cd55829c140248857d6c49d33419610b3b3ba05d32842c41287451fdf6892b18d1ee0c8a2fa41f11056be04bf6917670afbf6924cf4c4dcb99842806632d7c2a01838924c254a934825537d35538137939f3ec6b8cea1ad1b8a1d719c970aae46a2767c00057e47bc322c6c9558045a3742413fdf8bc0b076f96525fbd06af5a37e0a0e0513a5b137b795f283e1dc51c54553f08e1212a314eeda7d35e995e5cb8364e2bba6d50679d12919b7ca9158f01a9e20ef4b8bcc34256ae0a30efc60f54aed6526666eb2477e572c9896261cc2bd1bd7de5725a5d652b86303a36a14535b1e6e3085f6c042162d9a2869b4835091a281a660b20fa68832778a1dacf0ed1fc2eedcbb866e462ca67d2d8322120fcac5137fc91611e2c77889b83b2f132680bf6213e41909a33d57e318f6e8ade0a45b8d52536e954280dca7197c9b8caa9bbbd03d5f6563e2700ace77f57389061baa46ece2f59939f9e02def0b7401aa98e117f61d340a2f9996f25f9bfa9a521fab2345ba0d95993e3a004b81635edf1d172d182d5ff0aed92f88127cf5ded867eeece4ad299f17cb16e192c0316ec07398207c634534ab974586ec136dd05ddeac6c0d5e594732f903e14d8cdf92dc3c32c0ee0c8c23fe6469a979ba321ad4b963dbc5ae3d61fb980e5a8329b701dba74fd3bd29f97e84c423161d2eaef620b0cdb3dc515a518305d4dfb9731fb114cf24951ac115251ffd4515dd1903445d7b9317ed033aaac88e8fa59a464f4026539b96c4bfba788e8b7b468c2f80c9abfade9d784e0d56acd694dd5a413812b0a17389b0a58786f9525ee196a8a444df3de928a47e97cf015d3e5552468e7f32de840f2c075d6c10adef04e0d2550a41e937ff84db567779fe49ff4dda836a25de819b6622a811366d6a716a2a621ec1b6bbf49385a90c4d6301bcea1a59fd2228ac854c62b6bc8955dba8ec29ba46f44a9dea523343d4405c8a9c6662697b96106e128d0cc9a5f77ca423b2c3cfa24406f31a1175b2dea81a5a11d29c36f13ff3a09e97d2a3c82fda68067f3f752644571df64fe8d3903f83a75737bfdb3826e18d86a3d8c502fd0799b3e6150ad5b6ba0e9f2bc32a6842c27dfee9d6b92842c5933f245a986f556029fde4ed2aa0e11138ddd075cb12c65a53f4d1600e214075513a5e9661789a2db156385c3dfd3e3c3840b249ba3fb40b9fc01499965c0cb31cd8456c03bb25cbc60940152051706e237a682a7329c37075933cb73b418d0c9717e2efd4b749dc5dab5260945660f33f99ed350a21157eafca1906d210816a72224b20047c3e9404b929022f15c44fd90349aeecc5ac0a5bc8d40fa20b80969ab29653d713abb33210aba282a309f285abd37cee63653464dfed9cae5938a05084814deb448082f72fd324849adc514dba0850c22d296673d105e059f4a900ae3dbb38a1d192e4011c68712af4a8521b895518f51a0e7a9cfe7e9bd5153b1d32be3180b99ec190edd6f5457f512989d0113359b05aacf22c47e19554ea11c4ce52ec6f098d0091e893ea364ac25e39ecfe2d91e1c49540eb81f48d3ef99bb9c13354fc67a6ab7a9a093b953fe4429063715ec7ac873d62093f496e6f108b5ba88329c238e559f7c6f286b0fa98f9486eb9aaa42440799659f12c489e633ef3f05db29f39c0341c1d02ba06a4bd84c1da41017782ec864156771172ac7c11329430c9822f271871c6eebcd50035a9d8902427a74f60660e8adf3bc80e7b7e0a770cf2504825e87413fdb494e5fa709a9b2c5139e0311615113b35da33d7a145272fef349129c79c43278f5de1a0b2eba21db274d32fcba6c367f3804781ce1eb97b9ce10e74d32d69cb43824a376aac7922e1a49525f8a2763024ec5826b6f63589b6d3a3e32a96a357682edb246bf6b320b3d9805e4af4dcc90ad2b0dab2e7c406c00ccd50062a2e4cd104854c0c2dc58d4bc332e2a269244af21e22f918cbd7889286e88feb3ab70e09155649c7752c29322976befd2c50ab95764e1be4640246404bc7f257f68fd03022e493b65d0b3201df08c4eae720592405b558950689ba259c261d1bbe8aace17e43eb1fd5ba5078c9796f135c1214de151dd85b91a2b6f584e8ea3635c0aa0210d6c130afd6a9e9576739a189948899099e27fbb05d82392cbe5181753510054d44d231be9cfcf6288b25d5e8d854549acf32cca2533ed0f9d7f30b926b2d2e80a276230adf4b38c13a4e3b522784d3c5ec8e9560a984ae6952d95a9d9018558e890e04247191c0b156570e41a4dd91f9a820f456e685880cce82e3837a4611894ff2ae8c3677181e1ca03eda9708ce2bd6ca74934e5afed4c2975ac792f4ad1fdd14e6ffb9454b8ac7e12eb70e5363c7fcf6fb095e761dfe70edb5bd386fb877fd259c5d49950b9f1cb359263c2761decada3c02153aa5d34a00063d7d0f0a38180f8a486fca5a93eca69d3fe77111a63bd5ee26291c5e6cc7370ccbbc8d61bf4e121acd2664769a6dbc8417f2d14bebc1bd511f4b0af919633510117620472a5ccc32996176131dbb1fe68d3d03e1d25602ecd205c62c1592a9b5de5fdb6fc1d46c16015cdce2c1004ab254c712e18446f8d7cadf67b23c7814d5a64948b1837d5d095c999f92e2929f9711646f8dcaa11d08cc7c8b7af882c472162cfe7ac865251ee260edc70191d2065d1a219f4c6624997afabf3881d81ce393300c4c1845d75f9c540117873ca82f05c5482e700d3228ed44a13b1f52b5df3c650d2edb48aec885cb56d26ae2b1626d80fb31105accc1605cbb9fca84278d4667a7a4ae20c62c424bad625f6e2df7ad9a42297ad8de6decc14546a891be0fd7d6b2b0546d7e6be760cb88de0fa6abb6f7946251796bc2a6407cecdb701090273c053257e5db182240b2fe603064afc3b7c0352a7b23066c74f991e4e4ded032c550bd4604813a7cadd974fc2097e222b94c2fb02c3a1aa4f9890fbcaef9816620da845e43a334d622df7b4f7c4185c1211b5c4ce4d00cbb60bc29ad817628b4c248a6e73a5a0f3e7ee1addd485abb2b01c3239ea141a3a8e5a061a9398061cf1f595cd8b0c2ec76b33c69c44a914d54c0faee18811019797e98a6735932f5574b78801ac1446aa165289bf41f5471f82b1fc3b35a8d88c5a8adf846cde454def34fb6b16e76edd7a65b536f4c7e389665043396e596adc90331db7b6a18f1f96757ef2e26db0945e543e15050d50702218dcc14e5250e56379d50966bdbcc5217660d2ffaea9687dea23624209e30fa0da85185ad0a612b3460b03e9cc413dd755076f9ca1fd78acbb440ca86ca825f8e433898e090c41f08e015d5d56ce3b8c40661d952d9c4d860aa6b611317686cb41e7f469e1365e2911ffdb04b58eb346f1fb4d6ff11039a09db63edaca309f490a5e4699fb99d3e8d15328d6fe91ab477139ea261f257e17bd8d0cc28ff056c395b205059fc8c116155484aa303ba4e3f8817be737d093aadd9da57f1a7f00e61b56127bb5fb798cf2da44307d7cc5654d6f33989449c30c1dab9d281d6a5138684914d225a0430df7a839d2d480c730a88ef9d85a24b712582f23bda8ddf940c0913c86afdd0fb935f887ed247385d7f02c069ebf2950cc4dab773db649e6e3bda5887430c984ecc23af480b03da869377b8e7e9c86bf4574cf9b24efb5a952d9a34061387450981b9db6227bf77b55828b8c727d40837abecc7f09a034058e10dd2cc709540f8eee321052c040481050e82a8efdec76a14a1135a08dc6f6521735d61799d8866719c9390b39946209eb9ba505667a5c715384dda5779ea0affdbdfedd662f62b7bea246df71893bd62d361a9963552df0440a963c630c8b5f895f0f1c97b591a0b7297bf25c80a90bf579f178489d2d2063ce94d7ee460547f144a0b0507bf445ea18feafffb90fb23c435e121a32ba28d294bf1bb0df7e8b376d3c0ea6276ac1cfd4371e4ab1ba27ec89c5a4d8aaf7ab1beaf48da0845f88fbc66bb7c40fa290203be6d2351fa24be44922617c17f46b5c945e3063c685ecfeac2dcc0a43926900c6bd225612c76d6635ac6c2782fafca645c1b906c22d52d55953689e1fd30ceb02da4b449b2151e31e99e3750df22095a993e59331f7c0f44c861e4de3441e7548b209b540e4cab86e085a214e35c4a2d8cc18c6dfc666a68d4cb2f7f475759b6b61b44a4aa4e8ec17b28be73e3b8867ed2b111fc75e6f12fbff9f2e51674d14190e427d835675457c6175b1598d85f2dbce1af37e993f908647551a1a3c228b34acadd5d76388e6e3559385f68ce21f0238b48a8e0a275ab11361bb12fded03a24c16c876a00682cefec4bf650bdbaf89ecac023f3d6daba1181d66915d295093cca80798603a816223d6852d5b7094e27e17e4888d828930689d9d6f1faacef51fb833b84b7dd450266a5ac5f63b9777c50b5745aa008b2e66da0a6bc9426fd5fcc64e13cb92dddbce352fb87c17e747a7ed036a5a98e015614d9fd744e08c8d949397462dc36aaa2009e96979d78b5574a0fb8d1676948f89637b699b56e1716a1970197463bc3727042edb80ddf12787142a7b92fb8f34185d010388e449cc7311c39dec25e6806c560e88b019eea58f7a02fea932edcf931a6ffe1ac7c0ccb59c93d50fc9c95dc444112e20a5db5c628d4e732af6f7a90e1db9e627a7380eb0d72d8ad69517fe9b15bf00b77e8a3a8609d8b60d664895d6df9c5b4b68eb06cc4dd4514c73e5e6778a105506a6f6e2294274b24e0e82c19cb3f0e44c716c6d8a4b6cf51e6b59b3543652946750a60ae479d026d5b3c8e39ef6282ed7ba2473d97a83c30f384a83f1860da720272fa55d6c577961c70e8514d09cb525c42a09d9d14dc99c27611ade68d89639b76cb25f216fa74dd5360c3c53464351dfb181c2150ff1f11d2ec26d35284ac391c0c6f573d3a35b26f8443416702693bf9aa9e767a2183e611a6743e0e45cb8afcfa42267b4439a75d4e7fc869c1a4444b30d4201454c110046fb8c99ed3b13aed64019038014cec8fba0c8c677fc617989854a63ab118f734ba50a5520ec5b1c9fc27c79985a65fd68bce015911e1fe9f4a635b289ef5c67cdef5294b7a21eb9a8643370b89ba0ce0db715af40566a295c3136c8984f0f1f51e7f1e25317708f1a2d7261f3fd397880433c8678b0832b6d022a72540662bf8322931ce3c2bb78713e06551abe6521d8261ef2ab8023a0b820897186e65a51f45d04feabeabf57085f0fcde994b4cc89d151196ceef688af8e5660045d94ea6f9f23463f778b765ff0a3380f94c1d6c024483a59858e0e4da9c0183ac0c46611e4080d2f7342210af5be49ff0f1093588884785de67df557de9a36fa9d26244d0aa8a04d8042865a715418072fb251533fa301c5b7bf18f4ee387708f5982f562e407bc6d930980f460b094f78a5afb086ea077a7427cb1b5d8e02860690fe7ac0c2631a10311452f3f904468e3a45dc35e478ba83a0da6e678e9727196dfc555431d3b4ae93067442a60c928aedfd1bdb57e6ab7db8266434a22d3294123aa616bcb7b877849a2618ae5ad68c1890746aed15221882d4cb7a26ca6db1c7b3aa6bcffc5aa99172ac0a57ad8ad1c350a6fc5799b2e135b1550a22d041420e92bed10f89dc77c9dda8ac04587d599094fa4238fca5f4e0f99665db88af8d20ab610ef4806ad8537f76c97b19ad8d9e283b03fe2f3e5635bf1cddd25a5d7a05fb4fb20289abb3f4a3d50814bd52c23a3099aed85c166064722a0d8a170510421fea99402f5a68560146d0253ef44ac807d2f1641628847b2727596a668fb6e46af0e8f6aa359b0ae9aa3bc9a1fcdb0ed72489bda7fd327ba88c0638dde2eb0a8ed4b6f12f762d7a4fd42646060a51c6a6361583179c9c2e39a073bd4b6f58041da8416c2cfc3419a3aa001a7be5bf4c3a69116ff8b18d12db12ef19dd8d58285f174c0c2386cfaba3e8debc87e620ee538b7df7f6f00074e9d55c3fc1b7af46ae5ca9147ab8dd48263445d00c885c9636884818185c789a0e2a2ba133a650a06c46bdeba4ff9d56eb44308e4d73e99e00452cfcac6fbd1162705a96633699e358ed80a94c69482209c7816b5f27455f2ab73c9a1d3fd18d3deb2ba6d681dc46627142b39780d535cf1f5afd7c61d7d6c9bf42641bb917cb60d8153a4f65dc4d6975ce7bd9d292d574d564f7d4e7f4e68d0c15af190412d5eda8075b43d3d030da01caee753dd6ad6ba3ee08c7599a3c696c5e6a9b414a7d590420210e1c7ad41c217eb8706cea47383d5dd9e0bb07ca57429c43b640ae68b45fe06d594797bc77a6f4d95e723771114d832075b00eb7047d8dbcf501610893bbba9d5049f1c9625c127809c5087ae689f1cdc64c70487ec62a92fd250f842edec54754a89ccb9f4b96820dc2335715cd766223f2f4b5500fb1cdec1c434442257acbaac1ac8a8708b513f338fdfb96cb73d453212c5fb1446b9a17b0a13fe4f13904efcde23de18ef237f10384e10e14d96b566ecd52b4aabcd03fbc7e25316a9b6d8809b8c272380d067afdd662eca537ed37a3a34c7ef2e4da8358c9536b93ab05dec6d218e96d33526d5cd98d0edd72f058ecb27bc02847a8d200619ebb03434b4abec4ab6d7f594e26ecac15998dd6a366dbd4bba230e761247a7f7fa09e0e7c0c2c4bb0a224725db5ff205c01768f03d3a67f0d9c25b059c522fedced944ff432459b148b1107d7f4d4be0a4c791901cd33b942f8982a5de3519b95d743aaa9a65042ff62c0b6b1556ceb4f08e2e60da449876634c9786e0bffe4bc2a2b3d8b356bcd1b5487c17edb057684fc52e9bc1fab237ee7b429457348a383ff665979e5f2cdb63f2cc1f0c79fc3b5ff5d810632c94c3057af05ae1b21522c8d7e117ef6269990f8f99bd6a8355a6a9965a636307cf1c9cf8ec4ebd881c3e80f251c81090698ac4b1b1c48d57aa5f10401b3df701c014ed3e5e333d16e88a8491d5b8c07ff92baff774c3ef59dcc948780aa2053e608e35173680a721aa42f5b84e2ca0d985b7ed470482c65e5beede00caf525732e73dae281c6bcf3220432eb895e067f2945b706893c74710d82f9f0304142418ce53b0eafe417cb2daa631bd4707f2d74c1eaf412a7a2b88e44411c77fa0f35fe5d1da9ff13a5add92a96b630cb1625700347a07c50d2b676d317c9399d55a0adc9b3e013ef09128e0e75d7a0dd3a152cb8a78a4dd1c6b04221e9a52f8ab6e4741fc7255474614c6701214b9dd74d3f8e395bf48530d8475f551955ba15e0f2d53a35199660e30f21bf671f79d640e973344b7d9e83f93e158fa286680a78940d07415bf7c0725d94634d308223ac345dc259ad367cc4d765ce6afdec0b5684aea1e790e94658327c2b419cc234aea14fc2c12d4488032b44cc396d6c9d2a87ac8077e3552ea60724b51f350bcced4b22a10046f61401d31a2ab443ec25d27b9ca3c57e3d845eb443cae2f6676964b960c1b9d92513adc26c970ce28ae400d94c5a01d0df68198d69a2fc63d26dc72786468d6d0c44e019fdbb8af3ecc839287f8da1e20a4a6904b5c60ca47c2b84f8325475f7e337d1f2462d532b4717a256c1b08d527e74d92e21f57cb59a440916cb82a33448671ed6b9069992f6dcb4cdd5332c7d044e77cb678403ac2a55e3944a47448e8dda33aa681e5b54330881794b52d66b57b673cdcff32d855f7746b165c0906554df50c012b85be5ca91a365005050e17940658bc68207534b603bca17e9db41a793bb4e0d688e140a688a52131e6e212f57ca6977cfcabf81be904d4b451d55d99767e66b94168dc3ee045696f099d1e15d76ac8cf8e58e6021af662f7f15338821988641138002d6ec08cd8a8ae08b7e6afd1e995d28aef6d21c862603564719f3605e53aea2d5119520e5d0f3e067011013e5b1e69def991ba61aa1c79b3bc661355069f4f241df568a62f371a4763d6f4e43403b8bfe32074e4eda4a08703f130c929deb74d36e398a30ead9760eb0cb429873b28c62c532dd44523b09a2d6c1bbc8d3dc7a8d7c834addc8ded571ca8b7bb203d1c2aa6521766b445e55c9e09dc12d1ee21509eb2dcd5bfaf3081a050fa39aebfc7c183a1c8931c0545edfa33086d84a1dde12f511fd22556227a2b2fe691fbbf0dd1b4c6dd50388d29dd3cfbf174c21b126603f59017c14dc2442c99b8a7305cffcd79103c26c46ae552c40f1bc272c81a2de41c68a64e9ea365a7ad427fab746a0f789e809cc10e88b6d50efaa996511fe0cdaacfdc94344fbcf0f55456803f93aef1ad0d9e59da9ca90db447a438cda4f7c258f867629f3dddcb5b81aabcad06982332d2f132e92651b428c52a0d6321a6898552244c2165cc732f2e6241cbd0dadc627a04ab60a3226fe6c233bfd6b03f9e14a5f0473ede0ec65abf00a1959e516d6b93d51d765558dd0e489a95bfce4308b053c5e737082b5feebeaeae3128d77be8ff9fe188c5ae5fe295bec1905080676ae08cc6c4edba5ba8c2192cf85cae8896f0e7e0d73843a215ed5dc449d30875d751f8424d79c6e96873f406e1031d01e0602295e063d5b0ff92db337089e765c42d833367d0dcd1d2c3666555a7cc6d3bfe739e1cfccf5cec4239ae4b3bc47ec71d8899ee6f8b976317f8e383da317df0cda59519ca95534c7e6d9ce0e11e95e36dfdf30e9b6444cc1b3b893d82892da9508ae17032e921d7e308d8523ff08a68c950bc2880c07074b5af29b5aff0452accbb7f0f4bcaf6572a5817f56edb5579511d164300e3001507036992b8189146db28b361167934cfc9d6d51e656782c602d0392ae3922ba98a886991b44635eda2a09b6077e9eb3f323b5892e3e96889ef5e3dde36917d38d9615549836680b93f16dd6e8cbd6f5656fcf6303c4ad70b220f23349ec8c8adc9374e2ed40bb6d3ffcda0d0e8f8fbae4cecc85b8c4c267b6ae3a703b434b43024dfbab4ff7db3bd261b0c01947498171cff505ac360351e7258b0cecfac810ab662b80cca1ff43d0a19ee59ea1e7d3914beaa1772bab908ace9e8a9e14017ae08cd33830283d302fe82d0a7c9065c64c66e6c50a1d9a124e82d7e2bcb577ba5abc41657edcf79d54caac68efd28fd190a1fccc530057a734d271a85d9a6a5ed4a2c399f5043e5b95559a148c9fd8423294b79a22eb178df180daeb101cdf66a36af245d73faa86df4b1935e6e71179989187e212993b22c2f687c4edd7c526eaae5195a6112d2c43a5429ac7d71bde0522a97176c3c905b5b7ec4afac0197965f64096fd3a0beb759187d9d34af79aa7e017fadc9f1d2bc34c8f7d0dcb3cdbec02a79a0ee33e9200d44b024c1523e90a5cbf62b76cf04604546d8671d1b9d17ba3449f85ed9c055ab6abeb4810c20c4a6fecac316ceedb171fcb44512538305271c926eac0e9481c7386ed71b717c8734a2041810e1e4db6ed2ce796a47019f4d88203d03e981b7bb39a06288b697a13aa5e3d636de3d015bdf72c86560bacbdf24b696bf247a58fc1712747a52c7fee02dde84beb25c84de381ac5eb55084d8b24da1890e825e0dffbfd4a569b5e8b92361e74c61d3143b43a10c600a02232450e75d145b1955a866815e8fe68ddba65743d91ba32f59d8218b4e3cfda428b83de0f239b0431dbf96509ab3de718f3aee5e44705f7006a1bcbf83c6bcc4916bc8dd8baa8f672db6187b978e5c9e5cba30693f5d99f4983e96e72165936e2464c2bfeb5ca12724c16be991ba9d2f2861557be143d3bc8f5b4a1a9c0823def944e7e364fee60212282bd6bfc53bc7d4df078a71cd1749394d09bde18ab226267b50962dccda8b6792693e1a031a97d2acf26f389bb33539ebab653c0b9c9258b3615901b90b744f65d2fd1563428bec53063821bd46c6c114706f04270a71cd914e3daf2dd8b1e4d17f638bd483bbdea237e26f1fcc34d9587968d7a4f365266769e3d22ac5a8f3ec48ea6009adb13c27752aee198357d8d4035076865fa28c820fa92853c25910a8137d1ef2421d33b89a3eb68c1c0633e860fd9ba38d114fb2c6dacdee8f58349fa0b83b7406bd9e21311274ab873c81be28dc4b2429b7c87ce7035000f735e74c8c02efb5eec3a3500593d3e9fcce34856ac903921d9143217b5de9ebbbc10583f18bf23eb21b3fd983209f883b631f08995623fa97fc4c4857634a98c68fbfb7479e8c277183a35e6ec7134d87a01f1f9690d2fd67808261588a57684e44614dad7216ce5e0dff5f492aa1638e8608178c578b7381dc469f2942f7172564027d37285df945c51a3f026d0472d314661a42cac068e1467f511252b417e648eba3867be710a2a02b22becb12881d597cfc1d740e905c9ea11ede5b5b4d7782de6e817fcd001931b230f1e22b89dfae595d103ac460e8a40c87dd01227ab25a68c18e8ee83469d9e11c1017636962621187db3e5a26ed3f2b7413cb614bbd218b1ffd63c4157ca1bff577c22612dab686c672155268881db6c814bbc9198973f842641311dd70a6631f929eea18a18a666ff9f838783ec292820584d2e04c69819cc21c5fb4c6475562763721ebaa46b66ac147794887ac0d2278be43759f31750c425b843e9a7b1f5aae183f78902a46689c183e33e964ad4e48f5578323f8031ceb38527991590c0b606f55bf0b6a75e3727d10172d7329106f0403db98d1e2e7d1910bf03d11866d6cd9fe01ab21424391365a439cdea2f8610dca35da984805499562ddc754360e00b0898305ced379935d0d2b4738c2a021e6a85dceee7c724426c2918c9646b68ca6426e7c763b33b8c2b889a1c3bbce5e15c20ef1168f93e17f1ec02d1a7911949c1430227298644a6ffd3fc8093c69b57c6550a44557f0d38f806a42d7c7f93d4035f45b49b1ccabb0bae4232f0ba214c499ae6c64a69558f7d948748d3373b903199a1183a9e6e4929751f8bced389f9555600d2350ce5c74a8be6c071c6f723808472bdf34bf43ea44d12da0b10ca4468be0c28ed05fa9124116759b3e234aa97f43789caeb14c19fc9ab3299da8f6924556118898b2ebddc16940fd59cc0e5811e88d9445c60f034a70c90118e9d7b6a7568fa8250daa37e8f7084e546843e2d5d3213c574043f873d4e6a050c4e08881201145b23caf16fa5d3d4c8e2ff5aeb23d450a7b50c466ce4f4ea155873b21cbb491a52204661e88c9acafc2123100494ce8b963aaf037b915d360ae65fefb43b01994293fad638ffa21e51996b02848b61272a8cb61a19d04d1140687ca61df999568ca6690123490b2751618b8e1c020a32084425b293d2f9f6a03fc4e18ca1c6d0ad1f80b396aa92d9c6f638a771095e850f8e4704dcd1fd285e18f32420b56be104e74fc05e105017c5287355711f3399965ea46024a99f7607d51aa224cb9b35ddb624bffb1d0ea2b29f02845a3ad3ac3faebb6c51bee21664a388d9e046b97ed163bc2944fdf3d43b6594622cb38c26eb204647c88ba750ec9db5b48cb34c20c88b2cbd5128d80d9503d2d9f0ade84eb5944676c1a226b5e202db4d383e686127e75db6e3c24af27892f09da937c01e2df6a36ab18fcb602754fd20b41ff7c6d435f082cd9ff655f0b3ccf601657014b21ac00b9453c11eeb87085211e2587f4c3d7f89100fabda9b87da0abfbf9f51daaf694effd136def48fcad14c83a3995daa7f70e3908e24a64b863542b122547e7a3e10bc94b869f191c9874f8b265e355c0d047c1e4025e1100da02e5cb406b9e77b02a7128481ad5541e1b055d55c985b2d2a3e74859a8bbc2d5b28ff8191c005baa283ac50b7cf828f9e890b5282a8133c5988cfe85ecc57c6027106f120cb84ccd9aa6f706dd05cc1aa64696e009752f62262edf622f36caf5217e510db6ea75c02594c76674ae34e386fcb94ccf9e2d9af1b4b8865763669553e10b6ffc19fc3ebb8c2f99b37cbc65e2aeaf44bc1c99abaeeace0b09cffa5986e95a1c266b62213a5ac590496120bc204bd3794851646b17a1fe7a1cf69f32bfa0240d0822611dffe4fea293643a5d238eb0e6753bf14b0674621ef04fe7ba414477680a550f242f96ea18bdc892b6fafd6a215bb12d34a8cbf77ff7b98cb7412a414d608650065616b04d200d20de550a6536ac34467d9a71863f823a1e12496f73dc27be7e326cf85615923ec9226ee874c8fbf095432d7abd533a62b00480cbe9810e606b5a699785595b341d5e557c20aa5072e67d68c8476f6a597526fd0110398d54549cafc9222fed693ffee4e1f6eaf82d6bd78d8761e248c93e7398eb2504f5599226568a97ab84104b756762f7bbf17eaea0494472d52cd0f62318fa0588d97e47313f490fff4258cd6ae070e969b369a6faeb9d1209967dc576ee6b6a3f8547338781ff8111e69d22bc0355f52b99e1ddf6172481c2227671d8148e74cab8d427a067e839de1d3ea1e4c6df5d73cf724f7a5169df12607f03a6f6457cd4ef29e1a9303b8b40c965267d83a99fadfc9c5801a3e9275f7cd34a682eaf475b9946c64efdadf5e847e792c2c8a0777c04c96a024ea770e499b79c25d56c8851960e0790b74217c690017a0ed0dc3f04ce9c54074640d37a28993d552be9f2b65b436a7e5788e2e095c63fe8de6652c0ae4a9e0e4e730d8a7f440ce0d5169582053dfa0a67d0c4f481280db045d67eb1b07a7ee412de1dacecb3e24a21dbf53d92911a6d8bedafb4bbd5ce1e6215c948c3ee29bb907ac56fee148557249e2ac265d02e1a1caf65d1c729e42a444f700574d0156c50870963877edc7d9fda68bc3e929f0d45a281744e45d94b66755d92df8dac9d9b218f51fb633d0421ab2680f9da9282c9e5fc0d0536c2b236b5fd23a95aa72b7431a3c282e43c9a4cfa89730e4ca4db92836757c31e7a2db8a5c237ac4799521bf14937ec31b907ce754b719a02543d0428ea312f32f1960d3b0779d72e516719b73981a8890a08ee2fd7498db40e31edc67e590246de536081dd228fc586e0c83c0bfbd356bbcbeeae0c1e10b6646299800c77ce5c649ccf6fb88b0fc2d7ee948b576d9d464639e2fa88206d0787b59b61c242416176855e47ac0e2770026bd10ff08ad6e6e6674d7e5000e6dd15f05eba9968649db466b9587e5d4fd45a94a352b0b18e0e1ab9db02da79db08bef9cb00b6bdfc42d68b3d41769356c3355c7bf4c18585ebba659b9659c14130876df767588d802eadfa20eec1b92eb7cd782730a2b9c6a6b1c40588a163c3f2a2c78609a466523ddf1a164a87c205f15fe55e369fd1d9ff6b96f94848ea8179184d9f5c450aaf86319f073d15f5872912b0fe0f763b5cf8d056cb6021ade49625b304eca4a07ba1b9f173133c0a6eddb6f673c2f2e74b0be8adca1c56d5bc303966a70d39adbefebdf9ff674deb02184c1a64e28802ad35045c46889ef1e8e081b6f9f7ba7c951d0180afb5c1c8bc7fc684b42f6de9bec2d659249a6c305b9059b064b393c2fda02c68fdfe59fa0e9e2fd9d6db4781785fc3f19fde29f2d9fdc8ffead7fb58fd0fc97cbe572b9441eaebfd116067b71be9fbf170fe962eb5b32461741e0cd6fbd87e1e83fffc674cff5f25b7fa383260c71dce60bd13e15c71777e4a79c7719fdf2ff298e3cd71f20aff4f70fccbbd409f1fec71090f75d08026f7cf1db6fef79de0f1902f2c421aadf4491c7bbc4a5ee5f7cb8e47d0bef693806dd96a8e2c4aeebbaaeebbaaeeb9efb212ad1ce1e7eef8770a28b84f9e389b0d9d3afdab6cdda9c50decbe2a0eb99f3aa8f839c7710d62f16072e4e600848f3d13dfd38a00d7bd0cb124940b35ab1144af7038a4991e453da876352185177e7ae0e519a03a4e3ee74fa83eeb80336ee805d4eec56bd4ad5fc77525f629162589489b95122068595d81926aba4ebbae3bed284fbdb1aecc8bafed6e79c2cb6315fab5ad56c09ecf857d3ac0c9efab4c2da3871a1cf27b0b6e572771723add8d15eb72d97534a7d90b5566b831d5db7ba769ea756d6a4d4647dda345fe2b4afaf89429aa6d16ddbb66ddbb66d53a91a9603a5b6c98eff3c2f09e38dad958a42b026ce20dd019ce921ea89a6cbcf586eb09eb30e217439d80b742e5a1ee8bb6ccb3ba1edec699bc58eacdb6c43e21080ae03be528a3e58f9ac813751306efc591358f720f084d2e5b91c339354b50b60bcb1a21015978c1e88da1d6cbfa4a1631e2eeda3bd1062bbc74bc66c221afdc3a1c089e3dfeeb7ce013b2eee74f899a9f9f5c225d673e192d103b1de431e28105d6d3e6db4feece02ff7eed7fd9b1375b8a7a2d71f276edb7f5ff8bd87f3296a96996a888e90a6345dd9f245cacff85451fb745854c573ff74d47771146fb70ae56d3347d77bd5a7636ae9574b5c5926f67e1cf4e8802ff7abb0efd7bdd471c296f6f17ebe278eada38a53cbb885b2056e6ebfe600c985d30bcf98af1279c8b9a55f2c295df97fe9d709d7af8bf38967b060c789054bfb68699f245e6f2670d6ac94a5c652fafcaa2dfbbae8330b1a34513e5a0c9a0d7c83afdba7b863830b1a6989723fc904735919afdac106816407ecd65aeb0d5b95f4bf2b9354460623efe2f37c976365dadcf161d4681b37b81c233375c1cb3132462e0b0a4aa9d33246ab322d1071d61333b4ad5819262e77395606ea8ef64a17827506471b619981cdc9320383912192524a29a5f478463b81c60933978502dbef97634e3871f93be684d47549361d6bc9958233b77fc7b501fe6ee2f6db66e28e5e0bf4726c89332bd6c0dd3f833bd6a18a8203b8895bd9436d44e1d68dc79c37a001a2221117916466703bc91f0532b8b3c174ac25195f90aef7f43b2c41de568942f255ddc3c7d67d7f38bce7b8db891e705d1fae2b45e67a0c398cbff7c5e87aaffaaea32ae619fde9f057bdf737ba03edbb9761f7aad0fbf9e9d85eca16e577e1d8dce643caf727c0f5af1f9b3dddfa5a90085edc4c20984efde7798db081d911055a81d41d5bb4fe8b296f3f7304d1d1f541cc08a74b9f46ff3810855dca5f5a5709ba7dbffc2040c2d5bef97dd4dbcfd33e3ce49b1efd3eaa0e0c5ced37ed9b359159068b3854df07433e54ae3eff8dd6bec38d85c6ea8126eae8e8d7be9fe57738bf93fdfc3dbf451dae9dcecb975b43da9240eeb87d2c054eb66b7d1d6c3f9ef0467e15c70672b7efb8896aedb9a9e956df33f3eff0f63c74b9f0fb2d1ceaba4e677bd57f4f6aaeff9744d1f55f42bb6e48aa54aaee3bd93d8f793bbeabee857474b8c6d274682c6dfb4ea5e2441cdbab5ea5c5ca7521fa2aefb7505ed5fc70701cc78942f447ff12866e0943d77ba1fafe72acef892a5ac3d1671361d6400215ad9e08b306f525d08dc9e3c7f570fe2a6420e73175a844a1effd67edbe8ae3f79da8d3dde84fd4e9441ddbd7dfbed6907b4d68db341d9bb6a9c4b17ee7bd8eaa863a2a1107fdee3bbe5ea65cffeebbf684b81feb7b9ed71f8ece83aefbf92a715475e2c87dfdeeabbcf2ca4b471a1bac6043d26be1e9ca3c13e584441724a8fe50875259431dfaf26aaf02fd33b8f32b7dd9b56b57edbb6e4401bd73448bf944b4593a35e1345d6981f68105893042fbb42fe19fa1245120a961062d2588a209a01f0f8a13582185133653d200f5cb27fe197ac284992c474b3d64410450bf84e21f0d40910226054968f0e283d28581821c9a73474dc73ceef5e5d6dd218be336e7147978a452166a46e01f66e629c27863e74b246aed6477c18e2c3502110fdd48e36270b905ffb00ded7d3615d7928c00e7fc3b6c41b945c7d7a573bbdabf3ed17b3d7541b4c59d7414ba2eb678c6b4fce2d77c55b8b1f36be2685d6f91c78f2510d0684b9db38daede3dfef2e3a0687b16bba31ad0d742660dc69c4b9fd9001f1f803ecb0aebfd62db825580d10355b18b9dffb27b4dc7dd1151672c376198031df7a454cadd9d524a67b4a4ee9342a19973d2af15ec97f31bea4e69a54c9999d97d0a31e5d6d6ca76cb9d610f79b5d7348e69f9b285df26c5d6faeed65ad75490529620ef645633e72dc2a7e6cedb56bd65b9d05a69ddbcfa0b53b556ad52d653d5344d6a539b1b534d73e972d61e761c8cabf69c5c41f76a75f59db5bb7bce5a679dd2322f7515746f3f43d5f706fac575fdbed55a35773a5d6efdf2a2e2ba4ee5ec9b37bbaed9e86b9ab669daacde9473d6ee6e4dab92ce3a9b74054e772ab9bba5942e178e24fab54e50847e693f7f735d605bb56aa1bbd74a9dbacb77714e3696c8325366596528e37a529ab25d82dcafee769f3dbbdf59b2649692d99d52eaf37bca6ea9654e6dc8356d03a97fbb2b31f7a7699abb6b73ce29a5bba6514debbaf9ac41a8737577d002fde2a8f69e47a94b4a29bb900bed94b5b26a73fe3c60f3358ba929a232ae27254a37073be0db4f7f52965f651f7d6b9d3125adee6c925c8eb1c17247179b26364497636c3cc6e649d2ea5b75093a15dd9170266772a6f9d34dee347f4468828272262867eaa6168bc5face86b1633755697553abd56a75934fcbbba9a1dcdd553e37f08f8d71a5f8add0a9f4cb9bb65cfa31dc9d890496fb35f5a35ff44d70a4d9c336f847724f8ca7acbad2317e6c758c1f9b4b3755efbf6f80f1b4b71d6c964b61fcd841dc3174eca84bff5b3160c48811e39dba296c2b542cf7f563d07fb18fda674e6a02b52e57bfaee4291827572f572e63bc94224804d4a74f01b6e14fbf02ecc3853fda9be3ba39dc233fe7b970f24b7e0db718577ef842c60861bcd9bc292cdfed55b5db3cf975b0bd5451e19a9898baa91baaaf3497fe42b5dca0a00e871095cac4b4a6e85bb134d6aa655fc1a2a59b3ef4ad5fdcd75048fb1ef2c6f8a00fd4a8595169e271b13cc93a9223d92996c7e54c4cf57f422c5b9edc4afb7c5a2efdd1b95cea4e975ea92ceb4cce44a589c7e59c3f0fd67511c68fddb4bd8b1f903882fa45ff837e69620c3f1a33cbcc1fd6d316efedb3cd9f568a41f4752eced891b73852d0079e8444a515975f4e1c4f9868fa45a75a4b63e9a6d9433f86d24e38cabbf3f5c3a1dd2a4e37fda2cf9ab2f23178af5f2fdef5ddc436240651a00b97eb6f3400c21f92a89be68f0850dd04d54d50267413144ff50e4eddd456dae9ee847c75c20b4218e112eb5d21f7ab7311f69077ecd16b8539610fed8e0c457140d1ce1f96b8c55a6badb52e05a2208738cfe08625312b8a2c09ec0cb1354b626ac6c4d24cc5d28cb161b4ef64eda10a35f7ab387359cd652ad45c6608f08c53ea6e2acd279d4d3eb13437cdd7d5de82b153fa356537f9490b35ab945c81848292621b3a5453aac6d0d9a5589a2f445461c5112333474ea4c8a206204e736d09bed47d87bfb1bc55d775abaeebbaaeeb3aeb596fbef8d5ea85b52fb68de3fa7a7f2f3f8edbb81bd3b3bf6d9cf801189bb52fb8ed5fbcd066bbb79dddbaeded26f2e0baaeebbaaeeb5ebc7df142ecb7d65a6b6dd759ebb158615115a1fa5fff579f0b2f30b2decb3777e47ef5df77619124fa7ee510e0fe13474f1cb7a5eef95b327aa04e44010654a5bfbbfb876e73df374685536c8a2f4c5b8e13a5189aa6cb31299c625230d92adc4cf6890d6393926c920d63c3d8273629360592fd52ed129618356adcd83e6efeb43ef0881d5fb7a8caf633c727b238feed72f0f5c41d352ef7f3d3f1230825539aee0e86ba9c6fac457ebf243f693bd067a81cb387d6983d1408eb8fa3c58e7c05a97dfa5bf4212f0749eda32969e252934e8b7cb5392d1a9c98a59f430410891d21031b2022a17fb4cfb8e375a509edd3f5abe8c3a5159e4167384a249c981d25d29de00a766434f449f0d13e4cf347a54ddcc15a7484200623ca48dd1d2c75e5ada15f6ff50f036d40d45e19973e6341627bb6f78ff2e906cfe00d6a5030c80e1e4ae3c523dddd773a50d23a85534aa933bf6b2ae139e7d418f6dab16d289553b6240dbae683a03b5f4a29c7d7fd6007ec06435db9a58694e2961a2cc5525768c8e5dc4146bf2cc0e3e3f2cee5b9cc3576240d77b096b0bb9fd26edb76f0b058c3555d75012f6fefa647f8566badcc3f7982496c1b308995bf44dd28fd29d9a7efd0e719236061e553eaf60a2b9dc90895fb976545a64892a97046b64219d951d45417d95191ec888bec6889ccc81899912b32236a8ec8e0a587a8231cdaa8c09150158aa091425684cc1ded37a51064a63004890b2fae2a8f4079d4124d939a8646b3c288945d5744765d2d59911a2ece655991a43bdaa7ce03a55b8ad856112359ced060a1c911511079620d99158c8e728c463003992d47a294a00265049b253f929ae2054a699d92020f600a433021e3465258c24a414a133202b82c3372c2c84b95e91b6fbd599714c7b9895b774f3aa703790f3e85dc4261e7749f4ea87047d7edde1125092cd42cc55085911d8688a658c144c50d4e78604111d99029d990237b2608304630443101991c6444b258324664435448232b8a2ae3e54916263f14e9a1c808a3127af6d0724592182574385a339582134e0c7101919a430b224294ac902622492ca5544890959028c0206f84b088d89418c309a9b5d65ab7a8a05b81103148ec33a288524a299526656650928510591009e2b442101b542e2ecb8a74d06430970cc8161991982562518a9019028923233543d6c8820c21811541d8c0c60629425c31841701194381b4e0c8522a2b10a82a03a204111bd5e2b2ac28ca91efb2ac88c91195f45d8e1ba55456a4c2a53609b7b6251332c5f52ecb84b0e08ef627544f989021275da2da2079c2224812503b5931410051049525c28c9165f1201609984582677e153076207774492946052b2841050954111b20f9522675dcccaed81b2ed608fb8235c242b15f96d0a3a231ee6eeeb83d4d2c73ccf6347d7b7adaa4e6936f4f2d9fc0d8514ac92fac292bbbd20f8d66c36c369bcd66b3d96c369bcd66b3d96c369bcd66b3d96c369bcd66b3d96c666776669fec93558a41a185896d629bc8f6ba71deca4ac9ea62bd2b925c21e4cab752ca9e2e9aa8e7ee4eeb0b99a6699bca85188ee33a8f4bb3f2beeffb56ac2bb0003d6bad05717084b4bc9c9c9c9c568b1593179e0b172e5cbc70b9940b3c183060c0b840075cc2c0dbf938903bbfb3f3752031f83a007893635728a92ec7dc38750c4f865e16af73aee8640652fed8d9e343802bb31883c5949552042bca1a8d763c24395541060816446e922c95286b785ef1860d0c19911215d6786fa2bc376544b0e60097636d8ab82e2ec7da38d52068077c658b003824114d3101145650c166cc647922a509104b9298451df378e75daa83a3efa5218ebe936a33d4c121afa4fc8160c59614adfd80c68d1c22d0689ff913cc1047cc88aa3441c4cfd0922725b628c9a1043c1801d4fffc33f4021436597c211264062840fd2ffe91800a5da81c2991040f6d80faa7961b0d60813812834e7a1f3202b6e70f00dbd0ea8c036c0013000b6c7d8e1c3818dcc8d1801c382da7e63706e2b3fee3fbf9e86b9f4585e56b1fe7b9f971b09a9c7f1c80b3c58aeb5a3870c4d12fcee77c8d1afdbaa0e7ce201800f8915e0c7eac77fee85d17531c776c70e7c3fa55238d156b482b00c02004812c40ff4ef84327c8ff02718cd12ed70bd185288f78467f8bd0049ed16f652df84b0b9c1416a04394271bdadc9025870410a59951a9e282d31140cd7d3717de689c156e7f0e8dc901f738fe7207b5c2331a84cd1db41c07849aa330553cf174c4142f65e4c81197155430c1b4229da48dd95162914d0e244218d42f5812d895224e5801c3022217ccb09b1bae184144c39127279ea2052a3920611a13c513de42c50b911b1aba100d01819b5ec8ec9cd803875f1e3f2ef71ef8e5684f89a55f220461f9e173246ff70d225458e104cd0b6eb2ac196241c91040c8103d2935818064cd1928519690d24601488e48c3e4248934593a96587cc617f67253532103dd515ae9d7a75dbe74898633749e315f62991f49805116ec027601bb805dc02e6014d8050c037601bb804a8062c024c024c02806802bb0fca05487dec433704892443685682cc2b294e00a6cff0c29515f019bb04c6c2d66069760afa012ee1414e3bd6d3cc13156beb4ee609495aa231d6884e5e77e4919251575a64c9454d474a7b456cd3d5aaba6aa9eb66d2a15c7752a8eeb3acffb66e779dfb75ab1bcd5eacaed673951b9dd6edcb4919b267233f5c4250a8acb13972d920a934c924a12a9dd4cf199331b183768e6326e504376cfe94e65cfe94e69755aaba66d9b8a738e9b2d396d53a938aeeb3caef3bcef5bad58df8ac5b216047170725aad162d5cb87831439c9c1c6f86e3ceed6f7d1dc81eeeedf34bb84e3c4305767471fba9114d52802c124084142c820c21454d9121645290906284103cfeaf2d96bb2c93b2e5e2908265f6f01605055176b8e3c3184c41121c5190a2287db228558e409998184f0c068312223fc0704464bb2c83a1080620427e50c2a2e807a536427e90293d71b5cb32a5315994be5c067359360237bd69f2677bfa32e4b663bb877fe4b994daeee99eeea94f2da3b8ec1d3788830c0a142d58725519191426eef8302b509e08a17a3273c2f4e4090dde131d586082c2e58ef667144edd290a8e58973b141caebb3b5802bbba2c8332c5bb2c838274e965191416da745bfb34b394dd01a952d2ad6a5d800803430a6bc2080102c893c480565e985201a9ff3cff465d4d0a5293806895fe4b284aaeeb899b6b2fcb9e9c01d38d4b2e12a15085bf8ad08e1dfc2d76403e8e199884bc002ea940a607cc642ecc906288c189932c544f5280618d6a07224f702e4c5104e6b24d6c472a122a4aa904b08c604a14588e44e18494e4c397c4e50853120daba41d989e870912b860e585322e60618552ea3268e062c60912388859018836b93489854b935a600232c95c28e27697652e0031443f48831da59374329ae2099268f6f45751270499a40b9a3f39186afed8d9d33f459d1066fdc940edff03051751a822f7014679e5ca9ff5ebf4afa24e0424904e08f5e957716bb1d14c2a5cac0b60bcb14cbf3f84d9f45bd409f27781405f27080bcc452adad9c3508c86a7e6f7e011823ffda0107442a02704d1af1b8fa04d7c9d209e3d3f5000e28f47083f5c943f9da8e3066580c9c0f331f394215991216949d3714f1616cb32254877f46e53ea8c654e59006980305c049581885b976d592b845b6b5bac18ee687f9ee926495a88016c81c615363770913139a3c4973441c1d64456546bad15092aca3c315183088aa2b0d0840a6d72c30ab4895213194025de651992932dd6baa8539aa4485b57cb52471a62a48a6f887c54b87f69320bd2048c101797654da2ae5f9635816ae189110b2d3829025908c06519932aee7759c6a40513eaf5245bb245b6444ac684c895ccf6e9aab10dadb28d4ad90675b6e1936dcc661bcd33d88607daa6d5ea9b507f0db55a29759fb3d986a4958756e7973074db5fe898c737df6c8d06b43cf949b11093d2674bc9ddab963a74dc45b22465926448a6802e68afee544c2cf70c01eee56b9c2654555225fff3407b298e425a28543547032e995e4011f080227066f6b949332fbbc289d9ffb655f89a3dfd5b7bda779ab6f54b6b8d16d63374cc33c2344dd3b4bfd15a0718ea0a69bf41692f19301a6ca37b9efbfc9ec571d1e2f99f0b9770be850bdbfafe9775f5e7b4085be1f7ab5f1a5ff07ffee03cf8bd758ff6606861fdd29e15d2e897f6abb001fdd2fe0b73f44b7b2ffc200ce219dab3f6da4b6e9396522a1d48544316412256f52344bdbe3c974d8bad3ff60f8d6db3b6abd40b36ca25b75b4bf36ff4b861993574cc53e9278e302871fc76c06e076057c87bce1347d8f55ecb693de33cb7c20d44ee9705c39f3d2c1ed7f776fe806fbf1f0c79f6c8d17b6dfcfbfd2a1cedfdc4d1f3780cba2c6ae21c624d7f2265f79cee9443f9b26f2c8b5d1696c571ab29587ebadabef666e71c65666667cacc5232338f26dcc9dc1a5867db68b23a0ceaef524aef1dcd023c6efceb66b1a0cfbcad48008b58f93e6a9aa6cdd989e0bd56a1cb0102b1f2997d905164e5bfae8042ac6c812a58f9b64d8d1faec7f50db6f1e3eb7acf2fa37f5e73878786f2b6ea8fec682d8cebbce6b9dcf7a7030460480026c9dd01fbf241377b2417cabb0a99b9a804cef734ad7466680400208000f3150000180c0a0804028148284ab34cdb7d14000c7488406c4c3497c6226120874114c3500c8531003106216290310629e510190587e00c5af7820665e283ba0c9bdaae8d1bf78003d2623a1580fb17ae79427f403d97177fa51e1a73ebc048262bec8ca7f78a168ba3edda8e3c8320408b2a0cd6f91876a0b7442f8bf82e08acd7aac7a2b330b474eff7ddf595de6c8c5fda55c1da0108082dfdde9f8aebed828df79caf14c7cfdbe2c208648ae3d532e37d14872c30db4d2bd2c26e1cb33aebae80d63e59d1d4f0156d0df40b7b626494435b3306da363904ef7b4a2943492e48f57054a5e498a58551cca0972a706214b0286c24e16915fadd36a5081bdc49b6c21630adf4ee22f0d0482cd392a1a12d92e8e92ac3f8c6679080e88ca9b559b10b9c38e9e087351f9a9880b9044fc2433d7764a55cb40559367ed872a135448052b668abae102d14090eeef78ec7f8abe1bdd81a11c52a99e79bf01d6c5a25b3c0a4101b8c5819a4c8a4c09166444dc6c1af4c8354b956b45570ff63778ccac00e3afbc92c64a5f56b3fac831ef012e6b604a906288c2ec6af60dc26d53aa7aeb0662ac9de7f4b07eeac92484e7acc1eac22b2f1ce60a38220565bf47951b36c9af65e5c77bc4971803c6bd2635f487fe596ca92a513f9c33a539410a0344a8d43747f521ed418b0d5daf96c16d1b9c0665d9e75a90315ce6ff41eadffa27b17f06252b520f37681a19eafcdb0aa80d7f0f7888bc9a8a99d4788b01b47b40ba340193eec5fbba7a96743e077bd3c2406111e34994fead472391731a8cd98c732b2e702e0faab0dbade0f530a48488f30ab432b209257ddc0072f41222e6f0490a743d3d1f70240cc90713404bdc42f7c320fb888df1febeb27f22b4c6cc3323d529f5e791d677f67fe93220199ff05b464f9e0c19447720e70ce3416e0573a86ee92320a109bef8202bcc4ca0e7a9c22af322c2c5f4a2c454d0817bac0433417e158c0dbc57bc668448c55e98803a1015a99af929be3424262097b1282b0e1941def76f0eb83bcd2d523c67a9dc31af7891b575f9926f2bd0f26db7836c67a172f6d3cb559f58700af6f11ad6cffb840f32d6a4ac0a6e81a6fe4c84645ce259f635c4be445d8769f3b78b88b0471ed2ab2e9bddc714063b8a55396820014c411dc762577340e904cd53f10debb8be419ed4d77987491ee47dd093952d4b6dde22ca29625713963fe634d8ee85c877acf10978dbd1bd4e77113c353c3f09d787f3b5aa3cc590bb78a5ae9cabc302535c0b3bf59fd11fbd723b17b7e248a429af2fb4164e8f2906c8ff04b88380fa0658fcddad664d5344605d688ffa76b5340642f0a653ac3b428c0c0675d8af48c55e53424461cca93c10146410ace2e5fb7a7bf816c9bc36209da4713ddc637771310384b8c3ea482d6cfeee0d8167c894429a9b10ae0bd1ec8b3c981b4f1a51a47659928ff66af49f0bd57e24c8da8282ebede576759250df3f85e7bf6a6c0a4f6ce223fd497bada73a975158e071a9a1fe2c0f5870a2ed80ef9f770e34f07586a729194ed26fd2d6cbe4a595ea140c34a219bdcfb2e6ac157a7fc510f72d08a4bd534bf5f793d1eec16d2120d85226f49471de6d19e873c2754adc448b9501d2c48892c73982f2b28636e8484cce74a098f5d6eb0c47435cffdfd8df3b541882606398de96a75ac319c49ba8c9fd3175a82a13405f87459bf3527e613f73af6684960105f16aaf313fbd504966ab2c30a1838960048e815dfdb08a09167fec55d9ff061ef7396d300a8d82088880333dea4bdc90884a6b67ee3f722d266af7abbf66f719b7a1e327c81390cb239f72f40f8c603d5a713c4bfc470e179b07e802f30dc09e3cfcc17083638dfdc84294d15873bb976da9cbb0d3486857ecb4bb9dc7e5be85f635bd8c4fd921d02c507b73e7830f1a6a9d548c1078b1524960d6331e3d470d09950919803157c10c0811b1a23e96a33c585a279cf81b1692b32fd4942e33d84d98702dc1c66fbfbb8b7dfd8c3510c4686d9fb41c3ce60c66fb7a51c4fd41a8b986a22937567f8aa0f68b7f989b7f7abe385b908e9c2c847f7686906c1c18d21dae43e80de0230ca9f99f82fe37699548330ccdea89686ef4a6429a1541b6208673807ebd34442cc59c9fd20fe8c8a0a24173ab2aea6413656c08f60566ca91609c5f27f6f4cdfc958500a077af6d5e9a138bb75f9d05f4693ce91cce636b105522af107650e4ec670f0acd6909724e52b515e0f28a7d3c8940692f6f912cf130b82a3da1e516f845017c3e78898a9296d1bc6bbffca6365b2763b8d7348e0de8347611eca610ba52c8aed2d6709c6ceb4064c9705a9fd4fdf67a945b118cf30199dc8cd3aa404ed244ed0360c3af5146ba2c4fc2d7da0e798cd4f7fa16c00e76b3221c412d1874a92e9ee94260e04b3eb9d3e3de775139f884a53526c8bcce1c406c98ca978d6b4d066032b337d902e3e5a2f48dd1d212ae540de489a9f495f304064c33a1dddc8c6b8bd2088492af091ab834177759cea7a4e0d371b1221b6cdca03d701d57e432d9e905ae36133423438af3ef53a7a9ff190b354aa4996be0e341f382c289d753e120ca36408a7dc60450d7718c76dc0a0954ab35190cd9072dab451246397dcfbb468182624bad9f63a02e0ed718d1de93643a1c2e433c008e1941df9fc8718ea2954853579e185c4e865d7534be9ac64e89472cff85e0c0fef7f6da688cefa84505316bc0a862953d68b4de3480c537702d7ca94442a0775179e03d98494be48486233e0cb3f3d0900fc85531d6df7995d363299e8e5595011f7c6c5767f582ac9c6e1995d383e9b254f0ba1b5e66a7b23c49a3cee42984d68f946253180e1e7e8cb26b93607f768dc3453d5524ff2e5090d322fe87a9e2bca65a7bd0c27961d4e5e93f0747b2f563443ba12637e63a23e9fc09c3cc2f0fce97c946eec8c229c89a97dec4d65208b2d1cae64b1f28e585303899c145226d2489f35f4f9b6fe465742eaf20bc4959178368426d9be00a27035f8f1471c817ee82563464b5dc096f3e049ae4055dd9a565dd693e75fe213b1e222697093e130e990cd750a0c143fdadb7c6a3a6b36895d371398de6a66e4e2d03a8e842cc5e71246cb13c7ab81aab0d2f1b23226a5275386da374ef90982dd543f882cab70be10fa472e77fc21d4204f8542360d9c494d715963d030b44f6765e7e541db31c4c2ab02336cc736a138e646cbc86077d894f2ae93a0adb96befde4f0f68ec56fa85207f5b0cb516fab8c52efc399f86956c95e78c7d62cac554447aca1de701c161fc8acb9a974d3e91d3707c71d952d824ddf22b76ccc9a0b2e0fbde470bcd014912406655df5e6af55b3feca01bf60bab2a4b1a47bfc3312694d7c82003fcf538b2eb6e9c5e88098ed10c351c71329395a11de509108e1632e526f84d9f072369844b94a0b8a62f953bd54fd9e67b4026c5c1577340c72d00869e5e318aa78036c8c790ae1943e6e561958b745abeaab5510e14d816790de13c14bbf309dddf80f3a0673aa69b6aa2a86ee30853518c12b5c81749557efbaca6a20bac0e147471ec39bc9cd11dd3d6fd3156d674933c1747e8c6355e3705381af4cc2f5fbf705a3d7b3a650af3040f195609115e41b051450a5a2735526a753d42d0533f30c108e0eb827dc054ff42710c261ffb8c6d19113e7633cfab2ff6ea917eab633a56c87bd5f85bccaab3be055b8e99083edc80cc2da7d87eb6479225c72c1842fb7a095642e0f0758f12164c279be0867b756c0d8321e36461b7caf3be7bafa742a074b4fa51f4e334e8a0bdb9290a7aeb7ff2b92c7d51207bee90119471d7ae31c16bf97467d9579acd5e59291de053bfe679cc6e2c979f1674675fc88deb21e19cb72e34c9080cfc68642f7db5e3cddedc8894d6e55c4b628be355bb3ebc95a4947598ad6a18b8b76918c87819b9c455e55e962dc677b6bc113b5b265c64b25a13e01087f892d8e5aefafb4100319ae4bd7a78adbb6f2c703e076c446a30e6e289856d1fd9ad21d651f79a9a6432f3076f3522158b54e95af489a2ff385b3943abfa05f2c7bb79b3f989701cf97cd845e64c30f4514a609156076bdae24f717a1de8009b529942df056948550cf4392c0db32f838fec6f8b118f5cbc1c92f3037eb2eff72467b643ccf88d4ac4176032c55ad09b64a0ebf4d6444facfeee43dc15fe0c80a1a9214a974b9811b1130e33c23eb205cb61bd4284e60451b5b9b3d4ecc07c195c62d8205bc38a61eb595013009707652fc7349ea8c0a436e186a210fe1d641cbaec188a2449c7636cf63e6746a9a30af41c5260d14aaa872ca4cadf09c4a2fa9bd0bcf0563341983f7d21899f037d61e2a07bd97c1ce21b1151b5aefc4bd5fee261c5ead08a5ab3951439a47c3a1aaceef06acd6abe62d1270918a2896d237e6ab582f1302eb31363d42f2b6cf538940162aa01076293a6372d735793f4550391211d02201e3ac241155c31a1a5a6630e6bcb53cdd017725a3d50fb708afdde3ce274354446f22831177e9ab10b1480f3586bb6c889e20ebfda7b94a845dbfacd77a29b6aa5220b3d9793022faab65a6bc50cd9a7f6abd61385f02084d659892d4a7dd676b66eb5d394b1ef81b7fad7db8fa0f20dce6b565c3c5a87f95006a372f5a5060a6f44d1428778daecd357e67b7b00c2cec0e86f5b21e56050bf21eb1d4baa66081e5c826c376527f238462810303424c323161fbeb95b496686c47ee368314943824291014137d814ce21b0f9cea0ecbc6b720a6a1140848c185637582cec9a3e6753320690098b4d83b29ef8af3959d501a7eb6e032b96c42a66bbaaac5b3f2ad0e84658205418b9762a7999588351781327929ff5eccfd34e69a76feceba3794cf78dc3fd22ec049f14f41ae740673000e238928669cd6f10f9478298665b83e1b0cf8657905e00cde5e20eed413148e7f87bddf2119da9f16e3afc5dcc3cb802870470e18dec875b580a9cf752b92d18d9feaab5bf0e4c66cbe66309e2b62bcaa510373bc2dcd811e2164738f79e933e9e94803e8ac128365d1b9122c197e214cd4f521e30870c217ab3c222471e2b9bd8cedaf285eef97661226e7a4fe102037efd7e3969f77c17aa854600003162003083436f1ac6f36ca52ccf00a23efef97c78e4f0ae3b74b3a00610b5a834b46c5cc96e425cf8a6a80b6624523f460833aa0f8aab0a32db41fbfec08fbfcd0ef5fc2e485eeff92beb1f520552c4d676fc360b2acb97d7164e13489f0b552057f74699fd19ebe633871e267a94b5b81ebf2396b38ed91c8edaa3955091e5483a6629a2d630bf57890564e4bab958501fd3dd476c9cf8bd910469b2c94d8fb286943c02f72b5a2267bc8a667030fbf5dec3c28488c205124ec059da43a55e0764fdb2d37ac995777c6c2baaf7c26cea5e01679537c3848c216280935a6640e9d6146b5456014f62e201cc28f25cc0cae1d0aad5d0346fbe02d97f1b214065aba974afbca2e6f4647168c442cce79408a84ad2ccde330d109cdd8f261f30b8c89cd6e1747de5171669a0cbc1f6f93575e5a46a7b0d952d93b331566e16c3ebac06f7c0d37ed9753184966470741d236690dc6c5bd1e87c8f5ca24b64ca48d675b56d1bbf414a1a2324469066b985a01e9658f2fcdf0f9dcec89a22a40c9ff29303fa797343a2b6a5fb01a954f50f8b0a8a422adc7ac87901e909d9dc59e6b4edef49deaabb1c295956fb062e64c7b349f749368acee0b6bdab598d91830e0578fde09dfdb479ad1eb2de43d9b212d56ccdd22822c9b99cf134c538536a802a97cd24471dcd022aba2996d4ad2e46cb3a0e6d251a96cab4f971582109e970f271cef9585678469b66961d0ada80f8e382c9989528e4bb4342fa5907d5380d3812461cdc9c04d421a738d345dbd21551671591fa80dcb3aa490f4da60c5361951b3c4bc6ea04460c27c03b647c42b30d9804957e24b54ee73bb307298279f66e3b07509e8f058c3e4c713fc36265266f566576c9ad43b54965a4a810f62ab9e1a358b6936345346a561abab9c40d1340def0ba2199272af3a585f3eca0bfa426ee4286c36e8d08602d796934348aa1f9e27e4ed0adaf09354ac2ad5d58702808a61a2df9e61bc5618c7da6898f784756c1b266e33118485f011fa7d078b2630a77fc4a6b7f165ab64100f70074a9f6b8ce05e5151f0ce9bfa991a3cc4c308d5b510cee9042d3040d7c55bce4d2111c8a523ba69bd23822bfba7ef3a0ba4000ea56498fdc298f630fce5f257cba29422e140fb6317214dd2b4765c0d5ba898940f306a8ceb403fcd5e8aba488ecd0ca76abc59c06528e66258366248fb9831f31a97b85970338e15a30a16525c7a4962c99bb44c89223709b4690b8e63bcb14adbffc0e71e733ecf00fb958c11b733b7661e5efe55a807ea89416630bc9d253e25a4838500dbfc5c4a825cbc489ae75517ea0829f9d5b540d576009e9ea97a4368e4c1ec5a9622360d90a2aa447be84585b9a224cd9411945f2d517c55942aed070b060378360e855ab23db3140d6be24d87f7bc6444f73a7cbb7b8c8cdeb311f72bf234aea080e7e9cefd81cfacceb3b8974ec22c7060a36932cfecdbeaf8c8d9f6c0c8f59c53000d5ddc51d08e75c04a7417819839663c0e0a221df8c19459bc2103b592e79b94e90756c6483cd7a21766c211a34d6b45f1a43c474c97d29230687892dfae712cd18b22f8c440a1aaa18cd3202c263172768f079a9297e1d8d58c911a861d887c9c5d2b96cae2839c9c60a89743d6bbd08610a02af95536aa6aa6b25192ea8c3292451d90d50269100ed7ec6930ad23f6e0c83efd0a89c0a638f0b199409838e3cc068a8e929ec6d8b5c19eb6dd1a7e90615c428e6dd1213c43d1772a8d5351932ea9f299b38ea005f93f445d46d7760d4eaf4a57e74ad11dc6c38e13a082329d91e19cbe8b4b7c4886807b6a6d2b0d4c86c67c1eca534082a738e854cf6b9824801de54970bee2cefe8e1f43d9ed4aa69e969b40a7dd8c6a5dca41d653a6ca3f39124429034efe6f6e3c345c87b3aa510565cb8805eeffb58046b3e22507a004ebbc7c5ee040e83567fe425822ce096567441c2b47a52deede566d753e4e3c92c288512747cc09ecddf9ce3c1948f6e497d4796b226a992147149258cfda593dd4840d8688c6ed590602585d290614a61bc46200458b4431e6df1c2af34cde372c5b47cc87f7becd9b2e86d3a54e98f210a6eb2c0837fa1946385609a9606ed440f72e2d642ad3a59086721884acd02245d559ab40ef1fe28e8d67bf647060864a211e8aedb6f959b4b181623a7f10b988ca7f5af208803440c34658f6cbfc5172628c9a3917e70671ec0b5e49e199811995e20caed545eedd065007d1166ba7c16f042fe308e0527067273ef3c7d752bf23731e6d88a9e4d9843b90a120b8bc2d2dc1fb4ada04be250fe2848bbb266434f5b4be51af92c3e4a7c0c6d266fe57c108ccf2430b6be0824944843f52fb1cb3461b3dc100382211501a2d93d5375f96e653e361598c4387da5e536dd2668f119bd7419a5b4b7ee37efe02fa1c248b996ae91d5783353f439abe0f2a2349729a2c3c9216265e4e4b7a1b045f15aca80030fb264db54caee1c027a2fd9a66bec633572eb2b60f140134a887a3930a29d2ab7687d52efe106808221f2dd7ec44dd6a6b3db5bf04ad9b61c5381c0909b660029d4840ec92001151d01269d5e1bcb5e03ad4038d6ad410a03d66c95526cd0e69133811d3438f81179661aeda485738c075e215f8a0a3989de24c63254a4a4217b18fa49382254d7e236917ab889af55f5ac2e74a7660e9d852ec86e2d5f694ef06282f67802155bca2d7aab2e74c8f43565d93318bdee5c45d84a95219fed69b962f01391f51fc140d6256e2d88cf5a3fad0c168bcff57f5400772eaa80c984f2805053d264b09b31d2fb8fcb7ec40151ad239279ad7566809747051682e2bb1250543ea3d1d0e4fb91fc4092ee3a75d2cf9a9a4eefbf83195fbc9f147e717ac81c403cd7bbc97dda66844d3962708a6565399ea3d256699951161cd13e726997c9ddc5494919b68f1684430934ab87eb9abb5d3aba47588d2c177186f662106b3faa11da92be528da6e6bcad941ea42f5293da34420002c25b513c603538fb0e90e223617e6620f2f1569647d6d272c0dca0f867584c3b3a6a5d0d411baa23a9bf63b001e88c8d7c36c2b956590a5c32922dde4f0aecff3864c89d08b39c155060937876d7f4af4255e901fe57aad8165063b24d69e35f58f1bc193868972a25dbd944ff277a17aed7a27656d385f95659b9399fcb0f550de0ee474df2e3f75a6ff06eed12226f51b48f959efb8544bdd507381480689058ba67924fac7a23cf4f648489fcdf48a9af2c1d46d0992db099806b18fb89dd16d4d04a9731bdece4d323332a7135b1d96303dada2c09dc474a7c6fd7c3b16ed951cb3c8115eb53bc08c18ebd498df483916054bf986421d2ec8c5e913af923a5504d85bab98855c395716f6b113da6b299e7eb9b4a8b824424cb9e6a18007d8378d4e16ac94875e17aa210b59648d34acf41c0dae24c004a6461761e40f5a8983b7986ace869647475d11e87b23d551e140d4160c125d1a48d528486d137866e06985ceb7c5d775fdab09402f531976c02c91dd10036f445b5bd7f8267ea119a2241c323969b571360a3b4beba8f2bc0cd32713625798e3928aaed9f2bb6b2eed90159c62d2c75e669461ef93d14f1302964acfb839d198ccb1dd2c7ccc016b6146782ceda38c6fa38c43fce217f82fd9e278effe76ace74d472b13845e2b80bab5b8469f079de330e793cf0618a1faa8b398e47585a927b2923268acffb222d27eede1b592a4816e135da8343cacfb72c04022c1b73d9cf1f27c06592dc474b026231103c1a6d3b039353e243e4b0f2eff1b053132793d7b589094a1b84408845fb3b27d08320f11c3173ec469a396211dbd07007ed707ccdea1608736aee175e4626682235b7b40f6d1068ca3537ba0f3dd5c9ad148f978bcfa96de9d01671488108ccc2a1ee14ef57c138af6af953b466bf21432720fa8b8ad82437ed415f2f0c7a43903ea28493c4d69d26c63d1cb400e6eb09a34ba885be20485c4a483e890c7a999ef62a82c81bb35228d69c044a424963280b503534f99629f4f9887a8042290cfbafa7f5501497abbb80ba2db4064ee08d1a2e1614b83d493dae58be92e01e55c3d7d594d2dde27937d066e9855264fff98d0eb3b508102f440909ec902d91e835ee61026ff524a4380a0a2c78512bb54dfab6dfa77f7b401c75b472c56442954c179024d136ff25cc6e63c63e09af1fc411e6cddb268d6433d7b479ede5698cad96a6d8b67bf47858302bb85cd3c5a996938d506ff4d612694757a2e11f10fcbed09bbe7b9f5df50a78d23aae21b88e77d367eb87353a48b44e9b0048e0d70021d8a132fb547eac8656eb787065e631f4185d500be16f1d8aeb9e5df3935ec1575d9eded1df3af49c044804e1526ff9b518da42be7962589c8c85fa2dade7da21e461901d63cbdca1e54fd3f518251af285e2731f1da7e334d37562357b6e78b29cb44ec4838fad250b5560e0948cae1e02b411252d2fad9e7787f28d97058caaa19d79dcc1a158a065b7ce9579f55d7cc522afce0f5f10c10bfdd34cf29e9e472cc8a304d238825370358ee294b8824b70356ec5d1388a53e00a2ec155bcfad36edaa5bdf49feed37ffa4f3b6997f6d27f3a4fb7e92b0dc4f5d859628c7ddcca8a604782b3319f536afb09318359eed2ed9ac02ca6734b6d6f42fcacc7444ea94ef84efa455686349d38d045d1ab1c3c753ca8ad28b6e12b121d99eb90cd9d320a67aeab9fe914abed15277dc5a59a5216e5529af2539af2500aa0cc013124409312a50b580f09f919d17d15b771b47dc9eba714888216f4b34f77664dfcfedf0d68ee5c0a426f872ddc090cc6f0ba3e353c97bf00f311ebbde213069aedb25593640e6bff584de0ae9d2cb5a4bcc7ba3b575394b700fe941ebeb2b380c1088526d877a977549ed5fa6aaadc65e51fab66141b201fb1ceab7c8af5d474d9a7b573409515422b11d6ff3d821d16cd600cc8d9102f1dea1ef72ba5e905de72660f318b8dd75a4f0572b81c10ef37a8415bfe949ba6a06f7db9be0f6b64bd9b825b37954fe82fa7e78fc5c2bc9884b9ddf2a5ccbe47515f282f795a9e07daeabf7c11d80e49c8bde395ff9b6e0c2f954b9e2c7acbc66b52c05d953ca83173fd5597b45330dc0d2f16bc9ffc8357144ae67bac72b68865c6b0b8fa1fca86de5cb7221e45c0848a3a333c2956638689ab5bd3d0094c6c17d73288e11bf6a9f17237e0dd7ab7d59213f3b90046916f1658b9e27399b88ead63919be378aafe33d41365f845b02ead3214764910ea43263e519f13af4e39dc9ad1cd7f8dd35134703283d8de16583cda62a260c3b85b8c18192a6583a1a316aa260320bcee784bd0c5db9ad310116065164db2cc3b277dda91c7c4e8be0c18daae1ea04b5e12f67efc9a0855e9432f1b2ee931d8cb12fa2358c64cd828ffe70fa7dcba5e11081717b11b57f20c2ff361a3a4a11fb7e46d8e0e6a077088e25a0452c216ad9fd00a342d75cb843a2e43156112e6c6e9c4a2cfb88b9936832ebc3d5e5b3949607232d947eed368c31e936ed8c40b1056d1dff37d33de25cfc53c75bedf6e932e16805e3bca256dc82cf36185b8d89feb5b0f9097f0623d7c3cb8c2d8a618a4688187a9dcb5a48f6213b0dd72e1c2e9c5c1aaa3ec1b3ab3eb4a0390c66737358913939dff2c2d840ced7a7f5ff557926ddf4a43d55b1d180cfd86d15bf004055709aa0c8418d8dad8bf1a62f3ab84854bf38862b114760f42371a233e0330c50bdf20dca385f121dc34fe09f32cb3b7baf15d888bc81ad2347bb137feceb54a75d2c83c5048ad8ba987e95c36e3181a90b2818787a3409215be73fa5d510aa41a521b8967fb9f1250a91bd4e3790adffa52be45fcb8db23d138087cd7a285d32ddb3acf1d3ad7e12e84322544f6661ede4d148b9756cb649a863a4d3e769fefa113e6d9ceaaf412be6052ce56e11b0d029903393d1d6a67120cd5df1aea75b8cfd1de413fb03a32225c6cfa0005d09bbbbb50128f08c38df78cdb79aaa545b72cd30d3df568067986900580d4da8c67901a1aa41e6374cd0d10793ef62c66d9d55a54a6adfef47dc49e49127ae90efbaa2a0e1836bd26b4a11e81c66aa30656b075ca2df347d199c12027c3c88ee88f0118a7015b149213a3c0871132fe7023283d9409db8fe6dfb47063a0db43d5ddcb273e787e41a08a4e7019fcebc7620bbf5e393d0e687b0715defa745ee65061c80aac090d4b036c53cfa8811d3c90e03df5ab6f3a3309c2a282373e1eb81a8147d0622dd437515ed660a16054315baac188370a6ffabdad8d183a3180901d23dc6b9a685a7c90185728f9bd911015af6de36925a7f9a64f9e75e32dc5d8d26f5a7cd364e94c6d53f3789a3e7b3b41be9f68e568d377d4ed290cf346b91207a8a55753b902420ab5f270736584b4922be8894445aaef0b9ae85cc19cad7db60641184957034793c78ba169f576325d867ba40168a048c2b8499834bc891ef868fb347e8ad4477707fc00cd8c57a59085b258c236445c08bd73b530c24c3da9a5c3115a77a18f9077da13a164cff1eae88c38923b425bc34bfe13ee3d5df612c5bbebe4f4e7d823ec0487161b702214defedd86faa0ace200f15710f6ef3809bb3783e49a1ad8112d4aa8e74d94e54ce08c5924e03c0728df669624f4bfb39fe996134bce6aa52b4ec4355854b1e6e1ed19583bf6dc6e2ddd3f4258485c2b5521476c17b80892234c406703ffa6548561d96d2a352bf68f243e7e307012d0460fdee631dd0ea396ec8467e85ea72dd84b7fd6e272dd4578b6bf7d7bf9c696338772690e3a25bf473ea61386a66fa721c8e7738f98f765c8d4ac419bd44b85427dc558332b0782c4797c14e89a3567861a8d36501b1090ccc89bd03fe235768c79db48c4682633fa3f6c4c775d24024025a59a181c70aef1da52be40f3d3196f62011da0971831d391070bd35a6048158c4a1bd8da3d6bd7a2ebe4cf66dbe38d087fa22077fc87f688b403d063790008a03d7fdd22bd269de54422a76475e87996534f8f193e8feea8fc571592bd2db36fc53a10b3e5db11357c762cf4548761ce4e58a4c3b606483d0d7f236f07c322f825536a9c48ebb757ca8f1423e8db6e408c35d48d431a8f65fd5fa7abc8bfb88bbc9c70380d35312ee17edbfbacee4b3c968100c26082ff01047d803621e99acbeb6622b43d1c8772740b1b77e47bd774b418d68f1aadc571d73efd52863a0d298f3fa28ec6826f656481a890173b80f9477bab446e79ba5bb5b1b0c9fcdaa19c1f1d4dbc5265966d49177de3f4fd92bde742374a009881b60adaa89fc264a39c921a2b880db7cdb96f1179fe5cb0cffa50ba1d200f11afc58c3f70688b0b95ad7deaedbeb07a23890023125887251fcbd2e0bc8a8a1916ed52d0c3e0e04aa72acd3846fbcad8e8c3a3a85e7934b1a968226ca64849b975228678fcc01e03a9f4da99a43c20e671325f8cfaa3812addd4684c7f3301ac709c565093571f43d33327153bf58efc79ffeb14bb895eef3e197032a72fc2775bd29ae63c1453b5dde165e62a1bda297888eee2d508af0362a9c0b8e35620ab18fc3b50da1b6a13a48e5b60a2b2a3bc0448cdc50bc03a65faa04a0d997d9769c360f225203b6b632540ea55c201c282c6f2c3f55f75b38295ec71479b39ef96d0e2a56cd95451062bc936909379b7a1b4db64646568150bb058464ffa40a4c55419169beccd088750fae55fb5a0ab5463bb63e33ab9ad78014cf4881b2784d55bf5b99a76c84a74576d41572aef944863694dee7c3d425d4aa68151f3340b2c30cf722d9ee08e38016ed5257d322a6201907476f045d0aa926de8e63639cf10603cca28dfa4e446093766247165d2f18ccf02f1b806e55d099e3002e7c9258697b5dde8f1d741fd8906c377ca11d6692bef9a028470acb52928e9b06fdfe4c1cf2bae09395e248e18673ffd613c8fd6b71235583719869d26435169174bc121d6a7ac387014a6de065a1e002a433c85eca9748e5c890e8353dea319a38ec64d114df369d0aca79d986300e05657eefab2533b9c39a3b8d4a5615c7ea69f27c1b7891482f7286ff8ea1b6105d4c2dc446a9bcfe18d714f0a2de9ed0f8a5bf833fc4d4aa2fd644648399f2bcd3d69fd7d090b4499262d39d3dd0519ad398bd4936cbc4145a4d3899e80b2c856bd3f618e4de0c09b1763faf317edea10d7e9892577fd0d6bb04068d1a460bbb89f010914a1b26f23003bf1585ef05e8ab0b8d53efb8942434a723fe9b847c15422e3934e120623ebf02a773f0068557451c6d79e99654a5376f671216cdf71ab78e5a13f2f0bdbd7b61dd66b692c6987917aa38e2ee9afae3ea3fbe519c1851705b82a73a6076990c55876bb03bc17fb18dd7d8691fda10b91bd4d6af0d92ce7c9c2028c7ebed828ed446c3e4fa25ab0bc2599f1b1338e79e57d573c0923bec71a18330e0b5df6346a053b9c8e8d4f6170dbe2ab52e466102a4631c8cad94be978cda178c85bac304776ed8d6a84a85c86703a46b78519668c0b36454deb56ed694dd5445cf314f8c2f3298a859c14c5617680f2568c6ec8f8d75906a4499a9415cc9fce6123bb327e68495ad9be9ee1469dc4729ca6c7f9c995fd35834181431bbc956e431e61d859e5f48aff16a3a1e6006424b901fa18a0ed487ad79852102e55e9b01d628ec0d2a37e0e43e8b2af16b76ae3a299f9d5c9ac80a21ceeec6e01a395c89135da1c6d535f99fe339c74207e4fb77ea86268f0f9a5376a41d4ac5e0f86f84d6b8ab33a8a145d3f84f2e0944d212b4136505250d84682448f4e7342bfbcbfd1b98729e68ca5d45d384e7d87df99dbc152facbe96e59342e2fc2887b9df1fead5329c0f721df094b92ce88218403ddf58eeafe48cd463d8890b8abd6edf4767a63b9cc9ea650e50f6cbd38d200ded555c4eaa705d50eb96cbf9596fe780a6afb6497c524aa2686dea33ec1b9ee8006ff2a955f78ec92943c0a3447f43896060d83049131d72683a29e9626b02870b3a28e75e0701eafe1b9a1ef11cb8009518256fefc705c72c930e9384a3a0c2d2b97f7b831f35b7f9815f2d13c17f271165ea65bd191e3a03ac56f780b0784d86e2148e07e3505d26be5e944486ff0eda5a4c3087c5206441b18ac43c28e82f1943589c3da14137455e5a057c629e5982030eddcda8d5fc2115ab003c6fa6f37469429c2d4628acf078c4e8111724f1987dde6e317efeb09239b3b0ed57ba7fd2d15169ea6c60c2314ff593c64b2d739c8b82ec2b502525bf970ae527c066f32563796c57909f0a1a1a692837c7106e35e2879ba07a3a49203c259eb039180af5881539e1fc478ce914be47110ca2212813c36299bc1896043eacf41a26398bb0c773dc8cdddfdce643e1f43a864b0b863c2a6e468a713fe5bfea3af4f301613d0beca370ae0916c055a880afefa3557b8fb2d8de55af3810b3917756ce16fefff33c7ca2cdf9f5b81397d54dfc0cd949bb61ef43404b653bc4669625af277d0513f9462ab03bc1b56eee189201d28b69c6eb8e25e8cd17b4464231d7ad4336348ae224fefec92312d0605033e74e92eaa83a6aa7875fc2a9cc9430f20c931c21cd12e177a4cd9e68ba3fda57b527d49d75256bef0ad336c39d7b7e83114e0a7ce24576deb0dce2540bd39747c10368fae40cb0a48672f4baf4df3ad7dafa387e5974b65575374b5edd9168e75192dfb542cc1e0dc635d261f044196d8718a23170c186ffdf76a8634f78d24b1a5bc012d774d632cc90d6e0e3297ed36446841acbcd3c26ad22578a39985024dbdd517b14f2f8b4a9a0e954301c37969cf31973690038a9db6cd51c14d24f83687f0f8d6f56878c3b4d1e6e5657c8464e58a18a56c85207bd2c67b4b4f677f3049075c0e79f788a65e6ae27eee5d0c14e59dc54400d5ac5d104941754da73d6ec47f5adb2821042a828efd41c096a948bd8f0f340f767b2b44705c6048c2aeed11004185c5b3179e6f2441294c356b437424836b70b5fa45c2da787aa1d8b81d32b5f99f5aea266803e7045591db012d453d1aa3cf1ad513d0ce32dccd80e7ef4ab41e368fa391f59a278a8efe94f40000b91d435e5a11f01cd47e963ae5359557c1920f481d5d909d8c09e4c8dafd382a5f108bd904220d2106fa6ae3237c131e83bda5f6ea4e8a905de45d45f00403cb36128f6df1e81892a264e66fe80c9effeac33e3325b02c027141e38d417e827780759d25d3c982c4721a6acc62b9a908d1edfb4969a0b77995c978e01635cc59a93ec6ba32d2a7054dbbc2951aa3e2f421f39f72dc75962b9e17673a17f8cf00b379f9b4af0bdf500bc7ee8b1178c9f4eb2c217834afc1aa6859d358233b3cfd764f7007b1ff15a12970221e4f7634b4d8e0c3d2f7a9d8088838aba2d9c1a7b71f95525350beb4e05fff6240f96074c0b44afc50a33b4ca941c0e50dd34000e97596e9af1c46b929738152b271111b63676f65a023b2d7591abf47141e6c4d84f224ea91a46e949fa5c0a1d4cadb914c24a2d89a8a79bdce424b8ec0b09d6e4377639fe3ae42b2a5b5935adfc328ffcd8403d6a238e2a7357d4aef73caa3e5a79337e062bad44ef213bd1348e4d39d9de6ebd7e3073f69d9c9cb1529c61064da5d6080cb91f8e93cf6ed7316f0dd6bc2881a471afb86070d7aeeea834c2ad7d0497215ee967a04288520f71dc097f98df3c11eddc6cca35eabd6b5aad759f08bc8bc1c8e53985d6fc95987b830aed7c8c11a865bf1059e378193e80b89acc703617cdcabfd0d7397d21b219fa704424b5123a4822ef2787a2239a22ea9c6c2c1d76513598a130129b9cb086f02c05f89a05b26705f3324161ea01ba2d9c88ee863ed370cd4c5cd7e4f015038fe17cf064cc9ce844d16c15195889f3f163840e410b5f9cd5ac2c68396633207910c1b558d56deb128e25b593826651263fc42255abaf680250ad69de319f27d5c028d8de03667cfe8942aa833e173fa83d39e52e9b67a1e9c851dc44ffa9a939925aeb7fcaa77322f3e329b4c573c1e447488d26ab771b306edd76530cef39da56f40c5c032be60e17be832533c1203b4ce77962b8a2e3ef33d7e549750a8190703e93b96bcc1509c4b243d9899dbec5765d5587969cfd81f19cf73beb3e89487ae7b83c3581a05eed4741a914f4014497f6ec869b727199ce7cf7796c84f369ec4e9ce182ab17f98421a92d566703ca5511426ac6ed7b5ccfb4f219ba656fad8977797bb3ef6a3c0b4003fe47281077480f01ccc6d97ed56a1bf5f75197161abd2a353f324b442136478235b199f0d4bad129bcf88809bde770e27b4d6326b0e86ce73f739f933b5f59633deca34cd9d6353f1186194b25c763832844ddba2f6f6b83f154709e793bb1cc8cc156c370826c8f5683707f90b147c6abb7e30d4ba633747ae1935edc282787d4719db72f4048a01fa4457554adc14fc3189f3d97fc4b82c0674cdc4b183f72ce4b99014876b96327e21cac0b2d7bde43d4bd7fda3156a2276612554a31d7a186da675e98040c2038afe497481b46bedc0dd4f7be8550fdd4ca2c8a8291d89771409d89bd6152cf08fd19bb1093ed9e66c36ede94c9b32f5a99f34de99c9f4fd5aed16eb20b7528a2b90c58869f6eec50d50a4b7fddd94f6e61028a414b4eb7a603e4b842c5d6b0e879c43cd63f90215358f68fbc485b868021b76ce40d159e44c686ae687f3a86d2ef8b98cb73598cf745f903224ef30707a3d0d2e5d323651d668c2f4e3d38e32313d95c30ad76d34b996e28ee7111fc38ddd0f5972330a9c0e340138253733345fdb98351b31ced9bf760cb34c6561a9451189a8a685dd0f48955a0847cf9e00498f0655827dc7058cf368d1f1a038d29f0af71ffe803c30a94cf666c1594504a555aa7dda8a5b789a0f086d64477b641c770ca8574b1a1ad4813e1962cdb6c2438fe97e8930677788b08513384bcbe4f09818c3099c5bd176f7505eec1c4b76edafc7722fba1799f4f95db3d1ad13388b329adab17c038928ab9effe63d6b20af31ccdda149c665fb5a64bb6daf7d5822ae80d671e58b853fb51e0f8e7039f15122b0f816b02f1e45ad07bb0bb7d1b4593331d12c1227102bc0ce9ca92962b6af4714cd03372518db8c1786c80a5fbb75ff771ec7d11ca21970a90c33cde804c714a9b5db351dd6595f1ccd212db1add0260d3cc7a1ead11c8aec26228dc531f1ca86221bfd0ca272fd58f7e8bf52a18af6facc3bc101996beac59be5cb88c4ffc79d62da40656045e5ad7fedb9a5af44d2034a70c3a339c40b90fc18fdb251e9b314d8bba54b766b2eae1e1da97948b875cb8e0e8a45253951b5e9e2921d5358ae9b31e81b7d5eebf3463faffcfbf9702d494301c1f06591a330a4176e399adeb173f94735d76a969e2da6651576295d3834f7e39aca2f3dfe939e2dc95b38746be21c3dd895bcef59cb74f5657665b144de92faf42db3a4f758a55170fb31bb2cad4bc4ef58c42008dc71f10122b843b10745fcae450c04d11d171d204277297e508d10bcbba01121b8e8c001058e99dfcf6092bb543b273c03935cd2b69bd059cc722ed5fe04676102e4f2a4b19fc0d9ea1e52de4d44c466a86099f9fe4a03b1280cb1a02919a657f6aeec563103177998dc82bccbd561e3022d6d23eff3071c00a2f9679e1718eaf820714252d1b0633df761acb1dbe21654fc5537aaafef073be1407f035fb728481c6c6bb0fc5c13e1e766d276fd2a5da07b08deecabc09312bcbfc3772b268076e8930edd9e4aabc2e721105f44421d22c1dbcb25fa1b84f06738dc273df20c50e1f257c49d51bea96d3b1d2c8856e1c5cbe6dde724bd901b3210ac2c916947ee2c91c5374edde1ba76a215305f9bd302289de492d0e5eed5d68cb68025aae4fc5295e1bc76b5ca334cdd090e96049561675b6040d022f3d4a26559576261049cf3e6bd8540dbf02ad1b60b07d08de447528bc7707c2abe838cc40052cb9c08df564d00b10e5c3f592201f2694eafbf6b3cc6ffda4262b03cb51a0297e1820b3dc95e061f9a08c3f23f210781e9354024a6f818c2b1f1630bf104443c6eeaa7f0d84e1917ae25c11a3d89583482cb4d02a9c26178e266cff1e489b1e38e85db595ec684f5cc4a919a53505b6eed3bffdfafde3b5cf6e89448d50dfb29f05c65c005f3a573536f3ec3e370a91fd1f91e2321f3d5fe7da92d7b7b1ac1c466e096be81b00dd4494995b78aa4c7ed9d21aab2b1405ea115a1996869ac1be8a35302ad08e41aeffda9fa7a61622cf77b3996e22b7f37f953bc55cc8ab8a838972c362b02fbc2e05811e515f7bb2919347f61cfef1c31273a799ed9a676dace67b807730d5b7d672e431c1c3c6a8a71bd786cc861e38b08530249eb791dc4721be0459f88a14417987af81a54cb3a6e0ffdd5a5a713312eb665b339ef05eceeb650df17420a771d4ec93de406065148afb87ea74d101c82731a392cc81d3713592fd0eb99342eb3d5495e723fb5b519de0d446ebb73e2529615eeac320c237869b36f37dfa5a8868a4bcb322508339d2ce7058cca5ac1e94b1e95ca4323233cb172d4c2b63fa758b535526d19c509ffdbd92ec2e69410318494d2593d33467207eb6e8e39b1fe716627da92c91c7b09195dc0770b689bca0a98587fc33cb40bd8661dbe3837763d7315b2d6f335f4d884470ff7d6129c18e74dee862a98283454f9e4d89007c2828c2d3884c3b52bf2e804baa17503d70223953b7861d0a0d72c1ebb9501c201420f340a78b1b68aeb66e539b58898621394738fd1018473c963bfe7825f1e913a44b55cf5465612a9d258d5cd29591484ba62422eea9f970f361cde3e649ab5090a1316917057dad594e0a57543821057ee1002f8d550c26e0e97f78dec1db91941d5d2013e381e682daa0335330616604cb6da4a15a2d77e744ad4414d7c5f037155b93a6a3306fd2c504a5c9e8d8f65bbe6aefd458a704a874cfc2cc1ab41d8a773e70b7fcd456c24c884d26dc55736f057a53bf5455dc51208d1fb15954b2e65a0d51d162aa34942d0766b3312b66041af55172c50af2171246069199604b9cf39e593d99eb988b246a855e587e0ca650c8bd2e30e6c7f783bb0cd0872a891edb8827a66206f15d19c451c242160d47cba635d9bd1d3d68a3440bd2b5adce050ff32493581b525d214520d4eb536db7eec01a270a335afb820114d7fa9f41b42dd941ba46ca71350d89fed39993268b20f85b8864c6c2c6fb0e05d2d5ce1075e5cae066f7ba8dcdb66a30b354e053d4519f6050ffc9e854c433870670201db90c0cd5b198f64868cefddb89902b86bafff3ed321d2d71b9857b92493c41d49a1ae52a4300832be89e7ac50dab724d85a81455a86f766f02c1e446296f9f38bed2e38245690a8238c43e03257439b0e28bd2fa83178e76846c650d8fffd4468b77494702168142379140de98e7bee33c761b31f534358e119344163ab680289da164d55e0e973e7e57e106b91b107795679c67d584c7d995f1c84ab1f7270dbea6e198dd67fcdeabbd543db4f60040697d8fdd964e72ee1504b64f12b73e6ea642d90eae2aa642a4bea8dd3fc8930c7c8cfc75eedeaf3aa7a5f39392f21feeade2eec35270d20fcabc09e942b12165587c0299052f0c58bec59e67e191246ff17a5d946a055725a823b2f74dc8513fc65f62da065d19287fbdacd73362a615d3231f8a75d3ae507b753400eccbdb295b92541c0ad0f51251bda24ff391c8ca0cc33b2c74be35d6335a7e5332d932668a1e5b9303787d9817035f5b5534745ecd05a98f0917366193eef3383c4d07d38f2414f685ea21675a67251589269839b1b41ba96b7d05ba2999e7c5ebae4fc1ee5d28b69b147d03ad615d21b306ceccfff6eb3dd453e63b34e47a18d5ebed058f499601a62986711a2ef179ccce6d88b1d8ec77af56a8124d9964911d866558c8b832fc9d1ca2665e4ea216b633b8e1b060d87d6ba1227a0d0414fd93ba375052d3b2f1bf83a41edcc9277134d12948704b80ddce24e3451c6f60fd02ab0b255b1c3d4105a213f567322236c0b86c1f18d8cf5a02a32d1a8ab4885831a2cc82c5e2252d3f33d9840e615463b28686ce94b35695641c0a68140df218d329db0f2ae5c99dc5cee30fa9f9c69fbf9149c40c5d04cb874c04428fa62798ad7395f11f6de676da3ef33a5510cd8882ff5e261aebe87bb09beeb55342de44f68669426c5836b99790a60cdb25e927469cd6071a9d7aa04d1b4f772734cd5ca952114feea23131a6cb998cb144ea96575653e294885e064be4a95ad38bf39a2fcc19f157cc0e075fa01f351a0bd3d2d3be22c08491b02f868481376e09c3d5446dd281634f06cd3fafd0a22b6486d6effafd3f2b8874fc852398897e7c5ed8c0810dfe6f77d87b5fba3bb73bd3afb49e9519b1e3f5375673b9b86e35e7be3b8ae96045e4ba13c0b5d04a3d4a93676f32f73e3f77ac17363a3ce21316aefe47aad10cbecea9ef326d0c2599fe91ad385a2bd2455dff29c91f0002658ac65f133dec72dac24f9eae4a924b13b0d18020641c22a906fc5a02d6cc3d4fded9da4981dc0b011ba8e6b2b70a0494372089dc0672c3aff983e55e3a887fc29c5efc1eba2118a1cfd1b47fedafc18da00b508ea8bbb67180c26eecf12817786327632856243bc7ee9f4696ae2af7288c1ec824f85db061a30dfc675bacd12ca2d8dd5ebac59c47c5fb2d2687d41320d399258936669733fd459c821e306ad5c6767fd9b760144597381a8c3b8b57bbc4b08c747b5da1a1f94ff31f342d0c3714827d108988122beadf9e732a0993bbc807ddc88ad3c70692567d756ec31ade8d0ac38ef713849525c099757ee88f64e9d74a8b79887f0b3cbd7a3841648afede9e10541db1836508bd94d3a98ca1a319db7369c96912d6fbc9615bc0ea377c748f5e34cc4385a852236ac81d0042716b798479120d0b55c9d85b63870662434f2bfb1cbc85b87a0476816e8875accf282e1b18d64eb83f8295fe78cf628c4859ac97b11b0944f3d16fc106da03ca0d2a60e4e67f2f098895b5afc58b8e246770d5586cff879578a11deb7317691b155bc068c448e55c53258a9734482142aaa44a7032e459a239109c3dea9c06cf617de7d422ecf726b028f7c153126ef09c287c4c407fa3d2f7138d00a59df7ea3d2a0612ba32354f532b10e248e54efee41078ecc345d227bbc459c43a9a478d8182954338bcd288b3749895901a51175535b4655f696b3322119080d4ad078b3b57c5c6ebd1d88fb190a4fcd49a97d0d413284bd9c449b132e925bb6defea7e346ec86ce2639ae6b5d328d7cd7880b7f483982ac5435a0d618413a04f38e7daee1400c0139ea8c597894a00aab0b742efa48bdbb5b1db73fac462caa1fb1dc20475eda08b51d3444b194a00d596a9baed4b23da65498ea693e19f2d4a3803759a2a2569640e7a50d1d3afd8ce264b2179946ed3df0a9dd820d4758a9c55649e59166df6fad48a56344e07ea9bbd91ac65bb22f21962100a85f358e5b0527b6360c06ec5ba3a1aa59243aeb2dc87220da11126d13e32102620e68cc13b0543ef7a5f7b9a2eeb3c0f1ebf4aa9de67129409b222c063bb0aa75f680570e2c6403396f3625e884d7729fa571c5c04e95161d6a4031a79362cede72fd6677b430b22421a4efda0e054bfd8ce67f6b70993c6c4a4d1e4a35bf6c686e57c997900aac56cbfd7090b02115c4f7ceea36f66b25e353dfb6abf84f2f58b3d8e84dfac5b6bafc15b3197652235bf77210a936d6b8e08700c170d99d378e6b4205420c6793201abda82cfe58cb18f14b986612c24c5224780fc7f444bbd737b3a4f3f7eda2a798ea48095f0ab4333279143838324cbb77ad4a0d795cc685104e5a771e0318145a738f720932913465f34b2e18a1963eb3b9a75d4b34e45780a723ac2befd5be07d050660af76c6b546399b6eb7a927059618d8b14a87b265c45473a6c3ae2362ad6e75aed99f7738234fd341c19ce26475e4829a583c0b93c659f4eb24ad0aec013eebb4333da52debddb36bdd51f0c8107ace1218231e59d8ae3d0fc4640c5966ef5a997390230f66b170781173cda9a26d651af7dc5703fbf7fd8a710cbc261d9b4a4e487a1aad7cbbb9a813d4ce857fd4dc50c90c0c329b37e06b3910452946e177451f1194360a6a8ebbba2e4bd47f22caa67a5e82e09d9ce904792378e023c1a022a6ead495c40cc424a07c393d125f631e9844dd5c1516d7348c7615b4485e33e146a9daf801b83446690df35367ee623b3025de4ac3bc07e9d7ba2ee8852bc583a84c9657eec4a4df698266653f26e60bfadb64f089184ecbdf7965b4a29654a0165045c0457042830466e59c3de9b1f527e9d43422264ff1ffe767b11b283a1cc951baa2f3d6539395ddc6fa7130ea3cf149e6fc3a0c8fdf6b4fbf1e3470d43f0907e5074c917214f309459e68cbef5b79ec435a7d7238c1d695cee43ed6bc5a117650e571c66b8d6b0caea1d6cf32b0e7d629eeded5320fff9f3ddebe1461cc7fc8a67a87a6a4faed9c34e154ed93a535dc5a173f32d47390e9ccf8133cf4dfa05d953ef4ff42f587f70efbed9ecaddb0dbbe6736fb39b8ac2619b9923dcd0df777646b8413273d835ad530f88f33048f2647f7a6a9f3af7db09eb34dd2099fdc32099b9a7afbd477fc3619bd9e1efe27a74d9df8f380ec7a1f63f2a0879d23c6706e272c57ec42fa043f456e11a03550bdd40ddf27b8c1bbd2a892a49eb61dc55d7681e1f9a4c114205430e3e201951b1d0c3142427b839c1ce155090e794285fab1571e8730a0a397e8f0c27393ee75feee14b0f5b3e12a4943247a55fe553797de40bf18faf42e24629e57c2153c6223d20e5f04a29e2bff4e0274286327d00f8d247fa10f9cc5721713da6232682a8523464488ece71e4a22f84620d5490fd9d0de7918b600ff9711274cd3b9f7af2a363577c8a792406726f8329250fd00f6509f6882ef9aef9d18b2f6fe2093ad59049e79c73424993e79c134a99c84413292c582933a0905529276c523aa0f1c0e6f230c58907321d142d76698725285aa88892be136aadb5f210030f0f3e9cc00516d3630546131c0acba1944619a222b283165e6c072622aa21505cc83ab96350a0e4f05bdd910a05282b4041a21a6176505480622487dfa22a12c24f7ecfb7665c7b67772778696f9bfd9fb8691bd82f6448475319a994760a199aafbd7f6fd8daededf6767b6bb7b74e3fdbecffc80d9c3348965df36916d21732344122921c71f1f324c78e3d9992a318550937eca50bfc7f274d15915b3588ac146ef81fe57f4b8f3d9115b32a851bff9b52541d686a6a6afa420dd771dbbac64f5a43cb9ea5c9f95c7b05d9e270660d6795469f149bebd9941737b671d254040b27685cc0c289d20b584c275728a593162112ad24bded1c987228610b4a295da2c3902229260a72c79cd0e0c4851cdea733224bd29416b74189c32efb9ceda913dcfed9dddded6bb2487de150c4efe8fa39e39a35ad3569ac9c5df477cfcf70312744624db8b8c2455ae5d7fa4399a35e07dee417c49fb1731c5cd659daa5a8b833879de748fb86fd08b2c2fa997cfad3e3014248f374d5a76067eb01cd6c5f4e97274a7110992dae1e507d797be0ba69501addb171c4eed81dbb6882fe8c354193e77c6eba07d4ef3b3a4dd3e3417fbef480e4cf28f1c51da5c716a4941ea5a42ea594524e59c27469c6cc34e3b39154dadd2dbbcd7477d39ededd3ed17093d6cc6ad68d9862922c5a9a5126ad99cd64d1d2a435abb268d24acbb411d394d1ddcb7477db2cdca435b39a05c35ef2a224a668a43469cd6c96c4148d26ad590dc35ef2a224a6492b4d9a747ad194ad5e8a1f630a097fabd45a8294c32f934989b91d5b4226b6644b0c87229490492d713bb6a4490c0732b1254631254b1f9e0d51eaf876034239c3a1fc0f6cd811945943f2ac71e394600c55951bbe1219c71d38b35c0eeeb35a1bf8c1e17e90a4818eee6de40eec5ac983cfdeef7b7aaf86c3effb6765daca1b883678a89470817992b620be8acbbd8959eef77ddf98d89536797ead9f3d50dff0ecf533d720fe9cfa86f70d0fb39734e68fb741106f9a4ebf3e77035a6f1523d2a868327d1e1ef6ebe33c5d7d0543fb61fd53f6f673dec8105ce4acabde06abb7e0fd18a8af2fed6b1fbd1eb407f71687435d8fe7e918caaa8fc17db53f41ef27c8b3fa9befbcfd4fc4f91c30ac2f7f037978708ff3dce3609ed5f3f0b879ef6fde7b2f8791ee0384f6e5a7740c6515b62e14fe13e8c21dae5ddf10e2997e67ade720de24352dfba09ab50c683d2c605fcb6c3639f8714f47fdf9f3e78c6e0bc253de0008252d09a83f7dbcc95ff8d36eb3fe09d469baa7efe609f5ed9d50f444e9897e5be0dfa9906e68c1de32b53d5956af609d529e5c56617f6a5c67b570cee99476d26c00e7d188d895fba8ccdba4941287994ed345bdf6d2db80f3ce43dd89c2419247a3da3a8d43289750586badb599fdcc5a0cf480dc6b9f815a4fce3ad3a8a669b5d68a6d90ce38b06ef3ed5310033dd995d59839bcdb72bb5c8f7c5f0dd67e3d3d4ae6ec9940a76eb395840c68c6c1c5ac7091b3dc312b59c4ac0c61a4632ce2ba67c6ac28d9a943cc4a92abb0dcf873ce568c5929ca1d1342494640ee58153699a33662b91d591e6633b2326c69b8f329b62a2b376c0f7cfc701c9ee9a3a0b83177ac0a539e1f252ac175ec8e1ee555c29ffbe19a0c8a45f6c70f0dbc31222bb26e3c70dbb69909fcf02312bc499226f8e10b1f927e48f343955c65290831997ef77069a49472e2569e3874ec99b7237bf9dd79ce3593c98210e040465b103db832dc0b588b66469ffeb0b5c422f34a36a63472c206901e0a85b74b29a5f418a350cdf059f9846a862019249f08a26ed43d13d7d33752dfff8341ee0c5e6ea70d8c1fff07cfce2739aed334ad035d5b5b4deb3a9d77cd6257677d931687314a504a29a587fdb5507e0864869438a375484852c7f38b1cd6075d392f4296602833fde801c9acd1a61fce3639ac41640fed6fb2af6150fca0283f27e730e5ea63966e1348f8f7960f5ad9431b9ebd67ee9ff39e246bfce56ccb1a9ec460a88335bcd928d2799e9def7b1838074ffa128740f4658f99c671c877ac4919ee7cfd7032e5cc253fe7515e8f2e539afd07f375300a7b18060eaffc7091fc76308afe49c3de7b4e2f7d0eeb602aff8381511e35ca5c47f3563b291f9452e6c899660795ff2371c0d4df80d90a3cbd83f582218ce945b3a76fd86c73106fcaa9b761a399421bde64aee9689187ad29b286b18b7ef7da8773ae711cf4267d5b3ee2ea386c6117fdecfdbb0e72e71fdacfbefe07de04c664ba776a5f81ccc8dd874066e4fba80f5b39eb3ef43e9c9fb7a37bd7c561a7d33efe33f4313e17c88cac3d0a87ad5ce71ac7d1e150089e6bea64ca5b86e71abfa0e2493f7f1dc4e42ed74f429281ca1499ca142a2850292273d99f0b5592cc9429a36529b60519d762050d0e2c24e102d3e4410b131226625b92e49a3bb64585314b2cd16a29b59c9a218a25652d772c96060b115fb22ca9c410b144e408234c30c20667f9cbd7dafb080a1df9d8342dbef62beca37b89859c8bf8de1d22f32d96cf7111c6e7e03c8cc779a1231f37ee3fdf87ff9cefbfc23ebe0f7b80858e7ce4bc63a196f4c93ee761e09cec1db7f4c169dd60db3d070a1df970fbdafbb0af61a1a3d50b6d6f7f857d78df61212f923ed97befef78c3421f489fece36b1a8e58c8355c14dfb150c44243848e7c6cef58c80242dce95149a716ea3b472fa59a29675e64858e7c687fb190e7d0de7b1418c216c2918f0dbb910c61fb9d211ed6b047913e3b44361c82d051043a07fdfa9dc3e6083d4adee27b11628a60ca2f01cf417378d236e43383a20aa30a93252987339605c9e68e658912cba23453e03a72283499f61788f1e7e9b94ebe7fdb6ac19b4a3510c8976bcc66ad7493f18be5de6a6fbffb3046fedc2de3bbdd6d4f13859252ca1398d5acd1ece97060fd9d4ce38262068a5ae4e14d4d4d4d313a5082df81317359d7811ec4bddd8d91a57c206ed8656935296316ab969c90c3614fe6be7ed9dacfb14fe9af70f899763f1aac5ef5ab70f5f46d743938b4da4d4e0e0ea21984a6a626a50ca495bbee069c7905769feafe825ff727b0b3a0ed2eee6417b7bd376fa7b2bd834a6296be87f3e65e6e1cde400cb472e841766defcade6e9bffad91625df0035546dc78a54ffcc8c5aeffb47d167ef9660e6f6e5b5e9c5038f9dc8e6ac9e540d6b832ab216bcace9b78cfa1be3f954aa552a97b6f77efbda9efdc5329f0f4a8d49fb8dbdd37f5273f9dbe53a7f7d4fbd30af67c0abc388c71f109d5f57738bcdfa5663823dfe762e781e190f7a9ee51ff13f38df984e71ace3f37125f35c4edc71c60c75b3eabbadc28147de247a2173e68d9c872db1624dde64ef8d145d97f544a2b9d5e3427753a6713d264cad44875e54e26a7af18105b62484f664ca69926c7b92696c52886a585c934992816a29cfdfcec2bd71cfce4c6426490ecb52015989b0716e8b01a7825efe4211a328d82fa0b19cab6a5a7c3df664f31f781f4618012bc1193b3e7e1213ffb9dcf36d0790af6e3b405af3dca26b5c9d9d3a42932f752e72598f3143c5599bd94f8d471de8eec6fde82379f025d5cc83dea55208fce7786f17d75fe933b3bcffd0ee6d1c13c3cbebffffdfd4bbffecadba17a18a0ea73c0d07bfb38a02bf59dddeb913dc521f75605f6802efb17a340edebc0ec2de609e24d9c2635a62cade64b59e220de245fb3f2014357ec675e07f3331df3b3976058b124c9fefddd3268386528c4a3a33fffc4d8cdea293f25372adccd4d18a432bada875dd676544cbac88048b9e85d87792410dffb08c4f7f0100786ae3dd5bec964596f22161ce7fdacb3d6ece6fb687d769c119c4134c7f719a401317ac7906594e2ffbcf6d4a281a538c3149c764a066c2f254737a9a2e2ae799c5a3655b11b44b3f69926c39fbefc6f81377e375cffae8f2d567de0760e45aa50f2441bdfc1e80bac09d659bc8078390f2d660b187c70218623d816580fce022a025e699a8061f01a418b2260ad170db07fa980c3eb0358c06a801901f35eab576ba9a0bb38033be173325c600d78b5f082a2c1b4790186044c0a58005e41c0dc03f5d54a2c0163c28a241805f7a58363e92d4ea44fe02c78970e810926d00a10040d599098c10a2052c8420929301896384111484dac9481ad81edc0b04105ec0212600778a981692f2925c051029813b021ba97cff09a8d2609987b51f4ea2e555e36446902760616451230a6540f309d97131815300164f06a2f8678b3214209356848a0a4e80c931ad86759966545322c7296c490d79cdff87fe830303530229883498225e009931bc694c0480a5dd67c4102031730196052c04eaf0294812941c3b918c0abd93c811d09f3056c890d6cc68b8ae7d04b5fc0fc0a7e351364605dc07a0b226028fbba0106be963408d4bc8ac8e0c551180196d6a29fe0e244a4d0567816aea5a3e82a5c8c1be9a65ee35e90c0fa882960ac570930ff01e668b2a11f9ab821071d40c084929a34598ec0629fc12245d1995811138eb82289a6a218b2c06ebc9c78f51550b852c7606370b8e0745955595561d226872d196dc68a8e3129caf1675891bb8a3f8b5ae1ca77f7f914a7d05cff785d521d6126aadb574382b82ee73711638c1d1d04dded0f4276d0da3c2b8d351823a5547e1febda6f65746954c311e0f6e40670bfe81eb100ae2b80ebb93debacab1a6e002e8500ee660371c12e381d9ebf82db9302007097e8c833e05050a49432066b8574bf18dfc3e2b926e07674624ca90dea4e9abd69c4897922ee4cb39b10698c1b59546e7873a5b456e969b8eea941d41103e9d269fd90524a57425c29a3ff4a882ba5940ea4cb5102f1f5c088b1bf2fc707877367e0dcb130557253c0750b11591d664af6bc8261f5c20ddf86cf2f79467614629491c60d6d9e33da4843e2589827b9f33c0d499f801359f6f487bebebb6719a59f45faf2ebd3768f79a87a8481d229e9b7a493cef8f2ad836df1cde5c9f0c7923e94decce8845b5da2a3491f731d1930515175a508ee0ee0acfd550d3775b3273aa2b77b7a228b7e8cc85a21ddf09b34e27b3b9801cf7332a9ff13a59c111549e074783ef11820774ca9a76636e68aadaae9b666d8c880a31533a1157b9727e28f257db25fd970ef4d27c51db88ebb3db7eed62ed111c5257c8e2f62207f82373fdc9845b66cd97ec114b9634a620c9077c42c23b2e4b3224bfe6a841bb26454ce2b183113af31252e72043ba6a424b74727b63736e17478ce6e679c0ecff54a9c1b534a923b7644923c23772ccc133908bdfdc3cd4b7424898e3c7e2c0c0a3928e6205916228393f1f677e5fc121dc52fe1731cc9ffb8be4447fd25fc6928c7a18c3d66c79347d43194e7cbf81e0f0a60907efcea6d705fa82b8122a531bab4a46c64753f572edcf82e4ee545771f604c0ccc171187fc459683a83017cc961c763982a192fd2f8e31ca190343260606cd778c014919253b8440071d56405a01f665842f6dbe1cd9c248962c88880163440e4854c01021d1e21341ae67dab06133c4c20c4aa8f0c408871c907c105304e5a50cca4be6925c08810f5f88c0982c71c414274e4eb246258b91942f5e8a723d82a58af602bba20612b82059a2a487335e46c8d245082e485421a5cb17234bb12e5974b9c2a76baaa8702f52cc240e7c63e1401451c4b834896da9a27a820b12112754b9635caee4993bc62592f7a8842dd765b5748a660400401000e314000018100c8784028140281cd05559f91400098594406a4498c923811c06410cc2300cc4300800200080000883000628a48cf71c212744258462107f880b3d2486901f88150b8f357765493dbfde86f151f1da094a12c3c3ec7cfab10dbe3a993b1a9e2bb4d91e1683f0435c865004f1839845888b054f7d75a5def0acb59907c41fdeadf9703a44615e139ac54b4fbefb5d0829e43c49d22c46781cae2284e7209285d6e35fcda2c9932aa7e76c4f9c9b4549cf97db0c410f813844f9202e163c6976c72af1d817d773d363eacd22eb49d316c589a7371bc6c3ee0f470421c51f4fb683fa70f5e1a04094b5eb8964b348ef1970afe363962c57411194879d13f1811fc42f215620f08497633cb3272ad77386e7a9c9f463815787268b8607d7dd85ace764a3290f2afa3dd96e4541fc7ce119e73820de02a22c22cfaf36cc0f16769e3347ca077f3e287b58cc07fe0f162db587971e4485e8404c7b28338c9a0761c26e18b28d5eae01711f4211c40fc405441687b8f0f3748b8322da736b453cf87f5adba42054b4f6846b1d88538650a1e8992fcd82e9b1aa2e4fac674f9b05898717c780a07bd0c28f14ede56290c1632be77396c7e4487bf89f524171926b8585b0605b5090c02bc1db8e4144dcf7696980b838dbe3a45584f0fcc72724f71f7c24cf278d2284e13f5244760aa888e8f170972108077151e3492f67d6664f95430c883c153dbd5cc3c3e9215428f6cc77b360f45895631ccbb36707c5694faf86e9a1741f8bcc4eed24521ede340608fa87c5bab93f54253de5c2d824b83c92db4db41779c286f8ed27ba03f8567715ba8e25107edcd83a1c2ca8963977e0bc40d6dd00091553956625db877ecc18843164819d51951c3e4fdd9eab562c1a8b746e613dcfdd24ca7ef84af4dea201c2e8ce6367f7164893a4ddc135dfd4e6b609af5bb1e1d915c778ac07cc5dcf538fbdb6141b9e6b4db2106521f678394d0f753e48b107157f8f769ba621ce6af580ef38105411424f82e737370c089ff73d666e16939e5e6ef30f3b1fa638c4c5bf279b2d520f2aae3de268da01b1e2d26366575629cf77ad9820f0bcf3346b28f560f1df036c4768544fd89ac5b827a8dbfcc3ee3eb1a629821c2e69aa7e962ab999cf2070d6d46d40060f7f0e94f9b0fa7753d845f8f015c21ea2280fff3d548220fee12f04650f88fef0efe1720f8a7bf8efe1b287c47bf0f770b907288658f1d7d3b2510a84cf164fc26f163b1e16572875c15208d618a18eea90c836988762c3bc75b0367b6846b54f5179e8ee68219fada7f9c57bb60f66e29b8a6769182fd435b5720bd2319f81117f7651ecf244ab5944f7dcdc1b7fcc12b4e3269be9b156c34020ee837f0fb2f017c6657c576356132f51b084b4ff95185f11c86f053d4c0d85adc42a0ae75f4ebaa1e2ee79de977a5703dcf070103f8a4124fb2efd3f5dddfedf4eda61d18ad7c6865ae005eb55b0636ea42be568a0bd793972044c6a42a050fec6214e238f20f8bf61601b17368807295f7311e1eae2ae67a354ba87b4436c9651d59b7a53dbdaafea74fd4eea1d2e69b5f06a65496280060589538e988d46a1b44887d44c6884515c32f48e2c6839796f08404b3a1aeaf5d951bf80eaa0e63b0ffb7e1f2442f81d5890024004faab2da27a31667aadee075354c938f2ab7a13d58e2c4ecee9542cf12444eb74d9c58ee4f72994d8c74979ad2dd74c310d176eba92d81164dd840819bc654f6fae47b82de618b86470165c2f08cbc066d199bb668f552d1d1f677c01b2be74fb2351a8b771a59d1c2f5d8434d30be140f39c8bbe74175d7e655ec0d40555a7e729dcf830612c4e7c6c0a93ce20d84fb187f5e5bceb871613b9882afd784cf819b84fa5134e6f53a44d1d437ce7c0238e6650adedb005a78fae806d490f5f2ebbb09263972c268351b15a0ca8134d13cedc938041d9320c488bf86955121b8f94efa6a9fce5ceb6dba5d082421a60a9f14ff668d0203ee3102f899a04a833f63b90d70e7a19b8076c3709bc576eebe5328906602b8494fb1c85f32aeba24b82a1ef1b6d4b19040b0e9b9042d1fa4499cd71041d5db91e31714d9864f696ed8276aa6c2a3a21ee8f46c50e08932ce156728a569c7a513df37e18beb26024e33f26e2681f78b0f2a740759ccf3d1c49151ae1857f306b3065ad0be7084c32efb9d05c380616b5b283855d84301a6b2849801340eccb71d66a53b7da0fc449d7bec2682cbd9e3d0db4a8b4b8b9de1ada57558c98af6a0acd2b839d6b339d9d572608477b12f642b8e4408fe972a83da9db1dbfd0c74b7fdcdae52db62735d80a392a21bd8231342f59f48dbd603e02f0876a3c218f23f9f20a20d886f82d1bd8c898b05145d1f289ce78568af2cd5e6b98a0144b4cb32a21fd499a8446259dd3c59bea94501b699fb97530c5c7f01921c473c69f4d8cb66768ac3824c3fcf6de699346864535996366c0a70258976272e81c2e8726db75b75f1126c34ec42c6c07b91cbbb8884c5de65517bc0f0d7122f273a410a9102bb0474080671ec3936260c3c800d9edd1c5f97c0a45e1249afc0a744a340125f1755129dad5dfcbbcfaf6d40b64406b9d4cf17a037b0cbf3c638b53345a2a0f68e256e050229e0247b1722813cfdc4cbd24cfa4804e367ee812ff2f297c0d4f71925bbd29834c5b862fd30d9f66cc73bb55bff099eec26107d6e538f9502aa3abecb5fb09b2b627068f8a366f65576434087fb209805f1dea862704edc7658a102d233555b747531249bf3f9bd4b80761a183fc217f5088712bddbd4f3136693900d0413edf98d628883161781c6e4f7fe7dfdc726888548d8a140eddce8004551e0688da28caf14ea9ffbf11efa3d0b29f05d9f95b14dfe23bb82d50a8932789ff359a8814b140276e5fd51240e87045f21912cca4dff67c55cfaa40b36b4ab1c88fa03008c39f06cc4cc83253ee14d4533b12e124876c736294ea57aaff4a782ea1cec160d682791fd4d97acf72b38ffd01a98140c1f925d3b87d20059815fdedb86a0a8d450ba15b0510f4a2d38fe814e010711c4082f7573a8b9c0493566303ff1cd8cd477a0cfa4610e2dcdbb419256fb1d5fefbdced9df5243037f72aba9d8a6270b00ac280e503f6f1c71074c62c6363fb61b423b2a97ffbf39c72df2ae9d4229ca3707c8d4c57509314fa050c9805a6a4365d51aac09d15f7944f5083bf955005b20790731ec7f8cd1d0331760afd757b1860411d1f4da115e36be6f91bce11737ca28680afa0f3cb9e14538883557019ed862d41577cf2769411c0e8c692c8aadde3bcbf72d2160f909c46ae0cb1c1b78cc9a1630fd88c3cd0191400cb62c0806b2fffca459dccedc109a44e47c9700b3706b0f1e24e1d05307a5677ccff93fe31dbfb91cd92f9648b75dd1395877422f19709cb7323a73e50e0aecec906837ed2522bf8161d7f3949fbfe1f9cd797255eabc885b8b4e5b79bc3b77bc5538831efc4e46dde615db53da515a7bd138fc684c7d11f4b80d101a5c883b639d7dd7fc95f0e37c9a618727648b5c999fe5dd3d7f3d29aa016ca3178a7bbc0816668b4d53c722f5d0517b442f981bea8a8550c97e2af464d297f0611247455d8549035e9ca5e143e3ec9e02bebb0a8d53490f08f92f2d5242cd6b3a930a85266b7e6e32a57fe9b58aab7ad082b831b74b4800ecb491bb9516cbb1c31beeb79eb32cf82730bc425be29af72aa5ddf8350ff53eae88bba283e5571ea23d297a46cf66b9ea65c05d4934d5337ede8e2b7c14f1525fb13fe388fff291a518274a3f4d38a02a126a9337fe12e7a60ae086344b943b2aed4f863a5f9414a35a13f710381c9edbb03f4ad4bc2607f512e45bb8161ef9e7037a2bee030684ef2091d28efb656abbfce618b755c254cc4431cc65d9c8b1d2bf9ff926e98800939ced13cae43e6c8e67ee5a3394fb5001678c3968c3c4bf202c76c283a1a8bb802fd883fa7b49a08c438f10de1c1a6f1e3fe944a742eaaced14241df45b2ba47f9981c1448c0fa3b53f6e4ed6afe5f090bc395063f4809d662a6bb22ec932cb6bde04cd57634fe3eb36b88fd4e74498d2683a2d2e051fdfa0b26929c62d570722ec70fec07ddc0539ef2aaae03b9100f5865da8c8856e7d6e975cc20280f394f8f9165bed4009f99d32398a34d888b18f5b3582a0a4b2f3e0b84ab43143a0a4d122a2dc8e5ccca6874e397a393d6356f144a9c837b3c361da9901e10a7863788e750ac1e86817432babe3f415024ff58c8c7eea60f8eae358cb42f1f68e7fe36d017e3cf4fa063a2e0bb9d54b2ef6b0b4b871202c85ca7484cb65d60856228656a2542f7e233e17c403aec6f68b00946dff33b3c979002114a8f9c8fecc88d5df232d766c335a8242b8c60c6fd7570a52d3021b8f1042d838239ee8a89e10eeaf88669b096a985b830ece88c5fec795c8a08dc4ffd7d7ab72feb68c8193e3e45a376c8e966ce9902c507cb19d80054ef99cc177fbf8f358a42d13f46028b9169b24f2934d1c1a82fa705513a3141e0b1a3bec26f2b8b9fdd1169c1adb1cf4f4f348dab94b4911c517a05eee64505e1a84c290b9a2ee03f48a9f50f94223c33cb18d7fa49642e6e09a764231a6678393bf27279cab9ae9d407766570b30466f80be8a26e8d3445dc6fac0c269e6ac056489dc53afdc74350217089564cb7560d1734554759f684296e1a96f53097762d7072a16f006ce6a663e15ddd3031752beccdd229a135992ce4d8feaf7c200e8fa45e21c6e8790a7c22a5374d465561ed53dd17454d3a836ce036e147ec676c011a1fb6d8edfa4e7e898e72a7d65d33bb8e66c455505a88106c61ef57a3f5b066141741a31ce1e9eacb81d364a159790f70d2b5663a6595519955e00c6048320c7cc3ad054b6c70b2c67eb7d42367694e06b7fc86cdad8db829f08e7c45a5d5269b220cab1cb379f44339c59ccad961a61d08c9036910b3afe34b5154e3affa6ae2084a08a7633479a929501f8278f70c19fe214bc0195798d2b985c73d0d8a018416bdc39f6e6d4fab45787f29c0015a619584c95c62fc2980a9c72382c6b0144081e9e6006c2edeeb16c45e7a4fc3fa191a0035171fafd2f493cc552b8d38a4d62e28f211ed2e941f5a13c83d0d73f6169245aa6d2c07b26ec2a54df07d17d0f644500042a808d1f92b6c6de21f6c7096f89a775f518127409e36b2b46bfa9dd1215417b6b65d4185a357037871d7a6645bf3e22507888c606325c04ad19fa21edfd24d6e5b01e2c4106bcdf9602e21aad27c8c91b225baa04772e24542594d14071c4226682917363c69817f861bc5fb4a0d41c3aef1acb5fd2d75c28405a72f2251654169ae9fd61a90cd989eaec6cfbd06af5ee3d2cee6d952b66c10c644b251bd8ea46bb596d8139d21cf2235ed7c0c09b543b21fa7743463e40bf0ed0c65bf10a66f725741dcc8cdd10492f6fbb958fc643825367c01538314199c1b88cd8b98a2adedd70990023800925a010dc59396582e877e85917898f454d344457b0eec548be6bfcd04820d070e5cc056498b30537553d9719a0a238615cac01bdc18f11959e33c806472670a8a8fb14bee2f0f3d65b78d63096555889feb9b9ab252db05249fc4147642b1e3ed896156cb218142a4c927c4b38a54f652f406db3426b76350ddc7a9bccd1f01abf5a67e91c3caddacc07d621c05a01839396beb951eee6f6a048342bc7f2db40535dcb1ccda87a87fabbe0c14ca238e85aa0b3425fd5bc9e915367167c858e4a4bda3dc83440e42f6307a5af776a0c6aed27db4401657ac27b4cb49355438474f0435e10dfac936c57c536bf4524d3f8e22e84c9883b33081c21a7e06b818db0214c26e69ff41b7ac58cd69de82f965fd391acfe1ab1b9fae741246ad9fc43576463f98b2846c50dc5059515e6494148a0299b4db34a47da0588e5a23036efb6c4d88616d8ee82451a0478d5e6b3e82a48743094271605bfeea0b6a1cd30b5b40f833680365f8c0cb125f34160285dfe042d8463ba92cf248add1c86203bf1556c45c9db5600a015dd605b48b76a532aa618a0acb0dfd8b25bc675f22fc592bf0683fce891765b9225cb148e5fbcd7b2f19750a4fc2f88b8d708c211559f205ddec161b8f8365c43616d1c5f1b79dd7cda873e5e0d4e214b7b6f72235ef93dda3318827dda633b7b4f47a2ab9985a1168cea7c608c9039d2236e01ffba221b784c4697291c4646ce7701c4e827b7e35fdc234f4d101d43dfa7bf54ce5d9db496d3db8815cfeee254bca13e2bb26f6d811fe062f05dafedd702a85d4dc194a22125a20996a7906ed7097c214671a513e6d88a258f0fadf4a6698399ec1898d4c20466eb3288675d36851e59bda2690dead20baa61bb74b15a0491fac669009b2e089e81a2da8469f4096cf698c1a397a8439b6bd5d0947fc4f5cc81630a51191a2f3877535f56494cfb6f0ebe24921f57a4f2c4a459f76a810b5b4ca5aef20c69714ab39e1d0d4443b217d5a701c117c75ce1ff07559a23ee7765534c84c2cd6992d28b04502fab15b00e354200369fd0af0f7c88c53c972d17349d9bff4c2bb975194d11ee107f58a968d7aa5eddce416d238f35b9b338a12334109941200ac8388b4a0c07d8b8de91ba9498b4bf0b9af5c754eb45b675df116ff98ca6d911a127c1478618cf9361395f8532d77d4f0b83b3f101a17aaa241c31941fdfcaf070ca57805901bf2cdecc970598d724f955fc4d062a5c0f4e0f680603acf22cbdd52af4894a2fb62df00b62931475fc2511c36bcc3a1db203a407420310c7960a201b6009a2f1a18a940d3321e862aded8f23ecff0452df00f6d4d66366696882fb354baef7da03aa2b3c579a98700adb66925792f16e6eb8656c18d220f2682b3da1c6dfeee0932137cdff9300cd03300b229994777361542c24dac5ab90c8def7de51e13336832468ac5df848a37f616050db561622a8e4c4cc1a2eab47a5a42d997656149c87eea587b540e79276844a8cd80d2c8386c5c8ef6214f5d46c49293e7b744f74f4a76aa562e35289604518214a219f353176ef2bb190709fb4a7900cab9a9ff55e6c2bc3eb40e5a98dc5c8393084a8ac01f1be9422c2fd24f6d02f144aad219d390f9a8913335d671f361e6de264fb30833b3f6b743f4d5c79a42cca13cd6712f3f03c5c47cae9def5c1989e298cd8d703b8ccaac03fa5251ae8edaf3dde847327de5d2a86e5bbc1976b8bcf949ce3fe25001cb15fd0591dbfc01af6e69bb1950d55331edb3da851d1115dc11a06eb8b6d388164f878cd2e194584137179a8620c8a1989a07d8faaee65fce999bc662c05615d44a14b20d37f9d09e098b5609457f24cfe0bf91c19a25d37564a9b5e2fa668eb388c1a35472106a5a235a6d0c1962cdaec041d34a5001b0586d4e2021c546cc7b199d18225f8d810d4a75db55259b9832db1abffbd8dcd17a656f2a7d402250f47b2eac647fcd3d4599c69d3c27324b28e690cc6239b3fe4aa051b08979c8d566c2107b5aff9a65239ccf9a49cd2d1f97560e38244054f8291a9593df875a7b218e51576ec5df2791c08ea06410cf5e882c6dc739d8d8d7b7442ca417452d12e8e0fa74a738855cc8c0be528bb95dc3a30641ac02754808d0148d5c3bf7abada96c4292cd24b41b9ab4c3383d2445628808b37b0a8bf1a99840233a733138d93d2a15d34e838853c1fe863b20c60eed89ded1f4d2c73eff8e2889ba705b9302ce21418ad0a981f52174e92a4dc2e621bbc6909649e2633729d83a02732bf73ca7021d458f877317189aaee95b40075df9cd6d155b495d2d5b1483fabccb82433c342fe745638afe71b890d36475a1111a29a80821f9032e3a42a2011a06801b9714452165f2413798962154f63c57fd811c6ffc7f23114561029972e0fb123a23bb5ca5a5a971b94c738379f52c831a30dc7c55502c01fc3bb75b1e99aadcaec7d46dd3ae28c684d00336599e9e999405202071377dc7acfedc41d52a940e8932761827cc47c2657448c1d36680e4c09fce546b89d33c3acdf68fbf14f4f4167e90a85dd79be6bc0f467217f04844093847012b3fb5639c031570f8a5d6904e0c9ca6a6d5a478032655e7996b34459a013b0bb0a153f8e84eb158d4c823b6cc686597f8c02db635ab5c34d09fddb6ce289054944f4d877c1f111f005752f448e575a00396d012ae908321dc75bb8b64770a71b5ef9da5334d44559315eb976287afeb8590bd6e755fcfa9db24562ec23a99e45a305bd0c34d102ea836d580c8a5b916dacf3d887a212375a8221a57e2922c96be982be3cd0af2495e0de0b7b1f2d5c81c1e96528cd1254902915b2185abc2d61e1c5e5047a93ba39f8975a31170f11ad4cadcd0c4643c5cc67cf75aaf6295fb344a12de4f2ee32c225242b70cdb75fffbe339ffe7b8a189b57edd2478cc8b1eb46a7b6555a72dabbf2d3feec19f08395e49e95aa8282dd38b7fbf097edadce1aabe371e2a054918831c5bc10ec2ed0e3588328e1924d8ff1480fef369567a52c11a7d02acfc19d2adfac3af39e07d517b04e5090fdcf0ce1a2c73a5d3c012ac6e02b8602b162c23eb4459580d9aa7574a9aed4157df359911c2918df430988e042ce86bf9476cfe98d193f971264a580531a1c21ce3f4c276dc02fe0750a669c67ce09e9882cf0814bca45834ead02d7df627fbbc02d0d56f55b80869cdd3202449ec93af0e27ea50f7e3469f0253b4b0c2c4126156b22af84fdf527462eacae850eb1d1adbd0ab61bb8e348c991ebf1797d566440578d0ba36c5da86a6ccef7d4e1e57ef2b09bdbe4d53b475e5c224d00df9dd99381e168a266eba28edb45b8824353010c845f8e370618c8b22027072c7e7f2fb0292163b2f1a814751d947cec9954465214564747c68e8af4cf290343ab814186722eedf9897b360de5cb70b675982be4b8fcd6d83cae5994f34f82857c843da381fb6793b9f80dc2b4077b29d8e4d4b98bbae98a504d85c1c365033b4e4ac97492788f7f2667fbe2a5b8bed2c5ac7d8019460c9f49f345d8a77a04fb7f8cdc2eb3b684ce800a7571bb1fc17d42571447f488ebfc63ad29112c86290b9f0372883ffed61078286d17719f7c7fb8f728299fff3b8ba3b63ad33f14f9478b2c38c18781bec62fb2218d589b1022ed69d70a183560ecc1e85a5e9ad38c82e58b49af1feb01e1bcc9d828af7402d3f88e7e213aded919c9a56618a226546556eb72e2bf5b01369192ec428a1ace4cd814a0d7ae2bd790ea831b5143f1909249124f72129e86a65e1229bdc211f5208e28debdce2a63c6c572bfdd2d717649e4ef574d30b0cd326df3d03502cbfe7f873384b1c70a0ef847582c257c0eec063383540be75b56de3af333e6c95ab576221209a2e8e2462f1766f86677474409be604a4121b8e749f5e8b9b91094727230d87b01ebecc5be86623399c11c3dd79fee4f52ede77911e41c19bac5ea75d8302d373450e1810ff752f97ea5d8497eb1fd24bf0b6e4980d60f41adb385b463894efcab6e3574d1a851f05d61f75c55add0e797fbae56f8c91b96ee9792925db623cd6afc30f6b0c27676019f3a19a4afbaa673e040f85f06df321b309f377c0b12cd12dc466002a836eaec499037be957e051259db2670888fde752920f3ea3c69d43bbe890e9f1f82f1238c677925a09edaadeabd60ac7c3d87a9e748b8176284eff7e5d270ffe421dba3dc6102bb989701497eb412e45830ebac40b7f5f16d5d55d8ec0be8b170343b215389fd80416966a4ad48f370cb024519b9eedc0874ee264960b77bc0d317d488cef517e46dcc6d87e43eaec57195c772fc4b282145a00240fe0e1cca127e43069eb15e4fd96d524c50af83cda354a3751b899d4f05809aa875608135401b4cf9927617ce9e1c21d2cb69a7c1751008dc307d1b2828842ceb53093b86029801c8dd4343d33612cc2483ff72f633f6576b58e36f1824448af342a37363929ef464eed6da5aa9e1e085ba0cd1042353b4ad558e328b54750a918b1398aacffcb45ef9435c63551218e155b1d6c5362bd87b79ef3b45a883133a4c8f592f71e91d25683850bf1f96f1d1dceab1b21b96c71e37f88fc43194b54e4721d6f18d80a26394cc038ddf6547a92511288eddb7629ce98fe4948e472c4f9a190c914a084289ab6c07aef43c43ae1c64b721b9677408bdb7f765ff6c3448be8c8ecedc7bd9b62d13fb1af9d5182d6c14ad72528b70b25b1240a8fedbec0d9b58a36d6f9f9ce552b1ddeb60bb41399dcdd2747a5a994d271c7b1ff923feec9a64a23a9dc4fd04b046873a86266a07cacd85b89485e9962570066e3ba5c6ca0191f52f38c07fb9c7fe79fe0b18ab95eeda403f0c0bc5d27d271396c29d409d66192e454e4239348b23bcd2e80868ad8c3989f3b79278edc93595b8fa22a1d9b49443f00b23e0f6e1751160072eac8b2f1052f68a4d0fd2eed9f938a085e49b225b98bc73685ca142b5e266b84ebaa9334e05a1a35943c00aa8210e5cd1e7c0ac53bc833f8c520f809e5cbc35c83321233b3cc540205cef01ca0726cd2015ba64a16858050944bf80420716d2a2bf182d1b07fd06fd40c346358ae3bfeec024a9db556f3e9c153d9e0ac7854e6dfb9ec96b067a305291c4765a8f7901e1046283870b3803cbd6b94f8c3eb4fbd869a56e8e8d1448ac1968aef2919c6e41f11c24cc4215c25584ee5b94d28662c4213e62837544cdb05bf50bdb1ad8e206a8fe712dc457df2769906650bcda251cb6f7b99a9b2a383e674b614aa2f3c12ccaaea1569a24132d9a8688ab3a7e271a6306fb5c1e48c5df372300d903919e22e2d8fbcb102b7d34dd79e25ac949cf8636e05af9a4702df0bb59aca53016e1602060e5a8aad22ecf9aac0abcba019b62ef154908bc38d2415f5cf12c1dddecac758e4a475738c700d0a2d89ddc078ecd2a6a9c7258da2d6e3899329b2e9611232472247ffe9d44b8cf1e0bc252ff495654181a02dfdb3bf2a38d669d3a30807bbd44d0d2062475f5a6353d7e8aa6db076324c7f0e1f1f64e289b431cbdb2e962d1881da9a3f60fbf82f051fb7def6a97c1e2d647bd4e68f8331990d40760845a09cc5256c5b767969059365248190ec80ecd33f384b19a54e456772c28c1e5173165e2e0f8c1c8ce695d7e8f554ee9f160b8fcba510113e945c834d1a5b35f9af27084130e59621fd75796c96266da1ee8630091f786547f4b397ccf3a4048686765a72d6aea5a54845b6daefb2f34494141bf12d304557dbbbdf9e41931739b961adf48a798a737b15d9f7bbae9ba9289586ad4b286cf9e51a995d7c6114eb8ef049eb8023102cb79a737db1568b105d0eb567d1df95f4e7e19991060a0f338296c7c327db872a531e2b129e3c6cdd31569fce299c1878a4bdb630c071e6f9c16937a9afb276cdb3a9767e01d3d14618afa6d2015736152101951c6b683e552b8cd5ad60e78b38dbf546971921640b41866fb4edb39348ef146805774db92dd5e89fe66d85fd34b312d3de9761875edce8354ffb9bf8be9135fd1f9da84502ef79cadf8150fde25837bdbb91d484018dae660a4f692a16a27ab48eb43f2a98ad9b58e034e1abf8cdda543567b70e438a8bb04bc2152379fe347349fc9b6989cc48c678d2be603782b8fd910b04ffe4472fd554fc4d326cc9a8c81fd7efe6b2d263aede53b9e4a141997e8567e763960e988edd83ec83beacf0a6f0abc94d213bc960575233420b8e74fa13dc8c3aa6fc62f197d689337b86289250a3efda944ae2c149607334517f7be599e7c83f956be75923a03b33d0de493b1580889d143474ef1d49b213e25fc63ee228a15ae26a8f894182d0536b47c1203fe5a33a6360c7accbbd141dcded70daae5d04c86af4d51307bdeca18c85b0189a21e793cd45e6016a0efd5a1b5320bb3dde4746a4bfae063b719605ce85fe408ddf67d51840044a6f719ee4ddc37a3810a8f40d53659ab911a500112139cc097ab18ec66d98964a9e016d41e149d486f2fc8496496d8d9cc33d8b85648c9a14960799378be7c9170b7b26389a5cb986bcad1be453ef889ddee0d6b22e21c54c4bad48a84ae20a38a9e5f38c25052964bed50464c9ecd30a45021677ab4014b90904227fecd9e745a928d328d6b5c7e8555f6e71bae86939fb79b64d7be161a26e7eb1355bb8d37cc593f51ee18ba70012a153804a548604ec406a8f827bb12cfbdb6aa65e9a76634c6763902d6be41a425626c2ea800a0382dd777dd3014c29402d56123b43071ca4560b76818bd2d934d5eddb4b5309489e9553b2c0fcf276137694f0b26343fb6d872005b4fd9d23438c4833892165219d40ba66c84bb354db6f419fb4995693fbdaab6bad0a1650e9f0fc57da41784a9b829c2f5635c5c8cef33a0bb40de35a2ba260f9c2f56760b4803283bb206ed235ef21f20d44b0befb2a9463741ada74214b2c363aed9ebd09bfc22fa38615c2f70423c17b9c08f2d70b0660a19ae5a2ae907706b9cc8fc03733876998b3a07510552e19d8c28aca7ce653d7077684f243491136f4327a089332b9eed888a19a7ed41c9282e7f9e37bdfa132325a467f9b221b88e1cbc2d1a55dc5d03bf53518c90eb98c7dc8a40189dd336cc1b6c475e9f82944e186c6b5666430f4a89c382991d28cfa03af2b4f51102e9898ce9ace1da25d907bca253c0ab9a0cb6f7464f78bb6b44bc51173a11311b296773d7b58120c04641cc24ef681c6de7e91c1c56d23d5bd0205c604dba1cb657b184aabfa252d038af82302894b4645bb1073fdb3ae10a37b42bf16c008dbcb5fc5d8ad39487e8b2d3ca50b435726d7a051201277e7b6eae3f35f131536f8e020db4444530ec7a5b3c0dd8759146f580dd18fb834fb72e28d90848b6181b3683ebf6ddcd7f0323262179eaa22a48c4b0a21209f49ea955c91b36b798b499069effa4eb3c9262c086108e01b4bd2348401aaf2bc9a7114ce6630702955a65e37267e7ad7c9476e2391afdb5ea633c78f6062274fb66bafd021f04f8015279d3da6e9fceab0cca078d60c53bc1da333e1952b62a54073075336952dfb9d717bc50255cd9af6557987dc760de0d86cd0b52cb916a9e4c5aa55c0c9f2288d7c6a1c440b6bfc70117634569ceea24334134ced79d4e4142b93de74260ea5d0fbd01e1df3dbd5e9d3e77c20bf08dc7171dcb39400a4ce53f7d10cc41299dbb7dc16c229f0475147b8481cb7e6c43e5de8a3ff9c63fd32b5e30b9114074dec13201fb5aa717106b5a7a7ac9b3451d2e52316a604aad9ab30cc69b21d878b6e6ddffe3371f7d481277bac9db899fe7dd629f298b727fe9e6d4f42884a167e92b0e870521e83d8032cd9d045f21b1882e362bee7a3df202826215861a712754cdf74841f282a0b5619600d64268a469dd84d3428ff118b94ab2976d625f9753df3e7c244c96e728ba2785545c863c5f3104837e9eaca8874cca13c3d9831a299b85eae4d851c4799228ce06077624ead3ecc625f0ba2c98078650903d2f88ee8984528577071a94c6dc0df24e4607326e8d7cba6e4103e6591b2777d04d234992d8a8de6ee143b29fa887f9afe3910e5acb85a6810c9d3c4d5e7ea7a02353c521888b18b8db54a3aa277df887208b327c08145263e15b8b2c142826960c2e33503232d703d2546ccd4d50647bc57d1f5cba014db5ff8bbeb6c591b0dff569362d8a2fa3acc29b4c8976f27fa0e215cb5b403a020a626f213eedd7df447e1ef78cf8449deddd1f11462a66025f6ad44bf449f8f7cc8f93840724420022e37cd44d83d017c96935f48896ca3ef0a75f84fc5ca86b2abb6180f56a22922eaeabf0df57c8ce53c237d46fec3c2591619724091331d99b9a095d4fce5b6181f31d3bc81bd6d35f25c99ec686adf9c83f9519b2a6bbd6395bf5d51bd04caf87cf3130cca099d29efbf45370eabb369ec826a2edea1660759a4ce2499fe05beaaff2f85c6a20a5dfabaff646415fe4695ae414f90ee4a3936b92c9979e31ae39b4c1cf050bdbf756132bb1a6d92bb27435d27475a950626f45962fbb86369c863c8da77d8a68cf788b1b00a8e557494f7c10002d0b529ebbcd80e7aa730242fd5d3cc590dffc9cfb8f81d22144e0c21557ea5be9c166062443072613e0da35b28fb04aa9f9c613f4e5694e5e94d1215c5b2b0bb8dd0be3c1f580afedba3ac065cbd06698879e0c82474fab5448862259fabd3fdb63ccda0c37c2bc5375cc1407e345b2becac34ef8c4affefa5f91da398e519d8184d069dbd119acb5b19b13f2d5251ed3da9a2e84a42c7b89310038c4617c05560ec8740148442fdf6c254020b9ecc03a305177694484b6feac01c3e7ebade4ec7500b0762c4828f3c205193d99d78ffded336eb69f7a8f60b859c18b2600e7879735b8b8bbfb36cd5eb0663e4a1100d47f741949da3451307841caab0bc6f09eec392b8e1107930ce4d5635bff25a697a2b8188b5ac7686c4eee89123b427d4a914bc1d0e297d168354cfc880f142122ee3e91597661ae1b2bfc903f085f8ae3e94dd61a896e3e3dae17acb7317cf1e2ea203b48ec3c2986100261eefbe792d2b959e087332115e449e55736cccd006b67dac9de3bee6914f2f9a99dba14609c66505e3e9e6214c19acf8ec65924f8ca523325efa26fd0af85a0cd79dc0861ea186d902978060a7bf14995c1d08e0d0d714277df565bf6640c12626197603b94b19e53e1bf5cf15f55fc5b6f78ef4a64d485a10b5badedb1da081035349fae41ec66f4e23a59d356276ae4f948b9348ff300616d314eb7c04f633a827e93fc22d1e90e1bf65765c505dd15c2cf4c275b20ecb9c0f59be5848b4b7d4602b4161c7a7398d958e2031ab4a2131a21514c96882177ae7642a79e9bae3a979324004898c48345f0b0c21f086f0ab42301384eb9467c0c2706b4bd1e32360f4d8819e3d9ddd68c8bc134c2497f189a00e85a8356946590b47c9c8130bd609b291b0d6c347061b55c276555e37130f1d808148ce08869e586ff6fdd02c35341833db6a044db551c9e2e0540133341c3e7d6e1305035c214f6402a645b570fc2d415123c90f6399a4fdb68fd5ab6e54871441828a22c8613735b8c4478d6e82a65916dd9cd536694374a95ba4ea6ac8c6bcbd9bd866cabd9756146fae6c79000a3d726098fd73454846e4d32090721db0a25a9933719161a6787018f0316b5b26c9dd64a81926af32b3c7f2e5bc12eb3bc5327cbdcca32059b8c2c789f7010b76255de17b4dc83f2070bd663e7ca3d17870ca7bcacbb6a02a86935a828b8091ae95cddc070206c386abeb60a3c53430e4de1d540da4e65ce14655c8c5bbce7fcdcb4a7cb6d53142aa96c874f379607b5d5268a7afeba277399508cb3e37a89ed5c07e109519822988d97f1bb7d7cd1186c3a6c298a42b431b0c6d89aa4b9f4a0f142e4fd7ad293147931ea24fad05a24fd01eafe2a9733024bc4ecd2b257db356c7549b6ff31551bbe06f7839a4c0a0aece989ad60aac5929ae98a93931272c274b3cdb0a1fda4e6b35c6f445e4fe81215baf946437ed1fad178deb928b50afd256c0d5711c8c853813c05025bac675cff9e214092df4f815cebd5206743f80e613b37b61357cd23b093bb3f10cecaeac2ded11c6d56acfa83949bbf1a48c97dc6b8768ba0d153d38dc9440255ff468fd97e09b9f4efe36f4506ab4b5340dd446feff25aca0fb4cc8a24b07714747f3ebc0afddde29eec9f83a60beaf4109b8598c5936dda641361a62ffb094fd75633509a731e0d296df5cafa99122bf10b2ccd505af8f2114560317e4aa28e1e772cda6992ebc11784ee1c2a0747b6dee3cc61bb46a19b070c32c8ab30d4a3ba22cf3975ff9b6faad8be261dad44574b9a29660a3d900fd88b783398e229ed50e98eb422dca7bc779b0af3075ef29fa83767a12f760245a71f598a342dc137e530270ba32d8faead789eb09736e452c702c6ed08bf1f2a59e8f7baca3cd90c8e8879254cb6096375b62bf797f642b7217221247e337543656b56b2ff26d759823b4f73d800cefc3d0551830f0393bb9e25856674af99f40f68e043f4af5b3fd623f28cd0ea8dfee6b33427732fdd7202ffda6b833d9e2bd608d05f46223e7a0648aa98e41ae408528cfa0f46858344bf79f54972e216a53f445d66a55263046b6f33d45da6c0300af12ed9377788aa1b1742f2b060511cf2228a7514cb0038ab8055dadca5b44677b7ebc7b93a5e0a8d7c71cd82f0896e0a30aa812284ec868ff52863d9c977c9651147a1a12a1219112d5345a99e8082b70a0a863866c8b74b626999a0c146c281c86c7ac40ee48c94ea9970ddf1cc83fd06de9a33f2d826cc0f30dc85c8bdfcbd12ace32e1d37983a21cba0e5d73fcc9fd61133b098d0309b112e4f26133694e9ce4623bd47f46a85f87ba0a762a68a661de0e2bb311f69718d48e84b9749eed899edb2fd21d2c806e92ef3b841808966f632d23308760b452e9f5addd40f5aa80bc7289566a86b2f4d54f0107127b6a4564d98bb8275c14e659b923d1337be47849046b59aceac372d74c81112a2af71e68a42e92bcc68515908b668f7c8e85dc7beb66edf88aed5df8fe63efabee3c812edddf3ac612159110b09293f9e2577a99239231dd84e2c4415d7267385fe0175182a3c9c03dc7b6fe5d469652ba23379f6f5f9c6c623fb6523ec5ff3da375729500a1d342ab6841c289b6fdfe8ae244f45228eb5c2b250446c3ef29d4870010bff16717e41266de1fb8055b454343e4266e9fc6dfdfb331930a9bcc8cdc8915adcf90597a1131ed310ad2a8492d5fd95f50ce01e0e7fac7ac6e340d252ad1f27a320c29d8389e71ec0e52e497df9d47a9e706f08a4c470f7a130b33fffbce0f5aecc8d33cdcd6c429c33a80b1b7a6ab2e669fb9da1ed910def009685e8b8581e25dbb4c134cde1a22f07a891ab792684a10bc3b211e0465dde6b8c9a3c36f98125f12169082d7272765e289964676a4edd1404e2907d12c780b8bdf3f5891f2b1db061afc4a9f37b6a9f1991f549008946b8d208484c87eff70536bd8bcae9e9f9a9b41cc45c3db57e98ff44e37e8f56d9d76d5c6f303f954649dddb98c635e9392371f746863bd0c94f65180b42de48699ebbb77a6cfc54b4aafe55d2878388f3c85782e68e0ed867f8a96e455c4a9a9b5ce5d7351fe74968de983d3bbfee7f25f6a66e02211babe3455d758425df9c0376bf025b1d64871bbc3c8b0c68e1b9627fd73f89a4bbe94e1568cfd6cddac5558d52db37943bad41725edfbb2cac90608a8accda402ec7e36ffa155ac65ba18c8be797ff666e94367037e7366dd70d651e59b121b7c45ce0f990fd995822bd6bdedc78fbfcd297cfefca822bd74433e301fa85bfe541b2e977cd8c4d2dca10fc3d000cd1002a9415ac492af40b89e13370df8ebc464704ddc131c99b2f9e3cff53f44b73fd03028346128e7a8f962994ee41f4dc867ec3b90c1d65ba146cbf3085168e0f11b5a1806f78f15afce7048a05c27c5e804427e0425bf1dff3ddf1c8f8245cab1b9bc17f17283c64037a43a6755c508a424a1b6cf8fab2d2d2e22e4b0868038fa282fd25fb97b4e23d2cd7a3772078df78fe50fb8390378ddf200e03cfee3717432904c762084148064c881b56b7bf529817db23b958671d6162f25b9badb1f677b7944422bbbb7bd30e2f08ca06d406d53b4866cb4f6df0bcecfa28ee29577bb3d4cfb89b05c3b0ebb46fadb5de2c11c70bcc4bd9686729a4fa42fadfe7d8993f4b0087b4cd9cf3efbd36bb37dfcce26cdacf81395436da3ee7e4babfdd9cdc9cf3723d937b3a679e1949cffd10f684d17dce9a89cc98735136daf4210f0aa8fa8544b653ffe0c22d4d951971582ecfed72f14805876eb8087f72dd0df8f327fc995d5ec17ccff4bb6c946ddcfc4ce5224f405d719d5ebdc675364ee8cfd250379f758a1595569cc6755247988b6aad79696718866197cb45d9b51d3ae6461f4419f1e8a5813c492988e451e9e8e84217744557ced9662efbac1b7bf81042081dcbba75b95c244117ab2045a50b5dbbb65af556b98fa2321779059e8bb2512e922a0fc014dcf9e08d3fb3b8ee601c1f752d6551574164e37df3308d96344ad09c41d394c6c7ce36f6d0686729fc196f72bb7b8fadee132352adb599f42794985beb6e1dcbd9f1edc40f37fcefb30cfb201042d8f2a14bc20e625b4ffd10b684369f65f0419757001f8344306bc3f5605fb5874f03c222afc0be6b5c073b088d54586d55e918a834e8726b21c637f686fe8ea692d05ff559b601bfc76f1ecb39f0831ca7e23a096f385804977686550cc3ea8d6c5b6cb351f769756ea77e542b7efe1765a4382c656bf7c8c7beda9c399cfaf4e7ef44197707fbb47c5a12031cb1dcb0c82bc0b8ce0341b0c81330798cb6c3a218420ccae20d97333f9c0181fb1429271c3192c4cad118250be68f3d5f1fed59049233519ab29723dddddddddddd0ba7b9705c698eb6c26b2df7b20db8630423663fa6334a7686e51a0fea74f8661b71c7cfb0e7304a547cd48c74eb1d864e8ff367e18c326e5d7c77fa3a03c488557539f3a384333c92ae58a3c76c9cfd98cc603103c40c1f77762890104e4081035a7a67023336ecb81f93991ab6110cbbdbd144604605cd0733445465cc00d15e30c34325e60a25325b7c59d9b065c583112b129a5644a65cf101951554385139519164840a1634325414d1a4505182f6840a1f5aa869810aad082856313ca97e38628582105a141f564fa48ca0cc130d4919197628d38212658e5468ca9870a4cc0fd59216ab13ceac74083343131a0f48a860c589978a07106857c8dca04d21d3040b9924426452d0602013642585cc90aa87352b2356a8ae904223c14b0a4aa8ac1451e5072d4b3d72501d3186869508c6bcb0aa610c92108c19a252620c910b63768c20cdd0181599279a2ca18100cbea0341682d884073d26155c2144a4b532c519d9962051510539c30c2142e16a6e8a1a18085e6a4cc0a08186d052eab2159564946e0838a9a1d8490c20695192962d04e9002a9072950583591a248e584142298c96255c48cea4b142c3031c649c8109a154c6578d06410034504314c8e10e38208628c0c8931d2961033c2eac7164d54a1fd2066d54397150e49ac9c104145154d490faa2170d0ce849941954418254c8439f2258c1015166148600a23a4ba82667534852a0827b41bb4105dd1820062454308b08862421435a88a88020620a25041852888a48902886614050f28aed06820a3fdf802822daa272354619ab42d53545b3ea0ca020a272b13a0485a2d81828519a028a222028a12342050f8582151a30a01154840b17a41df276d89be47a88cd057088d88be3e682f7da5a85efa3ed1c08061a2f2022689e603cc1115156086a8b28001a23d81e9a15a42cd2ac98c6a4a186d47972c9450497165052508d51453342c3bac889e18f1858996c297249a962f47564c5f86a884f802c4f5a5071735ab2066544d8471a18be64509ade98a1626081508a64cb18396e6892a8a2798a87c7822898acb134734229e18a2757902c8ca87277a08a9d18498d16a08a3a2d2050625341daeac6e0862cd144dcd0eaa2a9eac6460b245122847343443b41c9c00b282133d42a066c5c4cc52188d4797294a345d592105a1d262ca901d563c9e684930518d49a27ae2481543a2f00244b3e2a587c6849a559199550e6176e852448927ae681f0822c994d58e1db4204fc430594549a21d1d1962880a8a2e407a74e921851a266690b40da321a16d17cd062d2a30461c1142cbf2a2b2a412810e2b970dbbdbf91898683834a164773b6f07c6189b4012e3ce4e942121d7d903acc952e4450c094f550c01a1437ecc27232ef99e5e70c567e5f327274f3c0c3df93082d61ab61fd353d3eef45bd1a8686d04ddfdaea2a7d09111954940e693203267723a4a9a4a2d2440cdc509973f973222fc7fce124576f73b7688ccff496add3d002f6361dae203d316324c5bbc4829271d12e2729425659b271886655913a5c2450b172752a9544a6b2e69b8947998992d48236c59e2fa416e79b23bfdd189083244f85875a145d870c5842454d354114c3655e2b102c26acdc9981dddfad7992a26fd8356248295125df7635a6287fd3a9fb384938dda8f698924bbfb1d29d9834dfa316d311a4ab33b4b663fa6259e989640620921b6df6d5159bc303d89c986e810232f60274f3c3cf9d044b6fab2058969cb921743024cab20340f97134f8ae001c992056f8b2f53b29517edab27dd0d60bfc5f176640da1fd638437640f768f04d2111312489664a922f4e302b012425619381ccccf9f3910e4cf0fb2008703d4bf9cddcafebdb6f1b6ce8b01ddc61739f8bac8c9c9f9ebc2c5f6db823c1df6f61dfbfabf6aa25c8fe55e8d46402d412a49773a4808ed2e67c31613169777b6f566367ced16f490f97e3242bf82454a0d7df0eab5932373562f6d01469abe6856844060be6012122388137968ac20a2092942b408b9098213c487c878473821048d1021479e186244132b659048828a1c80a4e083101e63842c110223c8028238145a849011d2802050442942091568102282300608e2501010e469f1c404691a820b70fb762db2d78fa41f473f86767d4c8434f56d36a7d6b676988d4ab91e8b7ded5a96fedcad003c9efbf13db6600bee973378ad37c3625cab6fca753daf11944640da14fbce62bfed9bf178aedc16f27800b76ffb66bc877d0ef99e7b0517ee9833786bd6d8c0de723837fcc24df38db7e49fe5784b1be7e796dbf30d1712da6f69db1bf62d27a1a0cf613c640f42d6135d73e8cf8790d2cff4d65a5decfad6daeb184fccbd8c8582ee7676a28c7f16120dffb9874f6e2fc6e86bb2077b28edf7e22165eff7e2a1444a08610f74eb03336b050dfff568b55aad56932673b264d09dc55872d6e113917e85f7be762cedf7daa1f4da2102e54164ff7eaf1d677616f1cb53ee982797817d6aa116f6dfa516d71a4fb725181702b66006f8345f0071cc0de5dbeb736711423ab76ca2868d981543f45c43f260f8eff614a0b17f1887d5c7d8dc767bd876f77dadf915dbeaeff0b26df67bedb0b2b388594db40e052db85d3cbc4f8293e71a4ef305ac186846717d1f653c0b3aeb81c4b8ba51bab3a7004d9ffea342b473f82d112f21a4355df675ba586e962919c61c6ff6ed7b6d09dacac8e063d2b175fb53e3221dbfbefc1c9dbb69cc2ae1a53c2346830968f0f67c3bdf527f10352384a8ba45d2de0269cbc72d0988b47d6a63c2acde886ba45db218e3c7549077409bd6ae67723e78c78f5264a907ab900550a6acb56fe36dcbe1dcefe930d0f618b42dde927bd1084c749decfd7bdfc6dbf7de977f3919b4e7d6e13deda6ff720dda75f3c19b7237cb0ca2ee69b2ec61081da3b29ca1361b6fa31effcb1490f2519c7c14d765280e0783b7516fe36d1487837d0feae963a8a78fe23e686758d0be9b4f6b5b28b7a0cc619aa523244847b366a3ba3425fc59e30b39a1e48e84b22cbb7211943d1f724a5067fa116b04cf452348aa43e6124aae6217d4a9121c4919eb667167f1d0964f65945c540275e2fb11eb87865d507c10384cf443c38f4856aaf07484ed6a8b252a559aac607162f1d04b7a492fe91d4b4ec563169da253d4e2436bade5e27374565bdce9436bade5e273747668df5a6bb9f81c9d5c94892c692b5afb252a7a2917e9a5a3ddc125bd0497a20cb894654b2daca573d1930ed9cb7117e4914f643b773fd3b45eadc19ff957e730ab8747892fe2dbfc5842bae7fe8c0279b0af4e05f2d026b70279b2faf16395582536d54ceeeb7272de04ea24419de8494a90c7bad26acd9d970b6289a0edbfddb592b4efaeb56160226cb4e9a319f224c8d3b5d0e868d9f1bdc963b2c24b944de409e8f05095b415c51525caf02418c569ad18c675403529cab0f1e986a3157c82d10d1e3abab9c146f3e6e6e6f1d00d3ebab9d1373737d8e543fac687347e6d63c3ddd8fcb3c198081361224c24d32c6d0fb21f939a261207becf9c4ae7d73abfaeaaad27849d9de6ea5d26a29d4e9cb54aa5552afd34a2cebff5a0b48d4af55aa7f4a7b24af534a00deab52a95caf43b98ea5255f55ce73636a92ce78dfe07f54d55a1f4dfdce8973652e74f49eceba3b65cc4d1e948daad0a46a9c7294c944aa51e0fa552293c944aa5b0cb87523ea4f1fc8c42a5f48e323026c2449808131955285964b10fb01f5316567696ba34c618e363f9216c9f736a9d3d17e5224f00f658d43b0884ac757e1a3163bff56419a5f5e78cf363ec276edd85b07b3c47f6f969c0fffc18e3308a732c85cab273b1f3a7dee6fc289839cee22d17b57251454229bbb22b1369b56e94a04bdf6054c618e7c7290821bcb9b981372d983174e5a29696370431c6782186f1e110bc10caeb7fefe321ff8b8f7ae6bfed10835862a3dd45c721ef5f8a0596ad80fd98b0405283e4be1a17165eeeb5f7af0436afbfcb2fb38d1e88bd043842f939e35456dd3cea559b0de793da320ea86e8cbbf031514be768a940c22e3c845dd885c4d3d98b64647d088564646435deb046a12ec63e843126c244980813216948b178f1a94b886dcf0f08dbfedbb3931b08761e1f692f603fa634623616465780d959cc96e90aa76d80fd98ae50c25aaae647f9d15f0ebb50d782503fac375f35d5dfd7b47dadfd958de5f0df6c0fbeb0e13ab92fc77540755b8c03aabb667fb1ccf9b476fd4c3e86c56bb9ca6532721ad6976343d7297e0002e4c1fe6ffe2c4184aa9bef5a3ade7be3d6631fbe37c66b519fa5628faea134ae83b3ce39679d75ce88bdd7ead78bc377bf58bc027fec6aa9103ef6b4d6bff03a6d5afde829be80d5886295eb99b5ba0fb602b3ecc96e70b35c5f5a719d55c11bcedd2f967a9dea75dad8cdcde209c094a20c6805ca804f9355bf8364f453f5211f6c6543bf59300cc3867616638c374bcd34226a4b3ddeb0cf1b861b4419be547ffa1354822d7fbdb97d311b5a5c31be59bcecf8f3cebf59a0929590ccd3de403e7cb2f579795e722c2b42e84eb77410607b20681f21c55d567cc85d4b183b910fb9eb5d40095b38078b2420ac0f5d4f56d15ab0081641172c824675cbf99cd8bd25a97385eede923f0de9ade7d1e196537da2905d2cf17955a21960dbdf53ff8bc56f96eb0479fce306c207fe72a08e9016495846a42d9e6505a13f0a41f81e7c1d007a33fce34be0431c6c1b401dff1c5610ddb915c7f2d9f3c761a803a78842bbfe7ccf5a30b2958b7251364a6a82f40443290bffcea37815af1225fe6bd0e522270a59790da9f212b2031227b2f12b51e343cfb55f8e9c6ec12724b06e560a5abe8440d9ef899eff76f73fbf73b12194a7f3786c08c9e1e9bcddc5bd778808f1b1df2bc8991d82fd5e4194e09f71a5346f86a7d900f2500ede0cdff163e6e0c5a811a38c32c648e4cdf0ed44f67b37cae02351f0720dfcb4bfd68a323e0f6314254bc167dda07d77ae64bf67cda05d08a69964ce401daf7ee4a405ead86731d1464a7407c1f8110403c540328e83763fd2d9b93afe94e582d5f29ffc291f0b07edfb85275d8a4771258beb6ba8532ba7c99f94d64dbedde6a6d8450b49773a14c0d871000dfa36d27eef5fdf4b1fbc3fbef854ce9d666eac1c34fdaab5ec6b0dcb94cca494187fcef9de7b51f8278e19ceb06bedb5134d1659512ca510baf80c29a59f8a111573ad374b7cecda482b95ac1e76cc642f870545d3afbfa3e38370870584f63bb176b1df05e4f98c42a16c6c6cdec6db361ccecdf7d8fc7d1b0e686337dbd35bd0c6f2db2d9b18670e3f0c8bd7e219a7cc58208430684f3a2537a584514a1925842eadf77b0951b2238c43314d1662585034b6cfea41bf287fe771ff12ea789039e79c106208b7077540d08f350efeb7218618e2770c31c610cfe9d28ffd7cdba334a252df531f85fafaa9b7401b632988f1e68377d6e96cea47957e0667152d4d5a7c34edf7ea6165cb1e4b9b4e10ea3f4e431d10dc0206e0c47f2f468b6774691a1eaf640f6a265e455668ea3ded285960d9a067562f061a6c506badf6b34a1d5e0a20cfbbf7ce3927d613401e4db5568e0e7d7228a30fc48bd6b40650077bfa99dfef20037e6dbc8d7d76dd395c3f9371036cbbd65ecbd9cbd1160bbad3d100f2744f010c1cf8fe67b2d9646702973e2d0da00ec4004a94265007da2041226274e36308840f6c0732d041c6810534c080860496e86e27a6e294d7a57f94a9944ca5b24ca53ee79c53b0b35bf7d21879128d16962648e99992324ab92db992eb649c574a4d236e6077f37d5a9b467cf822e2562a67f933e628a37c41776f293e0d0f6ac4af319f06ec409816c3a6bbbb03ed29b4b3dede9ca898b70e6f4beecbed63ae21e37212802fa0a5d1c2d2bd9992cd7f39454bdb8dd8efe5f201593264314e1f1f2c50c79da00efca62a28294b5094b292242447d5c87db8107ed8c5a73e1bf06945ae830c24e9ee559913cbe6e4668d99711d8e8f450eba768c534ae93edde5d1864af0b1cd8fad138dbd7fec71ce97d88f5bc47cc418310c8b118b1dc4cc69f8c2310ee318717874160cdabf036bab7850c3bf06f65a0bea604075031db0889e6a440c208ffdcecedff1406ed84112fd1127725cd42497716c90c98d03ec3088c61b21960ddd6776f3806fd60b3abf0fde16857a9b3fdb6ce1b3aae8f9d9b3ff2436a79edfc252a9d4db783bc5e1e8ef497dfd14575ffffda00d847abdbd0d07edbcf9e08db1b71946dfda5a776499ec53eeefdddf7b3f9f5bebf6b973ef5f0d774eda9fcffa3677b6656f737f0ba1b58f7b10dacb59086db510da67fddfc682417776779562fea4863aded22ac7fa41cf5963020c653c1ba7dc2c22ddedec70327bace259b38cc328b83b3fedfbae334bc2452465f79394a5a42995af7befd3daf46d9472985544fb7e906e73d78dbee71c74d349dfa96f673c3bddded6713ef8d3884503109c7009160d2dea857582ee9cc88ecf62a229779d8e60d7c652207408659d3ef5dfe60fbe17e39c71ce38679c33c269dfabd5d65abb1036ec287d3fa5acd855e7dce88b2f2f1371eb7c5ad0a8eea8c22989e98c16262b8e98aa00434215560c4b47be2dc19e62dbd05b80fdd1cc322e1b4d024481d204be887f95401d247e4482ee08408037e35922cd2dc522297bdb8f09cdd1ce5219da9c39ec823af2c5886f911c6d6ee41a1ad25d6ac7db4acd393191e5de86248748c06ad29d9d01ebd4c38791390ed2ad9d1fa5371b6feb871983185f7351a3e8a3e86b4a0366f931ea69c4ddda19078b3c01f77da311b1cb49a863b1d66212ac1168095841e84e67be7cade5e23184f8e7e3d4f6e32d98e16e36dece1c0eea7bf2dbcff65170eb30d0ce5019fb97b3a09d6d37cbad12be30ba2f3cada5a11134e29249e902f24030995194306654c29752d248a90b7ad3c6fd54eea14b3a84bff7de7befbdf778a48cb00208b77be0dbdf0c0814d5a9dad0848d7a54e9ae68614c0890a0b2a3490b4a908c40223d289212920f3fa1c790932b7e3c39228811fb3938d17485928f16a660410908a924c450d00c351469a1e5881aa0a4594189ca0fea151021892f4b00b1660b215470124451acb5564a29a5949ed06384284d242845719a48aad45a6bad95524a29ad5cbd45e81210596bad544a4a876aad43bf33b93c2b421031c31153988005c811272634e1440dd10f289ce1f294c234b2c40846cc9480e4081dac50a109245f94ba1c39112a210577fae1882abce8c08406052744a21327267040049a1c76c861090a5a8a28ed804e4a1861ce135e4a3d86e050ad4eca0ce51065ab06d7ec3189bc6c2021be6a98a2478b069949201aa46cff47431324aefdbed2a065fb3f1a9ab67fe7fb7557c74fdd2e5e8ca7c3696b7c3fe4819f53a73ffdbae9adab4f318efacf9f6f11486ef9f36ef933a319072765dc7cd64de6ecb973cee11f5f833cef23150cfb8b617f857ca9e93eecbd041db677ebaeadf366106f5c4e625cc6b98f2f809bc20d7306376cb54ad8af4e65d7fadec405608dae0fa7ac6f6f544ee2d41a15ee682d12f1ab1246bbbead6fe34f15bdf46aaaabbae9a4f626c813f763e50b7c695bcb65f7c6c6f6d89f6fa3f30dcb49ce62dc057077f533b869c4ba3ddccad97100061c74e0f3810f23e850200f0066c6f8da16863df6b137419e9b337024cc6aeb372827a5bf38514ba92250a7610266bf1713d796e169cafdb2ccce6c88c1dbf006a50d5f062b1bbecbb0b4e1672fc3f70cdefbffd4fd62c007faa9fb411d19b471d4adbbbb7e741cf421e41e7def8e0594fd760c54324b3106b39f1bd6393b6ed8cfedc697f2d6e8fbbd1891ebe84f3ab738391b39f9e20832185672aa6814d0cebddba11f29d81c0075a07def7636b59b07b373402bcd07514607f1058c3f637b31e0777f45a4604307d0addb993a30deee763ac8c0066207de8c09de8cb7a79cd28887ecc1170c48fbbd5e88f27ac1f55aa2e4b5c4c70482586a6495a4a42d1f261d6df93329cac4ee2f7df300e8a76efb62f87f7c1d3f75e31763661cf0e5cc38e64b0eea70e9d1e61cf20b00bf002f86ff94fab43e952c333a7ecf7c345ada97efb2877ecde886fdbd5ce7cfbf5a6aa5a75112b3e3472d4d52f6db4167f600f67bc180650365d6a9bbd70dbfb5f57e7e20b931a559ae41297d31f2d70da339bb664f376bb92e5fe01fb1ab436e6ebadbefe8d79719077d4abbf8b4f6f8d3af5c976f38a5f12b7dcf17c070eefda4b6bf18fe3605d7cebe0bda378bfd76a6044912315af6db6f376decaf92263b8301793ad46799be97d36e866519e76cd4d6658ffdbdb78b3ca9ad436efc4072cf1676d19a3385b98bfabc599b71609f714196520f7e3a905f877c9a7dae817d8672a78ff28ce33ef659deb6ec73c63d1d6ee3668faa9165500dccde8b81519bcdd9d8f61e9533f7b0971907b6599c71ef4eeec1803a0f0392c4b5fda10b59ecb76996ca60c0ecd1d703ff6d9d6ffaf246017abace03411bbe6e723dfef02107234c266673fbbd5ca8b27dfb496df9737d3a7cfaf69f6ef6efcbd930200f9541998c2e44d933f67bb590050c546050da1ef8bf17837e57bfd2f76600c9eddfd1aa46fb7e54e297b361bc18ef8900e3f5a56fbe66c31fbff1dedfc6e3707e7c532e066d9973cc7f5ce47444c7b1dc8d1eda1321e441e90a60b6fc97c90ba1cc38280c43c19833a08f6dc0e0b2c733d90540beebb3c468ac05314e5bca9b01262fc0c9af43dac7d121f7cf4f6a3feec771e41cf21fa7c3237781dfed598b79a5d089eca5708802dda3ef07ed8333e693a12d3fca873326fdb9fda436d7f94b185f42c8b9d790c5349b7ec501cc9eef4e3bbe3ca2ca867fa3ecf89285a4947daefb9a6d5cec717e7cbfd721b7ff4f6a3b973dcd39e8e5bae89c0edf8f4b75f33db8a1b707e0dfaff7a1df5d3f6edd7ca0fb91eb913d3784fdb6eefe93f2caadf39d7f52db03efc5782f468ec8c93ba50ed7e11cce8dfaf12bd7232990eff9e3fbc737e6924926bf0e2121dfb270b4ed53f936cad8d9d9e13c90390ab09d9b400200f8d03b30044084eee68e713660c7973076dc51ce97c3f19490d3e17bbef40e4b2f807db875f4f38dfce33aa00e7fa242640ff2fcf80a3a7c47ce62c04b902634c0234c11290ca1a9e24b90157aec56dcefb582d08c422f24af2367585e5248e42778ccff9ca74caa07a8d9610831488cc0628405d46807d9c3083524c126d8a880d463fe54e1470964ee0bc91525b09af8cf79a960c546edf75201ccee7e273362a1091bbef61c2e3084096498bc9818d34311ab0b8b899db8858913865e43b63022e6880b2bcb0b05242f14948c5e4584aa08ac23dc4e20ac2dab2b3c786cc1c2840f204aa082bd8e8408f22a72c3c6fbbd8ac410c4eb08ca0b851f585ba68b85455b2981bc8a24bd8a20ed4eff8eece5c009610fc491664942bf97948b96ff267e33e6ff84b0a5959f650cfff13d803deb4760db7f77c3b15c0610d8b3be354c585786605dd9f919e173708cb5e2bd78bb4bd918f2e5bd789b7292080d80fd3607a006411b00db236412a12da8c4cdd646b16434030000019315000028100a078442a148142649b8093f14000c859c42764a150ea420865114c418838c31c400608801000460684aaa0b0051ff3f7645d252a6080ea2c8318a03635a145f9e1a1a4d82c964bc43b2feb51edad3ca5789b951bbf277f0799c680a5f3834e1dcd8c36e42aa1bd9c87ddf429440cdf3ff0e6462f2bdfa6d949628e743c7302c57e0e916b6366a49c023245a0a319399993433c2d57582df923eb3b84780391a35b9f2c3c10b2aa4485d5c882f1693ff13a063921c3c3350120b2e8e672bcd81c2cb9591a266179b33ea274ef3b558a968cbde2f36c9776400a0b4b962424055a7950d2957e8d6d0bd493e486469f6c27f7f867c95e074e44e8a2f946e0bb2c684130f3894fa365ae2b75556803c104dd41ac44a1c37215ba598e1f0c249f420629a02801e6a3a40a6a92d4f21631940a816ad26752f6b926de99307229109759e9a99cf22cb05812a65caf2ced5fc762a22f9f4c2d825ba9c082936c94b0e65d6231cc46990f9d291c8ef33d86fc8d2b4905e7a9396c295c6abdac3b1e016b4d5e396b19594947fc40b05f0ac726ff1ccdd568805310ca93b7a80a4b61a039ad6d705285f1ad3006244f029adaa21a6e5b271f91431c703152656c088420c68c2641c10bb0e51ed9ede798d36144cb161e31555376f23b35ad917e5ff5d80c3ffc37bb9b5f2b439260a9f958f66c992282cc2f800ff3cd6798cb7942214b83d87fad64afc30526ac42d0a838cf7f8a53374121e993b527a6a006a94a9797457ceeb35628cc0fa7198afe1f48f974b4be6be512db12541f0889ed12eb50bb63895309b7ddfa405363cfa70ea9843069d43c22b045819e88d93565d4c55655780742eea209aaa858942793f31222f91635cda9f3730a9aa44e34037b16c8a26acf96c4a802f2912e53f49733c647fd1311f900621ab722b82777fb23206942c6bdffd810eb03aa6a07da640711841334f65b03b94fff531711e151c01750b629b1111177060cabb2f50f6b9ee8011cb0c89caa65c8e02cf6077cc5be480b1e9fe64d31cd1c97704c68046982232f811724fc49f565b3d1f751f644e7d21b12f9e4d11938ce4e063bd53e84f2afb66a429e62b3e8e8ca778863f8e9cadca3bb8cd7e021412e9c8880ea4cf7e3245c4ca74021237f4ce5749083c837a32d0ed008dd1f8e803db7ebe58991988e0111fc738aa4f5ae60cc4b18483f6db60826418c6ac3aab1a76b5fef3aa6fe6f84960cadd74ebedf89aefd7127fd32b4273ca0818de9a4bbd67109e76684aa8c63760aeebb77ac5a5e902bd58579725a74a94b970e30415b06ce023c35db6bed7139b429c37f02c26f26e308c425111d76aedb9493459711cb555683722c4f12bbf306051e9b94a23bf317e7c65af25fc88fbe961b180cadbb02b855401edf828371b0201542e295112c08398458e118d7b91e623a011a1e83a2831e9eda48492101b38ecbe8b17152c99d4663bf1dfbaa35a2c496eb794a800b49406383158af4479f406d667eaa6c38f29669b3244a730b5c1ed225f1ca3b8331c20bf03c7e52334ded23cb2c830cc10e9b3b04c8da0da83561a29f1b10ac9ba1b9172a13a395882c9608b089cfdc51cf0d81c148c84f17aede0fc5afa6adb7a219d72cd2a887e76985a866a7b0ae3d9d11150d7940f975fe4a7f250bd7822400903772a6de3b05cb5eb689a1699a96d5bfca05381af463ce5cdab1f1caafc6b969443ad7676211362bbd7b93339fd36e51f873a9a551d291d5bc5d8666014ad7f47361a1508afa823c0e1b7b177b5583e9419949c6cc043c42ea264043165c0e0de0fa5758a5e736cc932b72b5feca2a952e520145ecce42e57f936ce0bf615e5d497e927e28b8051df6c6d21a8702ff921b6c66d74a6441c296d66f672cbd8ce06d478006c2399051475f36557eb728e6c3b811e46021cb9235adb46ffed593f9a1a517c8c8fa648874c0313d4f0fa11878841dbd8cdc8a337a4747943aa794488a923d81aa420856b3b34ae211318591d81534f7a60f51f532a29170a6732d08b712a8ad0e85205c3c42011824d115b7a7a8113541acc1fe51a44ad9d4e2d8bbe05f8e60ec71fea56797c1850b0ceac9e75b78212f678d09c4f6f13708aa496055ffd8688bfe4507914109441fbd40465b90a55f040ef2b748a0b81763f0013c268d95eadcd1d8ac7cae529bdbee1858793e37ea4d2d26d1d232339852a9ea074c26082bce0ecf78a4685dbc9a85b3f31d4e36a3a2ec9a1e865cf2438c566d4a3aae18c0aafb234c5ebb566a2d1c7988b92864c9f76028e78f590c08609419e29d60a16c5db56198c48a3b25cf2f5194885349b13796e1ab890920e8dc5760ff40ed228f97b24933a54e8fa596a731aa33c029849e5dc51a5444250caa89a7b8a412a61db5f73ecf41c8dfaf340ce3a597d3a4015d60669f1cf7370361cad1486cebf823321f7ed42fd8e6cd7d0aa3f17a3280a3cb3bbf0ff407fa81a2e2b184ab823416f03f9ec2ed1111fb543dd16a5243a2595e97d392dcbffde00d1dc8d9e46217bfcd01b5c64e2f444ad610bd0cbd328e680cde4a35c62322e3579c8b1a08518a6df0bc8bd83191e7dc3cc9f3b65b5599dff5ec2c40b6e8e066e1e60d24adfad98983d6fc9583f8ad5821bd212b413104713337227d74b428ae3728e0129750879797693910bdd875e9b9b99db2cce55682207baa392c382400d3341ef00e1bd5e446f7e5308daf7322cc749c6bc9a903d470c9b96ef3d5da83559fb8dd2303e4507720acadd5806e1baa52aa939e37cb685f3e2b37138274680edb0e23a3ed5d636c1ba9ee6ae6b570471c1cc020f197291d58673bb901dddfea32f21748f8a67ae7719dc412d3261b548791e3a5dfddf1adfd3f94418bd669929cb28c1b3a378e5ed068e6a340a4f477d8045794a3ef70e5b5875a3e7a55a2768c3f92fc971dc4fd43635797357665ecced6bc9f6781830ac8b4ce13369df8b19b76ddce54187200b6ab42b47d8a80cbd7aaf20061dca9db79b359bd0152fc8edb664d68e6ecb8ea107cea95b6133e909c43793b18e177f939ad3400ff6f81a1678fad67913d4b4709aa7f6e4a80d310b8dc235c69bc26d3c246c72b5eed599f3d3070ed40eaf6e3b0f842c14e74b4790e12301a38690eebb58e0752f1649d020c008a25bc4c890fd26c3db6623f6e814eaa3007268620eb73438334966ae3777aa9494313624110e4ab3e51767b8faa38dbc9cf59122dc9e1b32df275c5be472841aa5e94710b621e0788ed2a4e9ad96180289cefa634cf181cea0a70c8b15ae1400d6e9a81326c14a7f783004e174018077aba4d3b99016db122ee84242723227eab4133b9e5f10b8c654b2e4eb5b9283089307509400bb315b741b6bfaa67b9b68c82e22f6ddfcc199c37d8bae040cd96fb45f4ad687e94656ba0ab703b8609c0ac4a5631b5bf9d04fb6d3af4801a0ce0675bfddd6cfe1310e818285f6db15e3cd550fc71830c678e8ac6a54a49b939cead2c5de7e15e9f11a77d587bc50dbe3e0cd2bb0546d4076ea4a1417d31f6116846098b828f983bcfe3e7b4e19b4a38c57ea249cb181c8ff6e8a884e0b386c978238c3718ebc26dd00e305f4cd1ddd3c27f5757a534d5dfaf616f6cb36e956a1463fe841b35fc693b1cc08fae3a309d733f5492d5d0075ec867a290ebd61474588d2b1a8f51f1c8293d1b0895bc50e17728b393f4e9b60c278cd64bf47f8f6ade605201e259defe905fbb3559fe23fbfb7f2b72c49896c200ba1cb7c0a584d46117620445c93ab583208d2f3b6e279ecfa022dc2b3d0f0ad6ed2e838f2cdd6ec5de24ed9de166cee72289afa82da5ec14d73e40102a2879805be5f80313c902e4f3756e7590acaaa6687dee7dd0c4177098f9f44c0602ad0ce7d7f452f4e4ee6c919c9d3cc5aef08577b714d3793467970b47e5ddf482aef04f1499c86cde9ab61800573caae9d0fe071b2cc27bc25f3e593ff9e2f067358420f924b71ce2283977b51d4800496916508db7c0e830466c2d463a16701dd5bc740018eeb241ccc8ebc326db6e6468b70826320264437e88b73eaeaac521730c833b7c5133af9abfdd08cb7b7235cb4769fce7c80d5eb3d182526b5cb238f45aa24ac80514557d33cbf3b12a38489d50902cfcfa5a8da72ddd4195dbbb9232a2e24656a7b1c4c50024095cc8bcf86531e376646682d0f5eb9fa290294c47fde8439f06bdc08a3235d81951ca4adf2c2d90fa368e4144b0e7d8374c776386cbdc047449e3e9ac9569d0bc4d40fd21c9eea2bb296bb66db2486ef5d823acc7dcd9322c2e9bdb9cdd2e4671b1774565904fb7aa44c8ee3cbb98901b32fbbb853dc23d41812e0cd5224097b52e85b42054d028f52624f1f21004c14fb5c76de2186ff745fee5e4ce7705b3f56310bb24f70b2b2320ae476be1ffc0d482bda4edccb0bce0e5b3e1eda108fe04b8109004cc6c60f37906e8067ff1fcf0560996006add46e051045f18b77a25f9a4729a8931ac373acbee3d2d3f7279a15b12c2a55c88e857f63d40a08277c521d5ecc6b81324b58567f0c0f47dc2b110245dabba01ed231d47d7c59d4b4fbfe0f073599024bdde2d59b437ba84e24dc730b8c2f8c70b7188a6238363d233b097a8b725364234730bbc00cd36a34faad355dd71d700266c73209e062e2b66cd4914b6f0c3fce61136459bcadf887c37e80b1b9ab0224f58ab1e5d2d6313cbe594ce9be845b2f80e5fbeca50e45f30c70bfdad92b38fafd452c5f3ce854f2f501f1647cdf5bfb3849b6969cee681cd35444001b4113dba604566ce0eff2406ce3232bc2c950b3c8e4840af56200ee94a02822a421e2e31acd177813832722e259baaa1957882c93a3a14fdf013fe0d251cb740515d8b23f8d154d735129ea6fc3cb16e888fef4480cd1e6e05fb880d78e5b0273233435b99d10fb4df340ff5d4ba804912b289081f1ca8169c860ec58841c56e796b475bf79a768c7179114e72a50708d89879ad5d54e06ff94861e58f0c88b7d645330b4f383d70fdf2bc9983ca46ff73cae39b26674968a2373acd453082a5b609f0391c7b444abc7889871d16305667741ec59715ac3ed563b5ef520c1865327bd17ea66f44b9635b14474630202ef6a81a538dde972a1aea6cf53a6ab200c55b02a69a5b50d6429af5a3354066ec138a2e60c5606ed9b15b62e9c76c3206080917904022edab2675a356c49cd880bd84a29a8313f01e27f08ccc5ac1f5cab0f166aa759c3a2da583ead5d0f938756f5d07a84c1003d421501390b6b57dd44fd627a37d8c3e512d92e6624953b0e25c56db7b27090e6de4388061aa73510e639cddabc880050c753252908b7fea9b9b34cda8d229c9a08415d3cb4cbcafc7eedf155bbf1e23e9ca30eca6cf5bebab8daf14201ec1fd0c1ffb709c718ce51d62a97a062dbccdf0388064a176442714a6e219e8db8af6ccc902c64143c8b3af98be9af369a87602e457e5730f573560870c6edfd22c1ac38a4b2c21a5d6d1cad6778c1f8433cfb3c981fd9c74998952731016de1dc528095aea92ba346117128e0bf0e21f9525e1967a0d65c3a76bba9045acad3ea439ca1877dd99395e3ce8e818a6b17c0673311d215822872e61f8477963d2c33bfac5adda608084326b609c95f8c16ec145478f08c9d444f2a481e2873230d5c461e09afa05a55915c715101d567bb991d64209bd54b249885642d1c1339311cc43e0771329ca763c5ab832a85d9ed1454c77a82d34ca55488f44dcac4bc4b26b47700c6728b046222db0a6e31d4cb7abec1f7a849ef6f98bc7020d2761578fc6f9dde8432d1d0328bd6add34f1dfe7ff63bb1015abbfd6e2bcd8201a6b80d4d5b78ae465095f8e6f5fa32cc32dbe6b5e6a31243ecd3e603150c07653bb8f4ebe1e3bf1fc120e990ab44899e85914c18ed41935d149de3bd6a154d269c2c42163c23e3ef4b201ae49a0fc82c4ef29539d065ea755ea29a4b7a251537aa402f99ffc7fba3865cb1352bd57838327203232b5de0cab250b5dc12e0a26345429aa018b741719b73b63ce06e3112b90aaaab5d996486eceb87af30fcb2af8413b635e14e9bc006d61a4c565e5a0f6e0eff4a7044866754682c6bc42b32e9d4ef82681428b2abb5c99d00881038657fb0e399a9d533191972a4e19e116caaba872e2c652de30b11c41ce155e96423be7b3e43d9f0602c71cd9292eb0dc67410f4d05e776bf5da45aad4209a547ac6864a939a16ff6fdb83a3db188698d272a6193a10265cbce3418739053a5fb410cc12694fc47e3fc5dc0e681be710842ecdf7af6a548100a7879947c169a516ab7567121a6a61de5e123aafc94d1719da8b7b92492c8cb56428f710281a0be783d5432dd9497c963a10b6d8fae364b5009e7253d3cac79a6d4a9f8ab9dab781e486b34adc604238a40c7ba4fa52bcb19e4bdcba5427b1c5d2331d45108352e08afe626de6e351a663e8b8a234b3f68cd66879ad7a23460db41177b28b0d743d4367b41794c4b4646da327dc28867783aeb1046b4c3e6485f7579a3e79f1e0e04832ae8f94fd20ca54233d50b9cacf07e2e121a4df1f59addec26d4c41acf4967b269a0bdd9a51e4b8e1d2013422c188d35f9c9ebd1d8a3ec004967ed6c7963707ddd146d2cfa3a4d47fe154d05dd20adb5c4621a1b2d4152bfe9ab6bd41aba2ec6af41ef20431ca98ed4b040abf52d17b13c1c187193025a0901d55eecbda51e10ed736c97d856ff8feab8cd25654dfee47a64a69bec4c281e0d168f1e66c31121a9065eb127d7af4107aaea516109c4c23f5635233ae8fc9075f8964c45521454bda80c06180af85f834e0de9ed0238f43c4504cabc2db831f5d3a7a315dce8f0bb0e8aa3454584c525cf5a4c51074a0c8973d3eeadc14390e4a61b0ce8a38bfd24915889fb4aae4192498d74588f6a3ddc4308bd83f3e48b881ca4ff49ff51318f9da0b1843cdfcc221c69841fb906b4189af6a53ae66ec9ebdb883faf2174a66fad42ae30e21f15bff64047fde60fde0f4a2acbdc26625c0769e3ab9f565c0989483dcc9b6f929f2d576764e1f432b1cf9063e8cb3cc21a9ec9ed39ebe4fb179a4a5896130893dba35818d84961a8c704bd50be25d0afb25b7c3e531423f6303abdee669e425fc2f1138d1ec694b20684a76a6c8cf50df00733249aa51f23e1c8ed86dca0a520194cd5caef181a0cdc9c5d1b38308f977870fd80cc5e1c16fba1a7aaf171f765787d62f777cb4bf7bd632fefab042e1bc4abf405fc8d3e8b8bb04afa23596bbe6a040066837ac7ae6f429706be6518a16ac719fe62fbfedac59da0883b298ee68ac67d98f05b5c2ca7631af08b41558e9b2525f0de2b3b52adbe6c52248a25937680f4ee2cea50135302c66dd6cd360c95ea191057724f0a6dbbf7de2e4ecccb5799074df6fdc4d3dfe53d554b3f402baff6424ddd888c7fa1a74969fb4551e7cc3614ab428794bbe8acddd1879e45ee659eae1e91106bed729922f1770f780eb412039582a1ec36d3c33d4f61b884cfc5a30d4644fc8ead73c674796565b92c3c7f952f7c52ce948037993ae5fd51bbc9ba64f439ec5d9fd50b9f5b5aaec1f0cb4e3e6f1cecbf7366b09e398bed4dae763e63134d32aeb85717d0b519da8a9be9cbd33b2c8b37690cdcb61eebcd22e7b7e71dc5fa8cfede8892ba43e18301470a1eb2048bdac25f52916518f86fc6d25ed31052bfe13c286e91583abdd3f1c078606e6f6312926045c180f89115454c4565200a72684240c06e2a4954f5ad1f78a97d8289ff0c328007e9c7303503c45cac22470e9140bef40f1b15f00d95d122fe05fa9356d254e35a2c5cfebba34c1f4e51729709ff545355f752c0eed568bc6753c206eb82b8368c8fadc2b4efcc497180c3963b0cb6aa9d3c3b0535f6361a01bba5704e6ab29f3416fc5b0142d39f73a2dbe3d26dcc047bf9dd83f220d461171e9e62d316df327930c01d53bc7c9947fbd9528ac3600d656fd93e8ec87d6d56242fadf7c8d11c537eb47aac55272846e500e4ccb627c0f9c014f9eef8f3e65b72c22fd9899390657c5a4dbbd8714bcf8e0b77a4ccc38c76b5197f21de7f0f41008cd42ffab8e85cfde367ec6cedf19334659923be99561cff7d30d46b8395333637cb95ed8a3aa6d1469cf391b39dbe31f2128fc85538b33700cec1fcd8e0ce743b452dba202d4ba59a6055a6dfd856aae490a00ae262d20ed9edfdd259e2dd91f4ad44b1519f6f972eeca4febac511fa1bef32388a52082b661889b22fdd89d6ef704c257d49087e129fe34ab014c4d4a0caf538962f337a23b7d567865457f26eacffdcd11b14cdbda96417aa5138bd74cb2770ed35ce0f47129dd438c77e168f887146f1a50fbc05b5ab75c8c7128a983c2822a82c4be849f133b89e8f09611463bb0683492d0f085f299b40c97015ece53642307d46bc6011784b6d8b945fe0d3703f1af92f3be50f574ed262274b4281a2021dcf49630a6923d203853b60aac9e1cb8bb98577b9fa808317faa45c5f153639619434366f78b8268d32b4c5104eeb3f84f5e92ab7b545a11e762df825d0f348bb30330302a71f36251801eee14256b74c6faf099058a1d2d34ad598a2c32f13a414454a07fa171d156f7e0586738cec7b710a6723eb4c92d66c4921f1f7838f79fa61669cb0cf34099750eb14f1a5b3e06e1e796a7e12b7d3afd62dc7945d0818e57589df9bcfaa54bb08e21e60140068bec4c867b116f13968f0a8e3a5b61102ff733c34d605e2f29c90161a03406e207b1247b7a107ad88c5de197b358f20fa3dde397b00fa181ad85fb729f1179ab1ad30ba18158e451bf916ea551a1d1ef75c2e8cd8b6b213185190f94e27d1ba1c44ff8fedac8b7451535d1a4a1475ecb0c0661769a1ae7337b156fc413f6c7e369dc13875341484dbc057b9aace0168680aa5e4adb8216d5a4fa2487d1ac1cdea1152608693f04acb5d583252439430c89d7b31f09caf3134258f91f7e4760811016c12a6829c82cf095de06c6469a249f239803c9013b999f7a48a9d5c5cce12c99906c3f98c15cbc927ea2b5c738c0d4a40c6d312739801bcc57840edc4a96e05b7f0e554e8d4031c1c5a8549859880a2369ae437fc27e6ba753c75b123e5ab1c1fd7f4d62a5a1b03df327bb91f4e661fdb9fb726dbcb417104130a8d97035ae66f3b4f6745d0612551ed8f4fff91388bf1f4643a1783f011b32bc755428c47f47910e6052b061fd58486f4d51d5509730878d92c599793cf9d124f7c15700d3b4ecf9b876623ec4500637a2950ed82f9c9072923d6f52d6c4a134a4eca6927886163305abf70164df499f591fe875df1d8332e9c057f91d7ab09876a93ab0ccf8d15c30aeb603880ba3d44bb05f5cd4b511566676da11e4bab3db2faced4101ddc1a58b987602a059606570d64bfd4a47fdc1471b1ab0029d6e7626f63b32bb88169f6801365b355265b02fd64c489858edc84a94890b60af42c9c0630b26a8e117f8211f2020a8080c4ecb754b51ee9c15cf281eff20065ebac98c6fdcf88be1d28b3e2f092f49bb93969cf20281c853dc650907c97de20b08f9421c2c8232ecdd98f8a218ec9367bb6b25aefb3f23f45392c59b0b0a52f972e5ab8a93047bbf65de4182e4f134814acdea8b24300635b8ea8cb5052be2866c7f83fc587e7c002e01a24d42e3dd4b061d18b27875419ca3cb2eeffffdd2ae35cb29019715ef7a13fa2cc9c22de22adb5aa512086d0941247833b0401645c8a506f8a564170690af199aa3a95b77508f2ebad9926bfa5bdacc1a061eb9b2fedcd6cf9353f01e1907ab813d3dfde33780e95e871b81f4df817e64d0528114508761d414c8d3aa019e17bc6d00bc6f4dcbaf0b16aeb7cbed780e413c857c261079e0c87724300f81352e60c954a229e9c406ec6bf982b83e029a5158756480083edab51dd6aa68f933b57e5460f502a0351886852bc8308588af2586b9067445146b0143d33232b88f2a5111263b402dbdfac804340fe57a36181ed46f26927e036324beb53d5db159ef2c489187b933b8af41833ac91f964c74062f23348c33622a6cc58b5cac24f8e8958231ce00f98400d410ad3333a70c3db09117741bb4065e045a3fb80115c9f0cd12156819864c94ef4205fff7af11d469c6c4b999529e136074387f7f0bb10c253fd9c6eff80e26e53733294dc51ea8594e5341a9b067b0f9ae4c7e57db8a4bcc3c15c5290be7a91ca15ba82dfb094aa2973b77b110e10f4a30f4a6e2d820b864f34370b5b8807dca6151871a5e0d686683a0ceeb137879b2f60a505a239724132b1b0085ac88fed2b40c59a32e138c8b2e3220dc596bf07efd38688ecb222bb2de1540aaaab685aa4deec80c8e6b2a1f7d5cf71b996b514bac34582633277e8a04f251d98600b542ff3a6c744a2106520bc141da4ca679f45b889a7f5f96308cb57049a5d3bb0385ac437dd51e490d163a23f372611d1f457b07cbc608c27c5a0aa37cb0202f2d2238373f417528cdd5d6cf7034b8b4cd82c1a4228d6e1ef7075e2e029a43f1dbe91918b4a656c15a1c34d8b93a67fcd83a8ded92cf33da3951ea776923988c2ba98dbd75208ef85f9da2e7dc789de91b0f7298d24efc2f9ccde530707fe8ef3c0db67c4215445a49649b30548b140384e0d14ce3df43b20b5e5a2c6e47e72dac7eb2f930ef81a716e46279a4109a6d6ceb4ca39bab57a066ca8028464f96e7fdfa071d89b736b6d8866ecf07c67ef6ba602d5a24a5654f218b0efa285a7ea9ea9472d352a47e30a7333925696a0f067b5a1e78166d6fe7215438194694eb83ea30a63ddb9639362fb8388f390048b96be6806e9d5a728a3140678b521edfa293c1994cc1a0f50c2f552c3e37850048b85b739e93a1a3f0bb7a1601993b0602624cf3e53ce94fb68505f6cdb1af9e26fb057c9bc83d8e1eeb5950c503722f2d412ce73abc1c7e6d1d59e5608c69103b2edd0282b750a82790310cf4135133b34c9520abe9f8204ad9ca948cbdf155c2c5bdbd56419996c2e92376ed7f13c0d58f1aa9d4040b9ef835b736a99756955a1b7a759bd71a11b6fb4195289b24086025ef1a6039131a63288a080aef7aaf65ab9079fe7c40a8c8f8cc9c37d3660d40320d089143c1ff57479516a59f4e1c6b33b68d5a8e87043f479871dc5b8a43db14ca4b0395e4444331fdbca843317251b0a0889fc404f64a03fd4b6471bdbbded85f1b82f4ddf8039c1df372a53051d1bb20b0f933318e2e39a2446d161ecdff92c2e80b68888c0146602ad300a203923c559f3e11a0b6e9449623df62ef936e007c9db135663f745864a8491e4080e2cb5774a39c27c6398d9066894ee5cd66105f0f3b2f8312d3e8f6ea79cafda22780ba47fc7f76b2c80c3555a71736a633a6f7fde4353a45cda01ad62b9818fdde467bfebf8966531873a013d05ba0bda682639e1966fabe6bf46e6814bf7df6cb5414119600bef8d8892db823c948696ca258306e1c1d2b02407981efa5cdd5441ea1570efd86815d898d723ef94d88425134c2fa92bb87543765225d909e226f5786b5f105ba9de4081d8d72dcc955b703727822b0669cc2666793845e760f78798922d9a956f9e56aece7fcc3751e7ae681a5742831614e8651845e4346c1f563640c289e6e7d4180ad5f73acba0f79387055026b55c92807c089964edc16df95a527d3b1486d0f0d74af0b4380ec358c0879333a3e4508b84ca8982f533b82cbef3bddabd0e04585ee35f84ab0f57e33aa8ecc363095b60c03bae290313e35770e9ada90e041acc39b4c4a08882f8936af089a43b186860b01313d642df84f3710f7f8c5b183182df2075ada900e88782df9b2d8dd519ed9914c16d84c231a6b1130d308fa3c92842bbd99da2574e2bbfcf8e0aba0f06cb1bc0e0914518252559bfa2cc50b2b846825e9c90ae5c9d9f2f80821d80456e28e5e83302eacb58c626d5f93f469796eb9c8216b398ea7e151378f64aee24864485c9010256a8394c08cda9f1b4c26107a4bc4902a3ca8963808598104d2f6734a54b2a71cf0dfd893109d701cf0d61cb197b6dce6414e11d52a12299d006c9a899ca71a615cc865aaaf0584ba655e006e8879a6a2c6116986de9647863c0025918a309d27ec83430445496503a5cb4c520e134c65c9a6b8234f1cb2ff611dfda497f5e919f7733321af288009b1cf7ff565759623f2aa1b9c5ec091cb050f5c20b09204cef1c51c5a7ddac0a0959f65e072c1127bce32c8c40b5357408f7c233e261c9b258b8b156788f4f2b3905bead2b254948ebc66ea81f92df4d053555ef4c1e13b866e458a14cf4d7ff5cf827ce84d67e73b058517cd9d9b2b0d0cf0c03ffc1e260a26283a0de4065a63c5a063e948620601fb77de7a48a933d90443c7bdccb47471ba85aa4ae033d4eb68e50d185942cb487972e23af368efacc5a521924644954813e659871f33111ebd9bbf302d1afc8e7a91251c48b5a565c22e54a30e3af0b21476039c462c736196515386bb3685d9d26fb43fa15c6fd985ef725af13800ebec9c59ff97dd2563c86349b94199830f26abcb99ba3755569e4c8e91ee0b901dbf0422fb8233e5610442ac822bb714e42a833ef86266160dc0d5fa25faddd9ae1e3921dbc073ed35b77c099890503a223fdb2b2b9d3a0a9d334b3ab93c123c28c084fa2431c0ba988e57aa84456a73c36d97735268ae3b735d457de21b89866a54507a07a8d4cd4565674b315251a9cac0b67ba5ccc35a97c285ae5a8bcc89d7e4344f1d8ea25f4e0a2c93091fe5af83ddf5115f10fef054e52e3a0ddd0aab8dfac1efc923035f09cdac3757ee2da383dab819ea0b0ae22a4b11f30b26b51290f6af3fc2d27066e613c3da1c35b2ccc6a501b0df2f1fbe5f2468bd20bb33f23626ed864a614e8b77cc708b541e6b7feec67c02aac55754e1f09d53cba678578cbdc14a21bd14a0fbeff06c165773ce793063b0c28a6468a71629f50bb9570819110bba5ffd6698c4aa309022c0845cb92e77234b6351cc45a175e7b498c919983246b1bf4c16818f0a40878b1999287a0115189699f38b8b7ca3e5c976e245676ac57a12cfe724554fe39972b7b009e8894f69de4592f74f29412773c559970cfb085dc83c0f0338e00cb459399ede7081dd0afe50a5e72700bad7879262ba5665a31139cc77490ff8e55456630a38d6b26e7ca54866a4c6b07ee4203605531f4c76f784c72b8991b41b224b30aee175bfa2129305a309ead21271382942fbfd8b1d634e5d3c407248fde1f10fb21c7cf5962deced4a6be6ea90f53195ae9c8bd6b8cd70288671ca0377ea9cb92e3252d1f6036c14a73c13f1e9f09dd72095c2b7f18da46406e10181964224ae348064e3ef11e5c2efbd66a708990c0b08354f834c0c0db04b498d298de98ebf74957802f385720d980107013ecbac11fd8726db17c903ab6c79128602ffb471f3e34fde759c4bf26940ec451cbd40206ff462e4d62e8d72270deddcae4f3a534f36faca5b8df0801eb689beabf1e8c7176ed95237f9184a30bcd5513e6fdfe38a7cd9d6cc71270d63370cfccca04cfacd05f5f726ac41db24f0b4a3099bb4ef02ebdab9aede08bd1fa71c7fa53d09df1f26595648d7c1f2bc79b61bee67b9fabd4b03fac23febe146447b7b1004af3d41758869d1e013d6e47c2b176ac4ba8105ab60e7dbd2fd080372352ab202b51220484297019c3af7ee732ee1ef8aa215dcde6156a2935d29d4834c953e694beaa0a67b88606e3eedaa971ee2886b43612b127b1a181dbdbf79c0e2000f501ec8f3d6c9ae4de7a308cc4a2b826c90102a7dad7be6375f581511b6d24698d1d4acc367fe309fd01f2211ed6bd1630b691a1214c420861c84d0d0150d198e0f098099539e80551dd1d654c9623f9f455eb21e82a5d1603f452d1e1279c132d851e78238b37164faee2f162ed9ba359986468815eec665b69ca62056dc1315c20f544772e9c62beb82d24691a107346c5b3c56add2edefbfc86af8e9e891f3eb089af1cf5912c5723cdce4f01156acd2ae4c0eab14385bf40a6650d99e23b11d51b8c1000f273558f2e5692fc7b2ca0d3700a99f2e9f119ba4375113cc2c6ed0e00141376acf315190e6b3b4bdf4a8ff1852d901e287ae2e1a9c1115d295429a72b1f89be47f19839e9335bf4fef81df0743641f8881fc83f2f42c0f8cc3fc549a089d18e434649d25d181808ae6a2141ccf1f0d176394bb8fd14cb0fdc71cc3439b300e87b119302395accc6568e9e771a03e6acc8054f2c259fb5616e288ca34cfa394942b05a749d5b50f56a5db720a507bb95fecff271b99c59497b443d4eef7652724ac2c731d92c783183b22feabbfba51bf53b74fb5a39d48d50ca866d9da16157296972c87c994b8b5f9837065664bc6715a19b160de2c69dd10a7f809eb1872682e64b790ec31ae7a04670d2e3550c00d3457d8641c624b1068ff063ac2b86b8c57c63d5870454165ea6d77cc51c6ec244b86fc3db8eff6dd67d33467b3c53f5c60d116b3c0b5c5271fc8e08700478d1a4f63d26539f18dab2e461e1452a48fea340695957777d811b597f40775e0b543daf73acad085e9ecee3515728144b7926f8a45f14a1ac5810fb8d98e00b024e16a60d17be6465f6beb7331c96952d5b21d817e465f81ad9290a9571e503659061fb33aa6cab045df269baf34334c20ffcb1b6c870c9626a41d449bf7e8363d218329320b4033f0079f0795d40f3205835d456493c8354b6e73e0f7ee49739424df45db73363654f1ac1319cce184ea035939e5400b27ec3e79eaf34e73260ac6b67b811f4a0504993443369995fa73f03cb14b6824f5cc89d965ef85206311f25455149bceba308b837dcbd7c3af1b8ed4fea0f78ebe51d02fbcf408fec8478cb6772b3b05b67705e5eeebf83635a1bb2b75b56ed43621839afd7fdae5d3bb6b92a1b622805769464270336ed6ac4a1c97b2746502f5f0fece1653f9631aafbf6629fcaacf116720258c720e4ff9fd41f11cde52147da87478eea3fb137fdcb6efbe92005185eac505e2c1a34946d2e91b74eaa29444581fbdca93d86013157e8277839cd82cfb78f6a63c6abdc0834afddb216efdcfcde3d8653ed75ef8e245f113fe433cc08fb5a529e6533a5efad797d758536ec40500289c2ceb4b09b392e24a191261d6ff18f3baec651ee7810704929f84e99db8dc5d9629c8ad581e22623fcd2562989f9e2d4550cf5d53e89a29907917b18ff49a25de31eda6a60134040b46423069a963010ed33285fb15fd6d178a704fde614a45cf2f0d63e631bbc8cca39a28a01f9f8140081e3e96d17ab7e303aab8d3a48ffa48af490a2855ad3c9f54c10a2318a4fd1e3d82331c777531004e08979565914aeb0b0533968c756266a2b5dc2e4705021ec9fb02c38d178a5eb04268df31530345f3ce055a67c5473f49f0916619d429d8859ea2bb71179f15c63be8df80c723a09789c055a6258571f5578cb630854f83859f3821e5497ca519d224a4e8df97cb399136e9d970fab4cfeeef584a5d22c5ad06284232d19553a6af089af2a3caaa6753a05a0d3a50117c0666b3c42e59b9a1687353879d6c56c11b6e18605dae6191e63a3371694b26f7355142b8fa485733a7012e22c3e67a263cd0fa0e93a8e55674e7d2aee4e64848351274408cbc8b7e68560bbc6be5b0491327efdf47859a7ce6dc57caf35718e017b78e5b573db47e9a7b69483a83f430c4b86cbf1c9feeed17f412e86525d2f8a809931df10b5cd511c54d40f73fd4d4347849e390d7d3807f8fd4a8fb16811c0afe4f68b88950f7309fd9cce104e6940d967683b92491c13f5a5a6a773c5c8444d2a52c994968ef7eccb30dc4856a401a362c03f6b019b91deebe423e508a22b64d1332c0bcec831405edd500d33711c34e8fe1bbadbf7451e5db4de9468c42745daf4c137b8c6329b595d37d55a414654710b407475717a0d8d2a51eedb058cc39a26f55e8ffa200e3b2c87548c6e028cd82eb746de0de2d22b08c005b6df6b21822963eb7f3c335ab86db962350cac37f66d7e76d09e5adcb213a05d8352e49056c18e8b369ae9283891b61ae7d9bc0eac2e949ea833cfb7671a2ec329cca92cf2c338183ccef5a49645cce5ca21c9f743c8937b074640d2cf81076a6cb7e88d919f890d3be1ab251381bd43f6886567b1fdf32fd2028551347f3499a030ec24d0208774a74827b20bd657670ee4bc156e104b9aa56523b8415e811409ddf5a426ef9a39e70bc5a4919b7d941c9265e652a87f9244facdea5a16430f833f25d107f85c5bb585550681ca93ee91118d190e1015690f62ea6c75e534b584fbc583804e757551a24610c95ea5f90e5d4a3957eccf9dd10e6c412690ce21767038c73a9164b104e6c441d9fd12e7ccfe98fbb70d2805e264f4f0f61ead48d20d0f55f1b0ff5ca8cea4a87376cce319cc6b875cd773b3c9a0525623ae81de8a91805cb3a7dd3893130e40476e0ad53bd886f16570442e0d795dbad471f897060ba5f022c8f72d359198b8cbbf2be3a157b1c1016fbb3a6704cd64c5457423938457f6346348b792f6963f6b598661e252554a586e00abc624a0174d1e337e64092bfe32965be53619b080b4fc89b3489794f673afcbefc33858053c7921a052bb6b512210dec070d7af76c962f3a264b24f67010fbb2b135b02f75f8831b5e491079f8c76b8bd1923814d4a3d6ac6bec0f4a8d5d1f347daf9ee3480fdf709ec19b12607fb7219fa20f3150c82bba6486b1f83e2896d3673b4bec32b37c28c27dd9d413e7092d58cf80723a0013fdbd81a48550b29baf18333fe34941f9d116da3779cc93d506c4c24019384c5fdf3574c19c37cfcbb69fd4d7df28f629bed245b255c0036591a3a7400573d7c958ee07e3f4d418e8dbbed3daf007085390afb8efad3a008c4b5772f58fe3eb9e6fdef3c67ec13325bd783ebf88a94deecebffc15185e663af52065b47a4bf2fc56b0ae4c45002c5a9a2e129307006243d88138dec16990b7abfc0d138d2b0b29f5796a1aa4f176c183cb4c185c34a1803de1779129be91cc2a0ab639569efe27aa1c002101e000bb6716bf0785d99e25702ec1b1810f608943ab5466b3c9419ef21d40d86d4b22d7dbac9924af2ed075615b48326ddc52e50b8688feea26fb4cbaf6cf30597dad3679e75eff1aec280bcfab233541c861711dd5fc2ec1799d04a4a4eecaac2b55dba90f80e1b1e0f056b3dcef28877b693094799d724936737ad030907c7845c5da9c6772cb9cd3f16793a68fdc6e9364e059b0f27aad080a68d73939652e07dd49870293a8cffeaca40b5c56b4a3611e3525835d86d61f1e386a185b4a12a5013a8b649858de16279f9d86983e9115a314d488cc8cc6a735a702a689306d95447230be4a2b7ee1673bbcb0a3b8ff2713aa33fabd60b0b8505d2b39e7f5439ad3716f4d5cf6d6f944e74f2c26c87a8499d0453609ce3e4058b89ee240eca7a82a130eb5a36c8a2400958e5c622870b565730186fae7aa3da1830c04833795bdac7a389819403318e39de161c2244986cb9e2f049fd9c88c0acdc6dd87abc9ef69bec60299fad16ad1b4d388fe526c2e4716b2cf5d400f43c7a9c2cbff7068afbd36fe67b42aba82d850ee9f27c373ccda3f938d83ac171bad05e2a9c559f143d6f2b68d75ba5f847b880bf715730361ec2746810c834beb10004654c4076679757609949ce3ac2365412eab08048842385acc6a4fe099c6da1001245e2a5734d49d878a4cf2b2c0f5bbedcdb23e05c2cb10e12cd08514f7c16f0924da2256f9c14b8e038170fef7e9f3d2eebb96c03731600ec9e28312a48232d4bdc0063f40128d5945a2b0bf4c96dd479982647c92f4d982339f4d181c45e38f5c3dcadf2baf0fc3013c7707110fdf81d5d2cae02abe33636bc28d53aefa168524363d0aab514150eeb3c9571512834b2a90437132e1986ed4c693c35142fbb4539969900240d3e2e9b1dae5adf15ae0ca0d97c9fb102f8b2e387bd030b40d6c1117c7e58c74994b9e6e403770215adbae0c80532f48bd9aa2921d50a96270181a56d024a44c8c10f39b8a4a817a7266f123bf847cd593b41d6bb087b170983338d8024fcb762b2293e31bf39746434fc4ed803ea33004c2323c5df5dafbb8b47131137411057c7336b06eced3a8039d4bc77d930721cbe056f1b0763005dce71365c8a2a42ded545d668cb880abd50b4bcaa1945d7cec373207f0567c890806d93c7dbd964d38a734273f07df43ae5de34dfcaec1a70961b558679f43728594a92404bbe974bb55e0febafdf7ffdb2d08be2c509c49b59780a1637c912de98e368da19e4770619c9cda868beed4af3a5fe58be6d009bebff3731c0b44836a1852f1a23a82835231f674ccf2235b466b86b14a8518fa048aa6b081e587c18559ae6566cf34205e88ea766201cd2269033d626302d7ba4fa2ddb47a3c803cdbeb3eae3c87dd9b1a40c1b68ee6d9177e12788d23b9043c166f16f8e80a8a14b2ed83e9a35f301931b120dbbb6fc9f9b93b1ca09fab8c5098a6835cb900bd45fd2a9820748ed9e9da0150c9cfb3ed24d389b49285fa960c7eb027ef285f1ce8dafc8e69b8b3ef78f3bb3be1946e926eddf62525f93433cf32d02e4aa6e7e2b824842b5d14c21dc4c865598b2a33293386b1970b76d01fa117bc959bd1d784835eec16afe8dd2c6b71227620aa7802529c354b0e9b00389afa8477a51f93dc68b7bbb09ab4078dfbf3ba6f1ab0faba47e4cfb9b1221eab52f5a1b933e563dc3bbd4b7ea43e8ecc3dc8d04b9fe9f87744c7cfe5ef63959a365ec23379aa5d0cab9bb41ec3c030308680324d1fa778c5f15bd3b6182c71f5011ca8684a2a1319a31664175c56724d38c8ebe1e38e205284f70f60949dc0cc2cf839d509013799c50e33f182cc3a58526128f50220295ebf9cf5d1be8a07d95d318857a1ffa572ceac4c8db29c0eb565e84242b5b329415222cdc86d7df79afdc473e7d7ff74c6591dfdfd3a70d1df493fd0ab28535d8720b55e5c5c0274565e899d245062d392ce501a64345fe12696145f433c997e705b6566f881715181de66c4b82909df2330e8edd15ab37ca56aa8c09de4b2922b2a420b88d5163e1bffa1aedb0751b7af321dd59c42dff42548eff2b81d1e2291ed44316a3988f3332393e7ec0ea90ad7c629a0ec9f660f6ac177357abfc686ede2fda649ad19a3563c666344834cce935e409b0d6e8f2b1b0f890d4ca7e0f7a964c3dd0a1a5e2b618ff5f62cf8678c6b5645d86dc8a9c9857dc49a27d3770f2a10c37eedef9190822a20a5206142220551bafa3a8a885204b74e81625cdfb52308ec2b7026291cb71e37e3d930c98a9d4c01c928c3211e0f1b765c13ba10fb74e066263f72747f2b8f47d5daebe56581ee60ac42da8c99831e7df649ac3de9137e1352a935e5c53bdcb1d7793bfaf4d400cc7c068fe29395f815b967bfb36c93d1b2f0911576fe5e9909fa3c07cf5a09511536a4a90250f4e75f6d231b2b4b1ba12a5b5b126c2331cce245d862545c422352050edfd9c35e629867836b181df3334f64e53288965b487084f208209ba9d56005890cd9cf0a8bff9610d222f6ddad8c63be6183f77d791941ff89eab7519716da5fb43b473d1495a3b6cb0ae311b4e9ef7ab04065e2d3e3228ccef6380e784d7ab71204b9bc3da46735213e9e30264385c9ecd817870201511f62d4de2c79ab5e5a6a446118f1f4691e26ed5550d785330f49cf5fabd45c51b9df11dcc5afc94453008dafd1b10130b7651b8d24c77ff803629a21a9a4a7e9311cfd8d25c6d2eab8b3b1100b64080fc240e2f30b203b4c047a5c81d5f3b78dfae0a050e6efb38a4488141a9f9d0784fd05c4f1f11b3fc68b39990719f844b8f077cb0895a0db86c4b1f9aa69d6c1dc0282a1cb1692c1d9ad9a30e0d1b90e86bd4d8bd3046cec0e7a1dbe6f58a2fb206f8d4cbc30b9ac4bef49fe02989585cb57fcf30b31aadfc1b058227863ab49c64f1a02601695890de5e1f5abb79e91c397500de8682627bc4146410464045680c400d8eb9d31bcbcc20b3d2f72e075107eea31578a9bf09716605c000afd3ab8e35eb4f1295795c5175f3ff79d8c142d06b5740d5fe54760e0dc08fbbb40a46dbe8d97256c724fdba2b113bc1a1ad39879bda320e68a9f10b43f362adac3474d9df8ed84748a89698953ffe7ad43565e1cbae87f656e26b792d95e0951467629fbfe96374e566a6c4e48e2738054e36b22f11441b8c150075b0a9ee7a7d14941fdb33f05313723c5c2e097ccf02debb3957373c408efe670fc26e944529c7946ccb85d258fdddd99609d6738be5d14c67474afc6bcc310c97108287ce45b4fc219fcd1c0f0841e9f3465f61ad66bba9c5308b8c92f8e69d30650739796f3532d4b82ce871f565685e0d927eea4a844d7324c06515e1a4bf4731e018bc21edf039fb42e4cf04368c54832ee46321a1277cfc61fe2a7f40a32476baa664d7252cb65285eeb9db032c3a802b9cb6f6544076e08e8515fdcc8002aa4e134ee74a156b780984e8829a95450334b484660c3b1554c104a00c15b362dcc70d9f88e0cef59d68c26bf53e1827d5f0cea38f94b41021ac40d4eca1c17c65a751a60436d81407bc48b14918cfec70c8401a0a4924c452b61081f203933103d000d5ecfab2a3036e060ca438a6017184fbc333398fd94ef6b984620c5138f7c9364caf18ce6682c6d79e669cb8a44d7a8123b90f8537eecf7731a18100e260bb03e6fafaed940c0346334a13c26c2607b37175a6ec3a7ef1560648cbe1febce7e14c561a3869a072cb8404ac77065ad1c20fe394e2cbb7df6aeace1eab0a6dfc24116a4b1867600f3a0ea99ff2547f6eb3a3fca638ca7c937e0e54000c45fd737a60d18bcd6df3d77e843d53c6714f93f08fd9fa4dff1aad3c03752e2187b68d409f30fb4ea90c6b7a9bc60111f3f90b4d33fc0bec7e3cdcbc4cf23bc8a7f936a83e62a4489b651302c55b7180ec0591036319dcd56be9f9053fd196736c87d701a2ece699acf0d659a7f265a3eecef2697fdb4a269b158fc57f1be45675c0bf9fbb6d2a85c8260b0efd0aa605056783b7d4e19f343c8b0d090a973f7eefd148abdd513d86a117b3e6ac43996c02492384cc837abafb83bf46147f52387cef8d7e3b220321a8448a4fd2733288566c9aa7f552296210269137074ccf992bdea6f9fdbf60471b3b568baa5d6479710d443d681c553a8c7b724d4165f2bcdf48f6fb181b3afe6377f37d031fea8d0ffe5035c40acff231843253d00a5ffcfe24bf202bf62066c00799f81c72b34855f71ad582411c01b4438aa6f9146988b83959ad7313118e5c1c123180a10301a4226e62f738edb332cbcc09504c65d511e0041ba95d06caa40ce00dd21a600dc00a61a8d602c772a63cacca5428568b7d0fe40a061f556b2b8d8c5c38e7ce2296159da74a024f5f0c60f2c366a265933884ab197babada82324d5290a1fe7306c92d67f765a71aa4ae525d431a91a053bfee1162d9ed861d4748edde1385a0a5c9ab89d77320f5cbbed448ebcff91c6c4d5e7ad805fe56236769ebad6cd8abf5dcc405981971c0d9b6f9e6cb260f15468291ffaf441e4921d9dba47c9d795b1173ba29e6e40b0943460d14c8fedceebb696ec9058fead31ff96e7040d0839d7edff368dab16fadbb4208957b0c81a9b5181297836c53f4f48d1e339f87bc405c88dda9b0a2ac17c013518c3f4e924e440290885227e2eae114024abe58407cbae8fe7131d7d59ab88b89188715a39f28458ff866ba0ace140b3f150d8df350fe1eb150217cd51d56b21b2f248d7652cb2a3e0f0215846c438d098d7ab0b2f44a842ecb22d4cb26e7e7a00daf97b3949dbd38d76bf4044b01fc1be0865098a720a608dc12bdbb91f05f25d741ed50bfca1f8e6da2b6fe4e8056aafb86663550f9f7d3f0b6e8d2e4a6bed3e8906b693ff6f859eb72e6bc0d13de132f8eaeed6b6a3300b50249c8bd15e1f561007bcb1a097091207ec778603c1ec3f7a22b876c6ff088a254d71601334e3568765b69f0fd58df6b2925210670b97844a825e520387892cf19a098af0909c358d21745a6a431ae416a51bda403b9810272f787c0fb3e656ca6c1f4d4d66c5f23824bd117a5ec18fd37f7ae38046d0ab10d88ec754308473f3022de546ea2237567712125099fbf7cb6c0880c1c977c684ce54fd95b743bd488a7cf98696225411b14bdabad32bf9740e5190fe34756a8b57f025826be1033a9ce79076733b92a6d9d4c509cd63a8c7508e0be187edf7706655361167691033a178e50fa0ce314a5baea8d2018d94843cdd5b0f367e68048f0857417ac623308b5a3a5b9126741aacfe47e5037936139c253405bfe105a9e6e2a2b327913b361107e64723be70977faff21927d754c921483ffd3043d874c53dd4cd96164cf7aba9bf674f87198d6bdcd88f503c18643651a932e8a9a30045a906e23dfebdedea731c2bf22560fdd1f7ee6e8497560a5ef3c73b8c8d939ace1b3bdf0a2b32b9e2bd5c11072c230690d78a7c9d676d424fca12685704fa20f279410db55e0801658dc6717dcf13da36f021dcb73e1cc6093bc46c78d0404f90e94260b3386b966d77237136fed39447055a4c499dac9f1cc2f5229b72fdb66855203292d3da20331e2216adec7eed87f0372f93f00317a957bf6929f59a7f4b79c46d64424eced93450b89fb160328d467625c2b83bea3e908a4dd30cbe14bbf425a6a0b795ce807a1bbd5a40c08091bfb26221b5c54b244987215babd56206ff679cd97312a58939d58ec6d014c7f6e41c8624f788a5488c940c272a289c357b61ed9e2a8422ce74970257da8cf7d766d63157dd6b7203e8949de9947a21403d2e3a5ae3e18c4c34cdcec019befa4dd4a172c19d6f6672c4541ef8d019155c84ca7658f94f36118fa71907c7ab2dbcdb3c09a0938f95a8d9b661745e0e1f51e0f798a9c1585b29c1086205eaf3206785c7b8800cbf0228747c032ce0a7ad8365f9f4198db7ba45f92eee952c12ab53e820e9906b8231072012612df4a3ece7eed39b16547707177d48ea0e595fac7d3715c153f10071512dc75680ac07a47f0f5fd9c8510d00c4274cb7e38b60c1ebadc10b202ce270026874be2aaff3280964359331839eeb8d12a3838242ac5abce724ce1bc76087ef2d0de4333f09ce8466a45dc67c9fa56cbd6630db5030ae5b3efddd08c9f1a414ef8a4dd389d5bd8bf225c089476a383ef5f0ccd4045960dcd98d1a972f85e0c7952f52d09fba83c332251eadf2df5012f6ef7dd35c1c014b1a0dbe138900a5ffe65b41e6389aa6896d8c373f50f4527859adb76660d147b04d8bbdd48fc07053c3ad0106808f0571e216c58b02de7890c224b4776dc9443004fc6b8db7fd4ac40044860f4591217c0d1e32d18ad529e80e60ba4e2ab6fe6e21b94a70b55eacb970c6fab453e61ccd5641b3dc172e7be284c697eab2df18628491a930894900fcb9b10e6ea395a5b953f9cc03d873d9f3e8fc422892a7f0e186e37c073771979eaf59c94a1fc02df6002bd1366dccdef47c7703fbf0709263f57a3071125d63e7546e872a64aca4b0ffaa20449d8ec3337bdabd9150b1be0c1da9f59ba6aac66a6e0c26491b8e21587d10cacdc82a4e0b3d67f70b7bea2d659e2d84b8c1eb5e1e3bd53b1f453c110246ad428bc2347a5f3f2b040d44eea776384c701604ccf171029b1e0d00ff1ffaa7af5b5480ff284e9c834e0f9c18db21eb4f3232d2b8abb52ab049d28562248f652d7d3317c74cca34c1d3f76bada224828b7e967a0246cfcaa4cd6dcb070788b5d436a31606337e2bfb767aa6693505b5b431f6cf8a8debcd02f6c2b517fa6f7fbd09bd7dc2d8f0f1e2a648d5d2874c7f1562d913d4248fff7ac452822ce1678a2a348ca4ac2f463462cc6a1294f2b4f49f71e3c78721fcdc12d1ba4ea036d4b71668d7039c7ba00fa297434aa0731e6d9cb98f86d85fe355edc833730b5f5e6f27fd4f75c29bac4840ec173d8ff434e3d0d6820f67d00861ae129a707991fb0e6034e813e9d67806685a0dca0fc11a94838287a7830fea05a772aba9e71c036bf1a6d662c7858db834c616fa6219a54bc4b1629d93d100798259ed197ed6569e7fdd6f5c7e734576b8c7c2a2062e01632e7e0ca8668511d1aed973a8bb0603d8f983d39b50d93981c835b9033802bc53945f784e7e511d3e6860371e99e319eec25339bb5b9de9f29be556d4541dacd3e0c873361fd4415804acb405e8c40631494d05351b67822b3fd46a2ba08278763900c2dd8ebbb236e153299f7482e12176144b5427cb9f452c1c107b2b95081c25a12a80520aac2563815e92e1fb8e0dc3eebd9c59b3f9924b922a46b24fc5f1afe422826287d80174f4dd90098d4462e2443fed50c2e0f60bee41fba1293087937a681e423ffd002a2c27608fe422898f83839e32f444c30a12b3c2cf614c24c192b37e35660c22ef0f9b5fb8cf4d0d78765c04b0b04df16cc4d7284eed03aa402083f60df1fad198da56aa794224045f3dc773fac02c80a0c625e01029166340fa0f79c3514ed3dc8e3e582e344590704620a455fdf9ee276ada5938c215e8a81a00f7e6de9da612d7b8a1359f78cdc30be3ccac132d4cc9c6001b2ab008b2a6e8928dda5fd59f076ed88518a5998fc782eb0cef64c96d9d902524d93ef47bde7cd02a34d21e8c4848a57997cb1e01a5c4473896b0a61eed1945cfa8192799d9bd822543cf6903025b8422b32a5c8df30be8ddf5e69a8145394844cd88c7e6008b0f8db5053f4e0c3ecce503157114da2d70fd71df36865f32698589e75fd5717b32927673a20d81d1d80864c07cdbe81536d05521516030c2201eeedc33d068068f678882e06e5f7c3e03c1b64b3a426791894ab3dc18295be50cfd9aa3de2940a8f39d9e90c2dc716f8ff7e0001c2992de0fba2467853f1384103523b01d0c468a29e13006151828a067fec32987b9cff1830f6c0b57ded330fbb7b9438432682e5d862d8414820f4a462e72159fb1caf3041b0c04b64c23db9bf3f6eee007ca1bf54050645432f79ee1826c5ca80f360721fce30db909c0b3ed6f681e84b7fe844298f484cce4ae91c39c5fe857af0bf8bd7815f81223c540e41299c2620fff56621a495dc6f2ffc2e3709b1de843542672c40c2da3bf53e2fbdc1e35b794b228880eab61aa1ba0627c907250859d0dd52b8d1b6523680e303a603f77299459ccc40dbbb7d628e0748c659c41910b33293300a603e42165d6c3ddbff7810e377e6154a8b4498f6d4df434a7f0a31b243036759d655b84ebc5cbbff62afdad1d353283fd21ddc6c1b7e6e7a8dc9ea75fa3d0c03b1d45942f6aac8a511e41615f2f3f41719c7375e0662d4e86a54cb371fca58c2cb851bec16fcf27e862b13b2821ba87b5272b3be947b8369a1cd312b617698c75fd452af653f55f05d789da600f69ec5ffb2f1ccb4d1e5482ef2f7b569c9537b2e7782b1b13b267b099b457cd856bd2b47335eb3a379214f5fc881d213e53d6e93b0f169c15514553319859bec3b5b2cfb0805689a0de3e58824ebed64ad54aa5d546dedbd343a487cf8b891b8338f2cac0c9f4551c16721ebdd5aeb950829537a237640dc1899229b9b564044aa8713e054d8edd9775deaa72b2a96c68f08b7822b48b79a0b2068440e8f92a14373052fa3d3614a20dc283918404a9204f88a20641002240156c300449040e370299497b80c834601838479549c35fe1c078e4503c1c673b91adc5514123a9d3e4bd186e6e8336030a9400af22682325022ec00363ad52030147a6299d16228724a2a178c2f91118014cc30440e7f848d8e8ac6c42978be1c5e491ae22f8190204bb0323015700e839086c24be0b0a95990316d08260a1de3f1782562963c901781cfe0a0a248e16e0c26962c1d4fe9f10884207092acc682246390f3641912307e1cab240082834003a021184296a0ac20676013c1c5c02258149e89a3065773a779f29c10790724004749991474e03a500504ca89c0c9e962a0394e808c0702820cc182a070be493c3205956e1927097bc04b04e1802283341ea6fd002d62b41dae99400980794878cc418808a6453620700018000400a4a4240a02424207434f5985f43a5c741821046e3e942dc0c393f4c068f202558530a3c9a074ea085404661927097b1099c19f8ad7bb763713bb3dd8db8fe151f420b6983995dca46b6dd6df6eee2236edafcf2f70d33e0d8d31cbd4cf68a55d2fb79d4beefdf8fedc3dc6d3f3c04df3e5f5fabf3d0ed3e7ec0ae8247a87d60bff6eb77a3fbe7f8699afd2c863d83fceb592dfb4aecde8d773cc1c3e5680ab205e08d5b4b83df48578a6064b8b179735a48e82ff5b2f61ffb6bc553a06f46da65c70b10f12677388badd2d7fc487ee0f5bf3110582734d71346c1105200326009eb86b650ff0b274b32d42b95280f802ae02299b2fcedbd642513c7f60e9d3889c43fe9f5f8419a0d53444e000ce9096fc6403c90463144393a17398c203f7dc597ac30262076b918992e61b205361e9b67f1b29c8ffb627e6b7a1a18b26f6afe2babd5001c2d547d9b48994a130132c159c2657c46a0412a69c981e8ccf2947ceb557b650bdef86d06f4209282ef3517bc91666f0e971ceceda4b36d26d7d4ebf08f1b188d83cdf68b84d78832a233d8a8610d3bf8b1bda092696fde424d801cb6f75995340d59a941b9c608f2e3bdc4c82856a7695369218878b9c06242d6aa44e4cacae71566e031a901f4420744e492d5f354d4ce2bb58b2dacf2bde5b3f4d4fe9ea55e4d0509690820655406dbedd2fedb26a616ae1542abc5f2ca08e769db510ac49be3c19676f17f8263afc5728b2eae06bab7651b2ad3ca638aca7c3c007595e9d945b7e040551bddb45bca1776dd5f4d7e6b27ee5ac694ed9ddc5f8ec20aabb6ee5aebb635f227f53d373464f079bca5125fdead9470ac367dad614befd588c136b9b204dd521a8999e37e9bbdaba1abe423b9696925cced39a3d6497ff18b786c166bd0f6f14d8ebf0f2c04e91577a84fd26fd0a4ebe698b0c7a589428a3969834c5856d5ff6f2341959c97e0a411581a3f58f6dcf14a7003391a6804efa4c1788c52071487f8c1be4863b04e638534b7658b41e465973ef7d216a475f82ff6317c630c75a10acda9696bc66765169789215556579af079873915ad2a8e06d2546a5a869a844f57828a02afdd9cc3f1cfe56b6f867a6854326b909d01a84ea34999351a7ed7156f47a44942e5bbbf6610c926ab01a8bdb9fdb500b31f038c7b92cf42e4ed78656a0c6192483fe910c5c3b2dabeacfde1195304521f72927123209dcf407706a2984fd725489824297ddf30083087269e2835df97f39311cfc96d11304f1e06bbb99805a0fd726d40b41af6d9049cf609541afe7ad59f6979bdc2270f2f4fa35cb8ae448e9bdd8322cde41f064c33edbebb4ab554acab7ce1e8800257446ab210b823f814cf2bbf3de84a681c5bc18aa5e9adb7ced6b3476220158b18d9f6840f7bb155fed4288c80071f8fbd685884c866c4a9d2b70b505cb6810140af89011bcc64e625450985387720310aa2bef0019e08e1a23bd78fcacf73e4ec7fd88995255f4b77f6f74a5c24ad1a15f4585a5200e99aedc9ca6cea6f32d4cbd6e09ffa1c9f32de611ae3198f4759142631fd4ed005e7f0e27ab5fb8c4ab9fbb31964d8878a6d69b22e11256965ebae9f088d86cad73f2dc9fc035c348ed7ccee2154a6dcd4fa4bcea7dbde0400a72f4478e04b8e84d9f0bdaff7620fd4bcf059f7f2c96612ce8debfdd2d570c839697cf048bde2b52c81e6595fb2eed4ad02ecd1d351b42fb34f01c6d136cd8f67b281e5a9fb2052b934fa44051669439109a107f52a68a6f65dd966569a62ff193f0ffebd067d9ec7cf489f49102f3a3d76be7fbb15fead2ebf5df65188e7872bf8c08108490a2edb67d8a623ed62aa7667f0ea7d04eceef7652c4c015c4cc9f7b4c949af32bb5a7231ebaf7a106966ada4fe31d1e65ed4c4f66d6533f0b2915f467bc1ad4702d727b602544db84c97e84c7a655c1eb28d1f0a85d42398d35dc764fb494501d17ff03d5a432418ff344ab3cd337ab9fe8b42b1fae5e103de979a2dbc0eac7a2e95d0e22adf2a0bfb592077a43a817825e10520fc4057a3d87cef5bcdaf3e99bc52b3c8cbe457195077a9b151ed6eb433d105d8fc260c4641e7647957a70eb6069886fa3f7acf43a9826f729419a72f7b3b867baceae8428ab5e8fa6773dadf4a4f4cdc24a8fa66f2aacf4446f032b202ed2eb91746e5a567b365db3689507bde32b39cd47957e36c48aeb50f7054acecc412745259e6e1d251f6e0e97f2a0c21e4f773459fac1273d3cdc6896e2a1f482d82aefe1d1dd290de29eb5b246795496029ab8d15a6050ce5d4d1bc92b0c1007b77b5306626cbf2e13a3c96f1fd5fa90c1e6c186b217d81f0b336fd0bc9ee601fd4bf5c7dc9ded9638eb27255a8e7e0530bc1a488f7db9ecf6ba7efc018ae8606fa16c8e2144edf821f5fbf8649ba42412d9dd9b5b06d605cc0570055a7af0124e9391030e220e0a07c4edc0f1e08cfcd0419e5395c8e066704d3890f7e08ad420837314b9cb57111c3ac873e86bc816d6a551c3bed04383b5d65a2b330307723f702f22473c3feaedf245a4cacd01941ada941a99151c5948062f60a12666dc18b68746bde10c996b7d0d8981f53234702e8e3bb974f91a4283212de820c7750c9da9a63a28a4907be52a24a51b97a75e3d1e32be3d1e589d3c80e6daee195d0b1f9bffb653fbaeb5d6292b2ed9af6659b6b9c82e7d7b3c645c7bd9b35cdfbc9ab7e6c9162e34d72fe5fc5ad23d7ba7c7b1e9d5c576afdc6ae9e35a6c4f77ea957ade92eaafa5ab7b963ddee7eed5f340eef6abd884b90a15a6d31797de222d5fca5c2449111fb222588a5ce945aa74301bed5c440ba594d64a5f4332eebd17e3211e38b22ccb348d0e2519ea511a4aeae01e1a878c4c2e232fbc8cf0ac983132c573fa2a42c4506ca88a11197af7a91c93c9c47146707051a2659078f28ac143a17a4a3559b12f32b8424de018437ac21868cc69c91071886b0850e74e31af2136e4d701539aa6e9fd1a6234c48a9665ac8d689923e68b1193489697944a2236e85dbe888cb9872ef92992a377456ac02ed0322b98f335648bd534ee0bbd7b11a1a1d66a2dedf12202245f44885e4492bc881cbde541cba82047a6a807191d3f7490e34e7c7490734a7e7490e34400e920e77208e920c74d30440607eebb7c0915790919e1e11c45d6e54b484907798e0caf1f555e4060e9b0c4aef45cee0229a2df0e08211d42332eece85548242c495abae40f7b1da0dce3859210bd5197fc5b49afa284a9a5cbe789bd8df7791e5d6644af6286a15178145a0991a638451e1368f610ea3249971c03cf33162e49cb0c4b789dcbadf0a3ce68001c85cd7f8be23c97fc61763e0f0dab1d122212878430ce4244fd79266a43566eaef7de7b6f34a93f2fd1d144aff4e71cd2a75ed33e402db0fef8709c8b0f507ffed282f9a93e19588b8864a870601d614e311586aa4595c8cefcb9da6a2dfd69754c8c468196a73f975975fbc19aa5267dea5132329b7aa5feb0aff2b19fdd0bf5e7332e9a4db79d1ad49fd700491b6b63437fdae4528d08999c31f539e79c33b359c6b7d25a53b3bf358e23d7c7f1ea714c1ac7711cedcd274ee49c73ce62e6a876b33c8e49e3388ea3281289a2288a39d3db4bcfb22ccb322b19a97f88b531691c476d1c335124124551b47b33b9c078d483e9a3a8699aa6692564aedb79d33169dc231dc7512412b7484551d43c1d8b21c562b158cce5e584ea683ef860e758e90f3b759bd4df6ac9f2388ee3281289a228de300cc3580c29168bc5622e9790cbe572b964b60c15aea3b1b9e1dfc340d83939393939b655c22863611886a1188aa2288a351c7fce71ce71ce717e28d439feb75229cebb8ee32a8c7bca31ad2ecdc5b638db32d956c9b636abc5b2a4711c479bd150ac44a62a96aab855915651cb2fd6f0757c6a58c31ad6b086fd39c573bc485b8cc6b45816cb77bb66cbf53a5a48e3e0ce5c2ed7d6f56761205d519e57618f1ee7b9f6817b9f6c6c50a01840072d0ca4ab9dadf370e05d75d88dbdf8262b6d1796011bfaa7c4e1870e1a03c6580613338608c9010ed68401c6c760f9d0337594c0889048faa16514c48579635c2004820524204be410c502c91124e40a8e1aea0a51786890c47aede0c425a4e5c20cf9de7b2fc61863cc73341fbf83fed04163c871438324397cb45ce0821a4008514bc052800c19312021d6a0890f3e6654930d2ccc703febd121c80e262d5801e2c90c6c0f213e6eec3861460d361425f1c30537ec0f526ecc00430c164c0e6ac041941facd80e7cf891a5a7079f1d1f54a3861a170b8d19661096196262fcdc7befbdf7628c31c679462541081a3330d2821e7ec4c003126b862936f0b8e10711375cf9b143901c3e3b300c2c1e18631c43e27befbd77870b345ad06af1ec6802648b8c1d33084fbf3172d0c14585c6c7c48c1f0be442101d6efcc000e363e4a036088192a386cfa1860d63f4c8a10926433560101e81c38f257090247248e1b9ac1d3787cc8c9f1c6a5c8b79ce5522068f0f2f21af242f2aaf1714ad8a7e8a74280a52e4a3831c9f49811d4203312580984115a21a4133d0afd6d23abb2552493475f5796fad1feb8ffe913ae534d62be5f5a8d70f12723d10c618638c31c618535dedbd386735cbe8cfecdebca96a9669daae99f6a3aa7b2b6da26adad5e3b85d5d4c9a73d9542f94c3c0ec7a7ad9d5d327a09350a73cc6d5eb0375cab5cca6ea767da0999f99a974ce64497a9e5f25258d45444730222271fe4ca0d6f4993e611d2b12d25db17369f5eca69ad2c399a6696d6f4d6fa592a9b4396d9b5c5e5e605ef649838941a164ba196d6686fe9c99d9323334343534dbe68476e204fd79e2c4aed9764edcdc7870b3b9a7791efde965142080104208bb7a0048d5548afe4cd56ff36da77a1f6c2a5e3d14fa830dc2a69a7d86a0430881fe0ca13e08b954d3817ef9c3249f265057017c68f93d7c9056a80f2329295112273be8a1c3f2e3ca8ccc897ea8a717d18c5e82410739ee6304b507a02a32525e442cece00271d2c4cb490e3ac873aafcfc3939a09f73e0fb647f31678b29429c88781598cb57d533373890e32805abb5f4de6e94b47edd010627431de47470a2c4c9918eeaf2e584099528c0170b83e221284be763c0903abffa14e5419db228ce4ab2807d37ddeab0a18dc53ea65f6e75846f633616633d953a368b2175700d99a2af8371ce1c879f859f45c37a1d6b6905d227e5e993c290a99933a6986e3fb12ddcb11a9b4f67bdce9643e290aad81a0e351c6a38d470186a3d38171793c9643299c2eaca364655b8203d0ee556321287a54beab19429143b75897d8a72191470a029ec94d30c5112250bcce9e420656d39905e85fc2d942c68e5e0e6d3a9169ed461f907350d7fd77b185dc9147f045218bda3c1ddfc9cf425e45ad0b0d3b75a302fa2e8c2c54c1d8b9b1c568fa6b59beef9f3b9edff1d0e0ecc52d3396dd627971945cffb74aa99cbf5ee4c55642a25f73a35b3397d1ced38c7f54da9b641e0ab3aef8a55c844dcf4c19dc0c90d31703de02e5f4e8030f15162d4951061831223945c519274b48485244b6e0cb1a4c792224b8a96883c3e34478172d965ccbf6b93f4b42cbbdc19edd5fedeeedbe3f1a1b99ed6c76b9f5c002bb7c8b8d65d534a299d92e2d20973cbc5fbbdac6f2ffbdecbfd7a53a6b8dee454bb52ceefe2ae792b3ef538b4eb4c67326b2570857efbf656501cda4be00aef55aeb74ad31c0fff092bacfafdaacca56c76fbe94e5fd87a7768f9495e4a7abc94f4f81d4b8ae8fe9fd2253c6064ecfdb3654025cd87e916e14a4a5cb494971315983d61ba7c3139316922a60725838b8b8bcbcbcb926eff30532a42c33d999dc882137b7aa212359b868686a6a64664c203940fdfd3eb5e987c414cf966be6f734183e089e9f215440c45103700c1c44208590e00d0ec48b908e1cc3c0940678400f6f45e4d703000104acf01d11d8c2545e840b1040b8d0f7c586203193abc1f1b1ca8c7031f2ddc14997142c90d1b29356aa8f8d03089818e991793263a2bf6dc90116508adb5760857bb7c893cfc60e35404e649f842c40d1716d8e044255a30894cf4947c62d87c80613379414fc1a1051183210be2062e646b9bb0d0adb5d65a6b6d6d92a3a7c98e1dd65a6b9b08e5a84d9ecc409b18f1f34d70e0c2741283f5721276dbe5cb498d8ac2cad04b090f253e5dbe94f0ec2c576eac6b548252c8a1c98c1c84e42048842071a12621725138dde28143c20d7148b4f02459a920230991ace69cff487a20691d6121498231ce19c91112279aa6698d040b12a40d8996ec08fd5c76b9cd2f7f8ab3d6ee40075b7a0a94cf3eb707c45410fadc543458a678ded580c3fdf9e4265057a778733c5abeee9ae026dfdd969955a7054602ee1aa9022640247a14fbb9031a20b904f0d34f076d6c50b03a680019badbc109be6464cef97fe4071bd51ab44a1e1971c0d97a6f09268871430538ac6000a48565d2ad901c38643d8204571f86239c11229cedf275e475c488c68f7ae40437e45c5bb90d1a3184b01023079a86e536880dfaa5712f10478274adcbd7912023b3da3a6582e09e705c91dc22c0d3789602d72fc75cd72b6f917927ace0c5985b5a58aeefbdd87a95724b67e81a07c84eb94a76fabda8212173cc2790bf14c5a1385225a7aa6a5d63f9a7d25da5fbcf295afe9c93561b380650141e4984a21c4531d2344deb284851b4644ae86e89102ead43142c322629953d3a58bb2d5a1a71fda524f5dde54b4a953e3937a594a2c5c886a45c6e31724a41921224e5470a0c4631782f231a7af7a916747744081dc42de5cc9c516c2431e7fc17224988a7465c7bafcdc00d49d02043060e347a58227ca80c352e77efbd97c64b8817a4d430c2763e28a594d2f9fcff7f1a517f059d8f6dd57fc5e2f109572c1e39246489ac685d76c80a59d76b1dfef800b5c21f9fb088e88a779cd6da6baf27e9a612054874c9696cee90a34b4ec3798320bae42f3ed15c42b2b854c708bae4b3459398d2251f41b2d0fcebc576b4f64a47fa948dd96d27fcf1b940b715fef884b2ea3bb68280845cad20a016de7befbdf7deb07dc54c945dd82dba449f7a7153611ebcbae3cd7293eea87fb78280845cad20a0d6d65a5badf9ec1ac364cfb9a501695710d0a7b2bdf7de7befade5e4b34f0ce372abb5eaaa350cdb18a93fdf70ac848f3e8567105016caae5610506b6b9e95ac7aeb3c6e3cfd796965ca5972521e55a89e712db3ecb38c6659cd8a32a24f7db67747939878cbd08b4f19a83f8f697dea51a82850fbd17ce4a79db490e23c9f5a4c8b69312da6c5b49816d3422da4de8ccdc48ce3ba8e732dd442aed3c219cb34ead13c5177c7711ce76733311333311333311361631ef338bac6a1110683c160d39bd4ab99331de716cf631ea90acc715d97c7549fd766ab5b9c66bc33aacd166ea56cc6a942f58c31c618d38a732bb7b4dccab955732bb7b48bb9dce1a95d7b478a43b9fc811cfe66e66026e63967ae180683c16023f54ef424b0373d295518d339a776614c2b866118868d1aae1cd46eaef56e6ca956ebe560d6365f5bb586afbd614cc76ced922888eda085f30699f844a38edca249fcccd69aecda9337deec7953a13aada58dee79b50ddbb0d2866d1bb637eca31bd69f835aa6b44437baf3474d5a5831c6bcb46dfc510e66627f4eefe59f766d9abf2d8fd8de4deda75d43687a3fcdb3befa03e9c6b0fed9b76558c0817aaf24a5f9c1ee7eda95c22d4a718be216c52d8a5b1ff830dc1aa98a6f058dae716814c7a27166c92b2e14c5fefc8654c51467d10d27ec86b3ca0d6337bc62639307c55cb1a1d8e457f2c8c92c9d6bb2562bb2c81575ab2a7da250a8be0ac1aa045c4845a84a0d29b8610508478383714548afa2258aae9c98365f4369ea0cf85cbf2761ad39be188234562302aa5717585d3fbf047ee0e6da4a4b20083cd05a966b090c01b7255005af9341c59c606e0914c1c73db839615343834be00a1f7c1ac5e4b316d915a7924a2a471a1b67f5643a7d2ea595577e3ba84a91e893d3984cf1de896ed3dfabe978a67b5da7b194ebff3ec3c183b12677bbf16939df9eff1681be2d8c7ed32d87e19229b4150cd54dabef8238f004d36092c5c629d79b6777509b07d3330ed28c342d9922a6d577e0409ab1d53d68d18c9d72974c314f2b1a54df9eecdaa4d1d08ce004d356600582f2600c4fd06913a35f43290dad33d4767d660b57a94c971f962ec902e58269f95a72fd75ceb9018981a39c150c70c00814303ab2525d276ee73ae7fca752735630c0012310191dc54edcceb5d60d546b31a0812229aec8313101390636508206aae47dea409b0118801b751a9eb89db329dd29052fda74270dd659196d3a3d848d117d725e828df44c25e05e454431ad71f4de26977b73513f14a922a28e0287a78a9228e1756411d2f8a24377409feba9025c05e6131e3a669efca071ea5a4f883c29d2f144054f8e782283274c40e9812203941ca0f88042048a111424a0d8e0c5c24b8617502970264929a59452cea74f6db5376f9c33ade9bdb79d52692b99765c5e5c5e5ee8cf17d4a68291e93655cc9e41ed480f6653cdcec16c3ba71d53b544c5a050f4272a77945b3b6909e440a7dfc3bcc96bb5545b0fa49f562bb5946e3b32fd61d845d6328c31d630ae18638cb38ceb5bd004ea8a40a5a10e15d4d85fa69e4b6e5946bae4758845e63663c2c2d08afea663af34165115a52c14e7b989551a4b2385190113fd732f8d14e7615ae3d89fa3881e25bece45617914d2a3ac3ceaa8ff519f1c4419c168195fc726bd4ecdf23af4dacb3dca9229a666f438ff69459f2afad41b7dea2d4ca6f8d2685abd0e6879697c1d9b94a5a445a6df9a51a3a597094ab4443a2853d0435087757a375d0ba98a9228719ee7cc5d9e395812efe56049bc2f4b04d1bf24be8c306249eccf513ea8f075288c0823c28830228c0823f6e73030443045af732965a1c41e5d3905c1e472525a2aba98721eb849e9ef94a298605da6d6e3d0966b53957b02b3b4e24dcff1cc65e02fbbd84df5d29de04ee1c07a82713122373f93592b3daf51bde8b588d6a6ff40ebea74dbb97da883b473c25dfeb2d3b4c0f16dd2ff8d52bad55aeb66adb576bbf7de7bb74e0aa7719c85b12cd2707c11b746d86c49582785bbfcf5c633aeda36cf7dd788d0331136cfb86abba44c91d56cbef19a8cbfe8dbd781fdeddc681f98c5faf3ecdbb9e9f9fb40cb65afcf55e43db3269a1e71d3d46a551775535917370cbba3eea83b0241938b1dbb9229527059bf9d9bfe1ffe31e7dbcff6481d0e5a1b7d72dbfae69c1304b0beb98be2bcf8a9b74e64ca164999ba77034db0ae5e6b0784ab9a1713eab9adf83a9c5b273d32957a3e81981e89f3dc5484c4b9dce57297cdc5083c32322e2326a438763ead528633e065b8d4bc1aca3bfea245e6d5d0af460219d7380919d738a51f091aef3e1566b8c655e878c65fb4b045fdf98c17d3797373d145e1eced94cb7c27cc2d31acc963263fc178939bb8a98a6451f973538caad078894ba9f3814c3d7fe152ea700e4cc1fad3cfc6fcf4e731270fe603637a6877f9405311fd39e781a62bfdb909d460a631a605025837c1baac8bda96c4c1983fff00731bd431d71f983107533694389867f607e3ee051ce5f8939fa2944b19e3da56f817b8ea579d53565a6b9e432f21e6935bd6d65f93a9e793bbf4b7415d72db92d686b4a1ed910898fc432105ced6e8929b42aaa2f4d958b8b9e656d3b490e656ce5cb6d01acfb25e3fcacac29f3e0900d29e5ea34f6e73b8c4269f934fc9a7fca8fe7dd3adc4799eb316d65a739444538814d3929484050b1212680a4d3ea61ffc3d6cd29770857f716b66a2250ad0d4624916b8b390693f3604b35e250bca2ba78995a2d49f52914c5179c703f7a0f6d36bd8aba97584fe9ca773d25a6b2af59b6a4a15ddd19d7ef65bfd9c1c5aaa5c65bfef4cc0817bf6175d6e2ebe6f2acbed2682055ff4ffea57650aa93afdab5eec96b3dd542e22cc6f7e53a6b89b8aab51c9fe5fce5ec3176f2a53de48c8be151467f2fcadc8b62abf6c760b645fce94f34bbfa92a8f40ed804acf2dfdf997658a2953944caa2fdb54bb3fbe5a965ac9f2b7dfb7a56b1368593327b94a76a96d097cc137114e9813902d78646c7ccfad46021bdf9c846df2fdd3252741875d72d5ed95abf476c2dca292f329a5b9d65abbf5acb5b667aeefb6debdf75e8c31c618e39eababba62b171fc3aa26c7b404cc75c6bb904beb817f3fc5d6e4d19dd44c83ebb92a60eecdc74cc776ebafd6eeea4d7dbb9bfa5d7cfb26e5fa1bec8afb5fc07a4ff7ff56efa0854855d499ce7394bbb82f149c3e2384041ca414bbfec41e692295e5bf57fba5ad198150ecd3dd5029b54a8a4ee1606295433030000053317400020140c0a44b2280a922c0c6bfa14000b5a74446056369b09a4b16028c851104651186388310401600830c628a5941a1412c55f2a2938bd2c0095e157cd9d99f5f8cd26d01042117c474ec01be3cf525c0e59a0e698331a918f0fd059d597cf9544210906f2283db0afba2d1b94b2eb21b9649e9167b0edbc79692948479eafc81dde91970fc74b2b3ce0942d4349450f1b85345e6dc733ac8b5b6599f5c41d2462cd57c3599165e603f28aa74dcb55aee180e5036dc1c96114fbbcc9b84f546e5394986ec19ed51ca0cbd86b0e358b886dbb27eb30e6acce3ad4d4ae51a0f9fd0724fcca7801ae26c72f9cefa8920f2e4cd2ee73963dabcd75cf56cea1ab5f8198e66dc68971502ddad42bb46076a19fc942bb1a1f12c4a7122bc266f9d3430b635a69d1715f73f76632baa08e4e0d4d93dbb87d9f0993209694d6be560336f49297d73d93e6d5b49cd73056e96426b92dacc913e02dc841ec465894f7c22ca58ecf5797bf67503793e140a9edb126c79eff7e6f92ee0e7173b4e044ae308c3aad8e5934e3ac695734dadd1ce58b449619b9f8ae130027a49d98e03434142a28439797425bb5c4d6c4e36abd8ce32c40958b4cc67bcb2452b967de46242c173ae5ef9eb43c398662c18c0c9ea1525fec9674c18e0e83ef934584cd405ca3c4fca53821a112ceeebf05a03c0c94d6909fd7a12bd31b68e23203010ff66092a00a512aeb50e786607d024fc2c2b47dafc75782b41d7ada9caff8c95e7321c5ce5bebb496ded157049645bf084e507e38da46854f46330e99a48a01d2694ff4d88803f79f2ed3b6b10d4e3321dbf0aa34d94677335ab61df358675da1db0580378f98579bceb9ec88af5136147e3e1b955360af6c16ebe50ff0004bb09b12ac3a1eeb3e0a944973d46fa83c4740eb1bd00d8a96182ee2a641c1b81096db6b64b8092790dfd27b8ed33254160b516ac5796c102dcc574d84fcc969d8777ca658fa80178e2a7a2b750c51ee32c8cd6887f9a853aea317dbb913b703973c7d2986a89255c863caee202252131cda3a051b6d56e3e87c44ecf9101f6aebb3df22cd0ba6a5703ece480b1a96994bf7a569cda316bafb4ebcb686ebc0de3a70460240eceeb2923559cd16c9149ad67ae71947da030ba569f3a753dad02bac6cfc71953c1e217a0ba484c1ac27324150944979717bce024283fa6869f2ae9aca3810080e6d5ebb92577e0e30f280a1504ee2cf088e1fb155dbcafe9f11dc886a65076def03e9652d96485abee2192e3422b613ee9a08b406044609e4b03b28f430a9c7911f4c5f3e562b32bba177c5f57ceefd29f81d7803b61ea45392ad449007e601434a8b9492c312a0ef1e86403826d2bf314e84722638441ee13c02419191aa680f59ddcd59283d114e7be3e13412f4d46d83f0ab7808e5f4233c435f29f079f6a18e789badbd78491d9daa467a6d02548072e84652b47a0c371c405fbe0968391f3cf6cdf8eb95bc9a1d88ec854a85310a9a3d7b3f6828aa1466b615232e17f0ed046a172e7a23c1343241b1cc8714757db32f40d7639743cf90645571e02ddc511f8e99e0de5d783f68c50d310be65f1b80a10390f497609b0001813f0745903cc495d986add70e2d14ec93de344e2c94e25716435cf2587635b10d79d6bb7a58ebda630950136b08f9832b8feb5dc4231408e0c45a15ba24cb10d7e3d0be23eb0bdc89352ef25b526da6f4aeb61b64e23b41765e7ffe9238e9a63f2548027fb637367c9b451ccfa6277548fb6a5ce4ea6d4ced3db9aa533a003b08ac94cf2824d537289abb0a61154d9e2c6cf59f95fc8326b7bce034185529571cbfd4066a0c680f064c190563eb40f4260596f950cd56eb4ea1c1080ee90c16aff003cb38ae2877284024e038cfa40d3212500315d64aeadf0e33691f33148125686e7a2ec2998a14f197b5b46b0f84ba11040ce5346882eced42fb855aa8ebc520d108ea494e81a5623773413aa62e5ebe8c1309e1572d74cb4a690100af8f7d10f8649860ed28b32abf5fc50d2f2c3fe34de88fa3326e7ad6a6df9a021053f2ac93d3a65278f15b33c4818596a44581c005705ab713252a6c37de8959f958e2a56709dab8b6c3a99fcb97202ae397206673b794099ed028beeda64991f564a20b722ae95f4c84a59b168c8fbe03a4e019541855db14745e52e80a8ff199ad1f41d1a0fc36b0aea5868a7c32fa092411cee327b4869b1c97cd7615d8a194ba2ec533ebc81c3febf7a6a35b0d37fd6697a8a561d5534f8750fbde487f048f0b9bc0410981976df50f190d3a46ab3e7b56edca0b6b7c7b0cf349ac1758e23baa9eff2b0622db63db91b9fabb974853d417ae2b09d1c3e10f06c6e7840649287c708bb23048c3d6f80a6621b1f4e62d1ab4bb0644e3916210ebbd87ccdeca74800dcb798fa584687f178387e0ffc14041cd15b7e7dd6dbe7558aedd104ae0d7d0451cc83a76cd71ad50bbab2395ef3fde28247c3498208a6e0254936acb3a9491ee02ffe9438c8f89f8c990ca0dcc97ea61c4e5988de02ca8bf1c9a0e59d7859566470ba4d13bbc28a246f20a4a654aa63e745a99dba1713fcdeb624ba63c7ea81129f5ffa0cba2658d7017ae0c02197ba5f8d8d06815be568eb084aa004bcbe65aa02bc494376212bf7486558cfbda914a5121ebae06f1e37ab736708f1e96146a4b58e9a0a487b5d30cecd8ce013302abe7a0827a3d4bc4de17849df301993c530ce3e4667ea18d45e99f3a31fe084c7e0368dcf427faca08013329729c92114d2c3da66904f5316a44e28c24c9045dab54dae6c51d12ad721dd4c2303533539d6d28059edb63d62784dde428ca816ef8aa5a8662e4d05477caff7b5a0ef88db919d41bb6096b9413a4a20c26a4a251ea8cd10b812b0d8e1a3c40c150eb7d13ad69d2a3683842268a40609039e110502df2c38cedd3ed41a61d2a6e6a42a4273c005289ea25086bb56a68c19374670d8e0606c4aff22575bd7b0c3a71406c1cc904c83abef2d611f02fc95f9ec88827310281fec1d72012a73504a5331e826079477e58e0cecedfdb7cbf7a0902dcde2e094e3c4f7ff3698d5fa90ce4f081b48775d7c7b2afc4bc7fbf85014ed5f5d2fff6cbfa20b2b264a88acc1c9a78f84051641d3702032057fdaa663e3a8ad926e197093b7c610ad990e01bc7acfedf432b93dd6ab15cb954d63b256782c473b8ff515af5996cc0ae1967f7ac053089f45e775324391b898ae7fabfecf47725d84ba5bc386721ed66a8ad022b04c85696c10d1aaa1742535db4bf817ccb00d9f1189af53a383621559c5c30ce828660d7052e2fc57ac7c0d07d7e0991c621ab8568a43e57f0c9433e04a662aa5db4b8105ededc8b201e1dff31dfa389a33489875ef5140f9882f22a18de94de596454fda7542f05ff2d3ab6bbcc81b101563476b173b83d40dabce10a7f70ff729d436483e09c25e7a8671a2db7d4172ce4edb8121810e18985bc57a2ee19580be004fd84e923a29beb002ac6775c9b2f67e584f83b783090abf4b43996cd1ac590eaabda3d8c0bdd542faef1c6c4de5548199c0af8335c757dcac9eb757c7d00e2c6391926d8a232c73e2090a1108ae89e7e38d30666a0ff16947d72d8dd909979ffe257d69c3eaba551f4f3a85890fbcc296e3113174f091f152693f2791309b4c1ca3d44fb783a8e795791b5b9d3d1f26fbc223ae879d9fa0cb60134588776374248f2d555dc1102271e044bef1aafc3eeaae0d6427ce0214bc1671e741f825f6a58d50d15ac240c451e406760b16add1d406d3fa0f26a5493e2111e568517997b0ad10117af1357fc39baec8b956afe35de750db75811f70058b00d2c427adbdab6149e0e33d85ab1b5ea6e7db01c1faaf081688d2c04381d5a02c0643c09a63f6195f5c2adc57114df12b06802bfa4f9649ac9d04d76a47a0835ce5f03ad8a946a270d14641465a3710eed88f0b87903a4a43fc6899e324927c6ebae1ecbb3a2511e81010cf8c77dcfbedf81bfb1c9ae44a2fd618722694dd62d4db4612c37d06f8dd88cde9a6834de94e57345a643097639d1dd0bad1b5e8ae3eef79f1c9ae3fe3921e8cfcbf7446d10bac50d381e3fdf7351d93ff1e938828eada66d69758c44100d9036089f1e9c15293453d0bf96d7d8246699e942d1de069ce4e10caf9a9dfdad111206a39409b8b05c7a4c6017d6a58a65e76607daa7315656e59d15e7248c4ec1c9a214c2755c697907ea91448d88bab001388bfeaeabae3ebe559f63a44b166cbdb83d6c05deeed13ab46f290a0be4c644a5d19a5d990a980e5d598a085fc30e674c8770536d4276c1d7dc0d850b14637ef73e09306d43a125a56dd0cd212acf2c6f0ea7aefdedd1b2f3653e3b2d59a36e31b2f82e05b7a06ea766407717f6b877fd88aa5de103d14d36684f72327d3049e1b11a92aa65bb8b77c07ccb4ebdefd3928c5401ea5d252d88fcc566aa5545744981f9863da6671a815172bccab8281c5336b3a77a5d8fe958d4cb5f77ebb8da104e0c013bb49b13ecc876ff2cdae4beaba80090850668a449d334db3ae484a3a700c84b38b4e3bc2929d95e1e2f792e20d946cccb7eec278f6cf69a7f340f2635b80a6545a6ead70467b0651d915e517b5f3463617204550d8637d9b109f01331b50e30185efe75c7b7942b8c605e11583ab59a1b4d85b761433c09f2bf99e254144556f2250cba6628df586941e2a7ba225e45c42c8bf8e5570d49ed9f605331a3fc9658be7cc62e3f8072c29318ad916bd5c49f0e25b3fb25fe6acf7d8ca264ce4402ae05df9bf5060a876f429f5ecd408e3ece7e6bc5aa536b876a49d4a8e3bd08c89f108ed8a27530299f16ad38713d5993d238fd4a319b696e93cbcdc5ccd4670da8d12ae8d6e035bfe6e094c58486b997adfb0b3d82c24a2cc3b6d783f8f65f1e0fa8ba1950bb9f8deb27263701191d157ba7ceee78fca3ac1e4905c0a893c56c7991c055689f1c28d0df6ab9d6aa77b114f93161ef52624f4099035b8cb08a2f939af5f4c6fd90ad1222a330ea4f312fc44595ec9ead1347f9e6327bcd3283f01588a06635279680b67279edf6e597ec43b3265499699b71fad015016d562495fcb8b46b1407ca5c8c5b126552e6e45b40b16db7478ddd2907d4e5be9a9920ba941deb0fc501d552826f43a6e545efec29895d260d6ffc423cb4b08bb4eba758c4308f8c7e36ececb2d7b14ae64ce412722903d4bff0c10fe1f783b7c787edf2b25a8957fec0f2df071ef2d29395918c1fd362bc510d18438c2c8cd9377b8f1710a2e542c386992aecd93aa4ded47db49b70ff9686a164f56f64b5f61b41f6f27f80f3546d0cd47475f79af13c0ed086f86800a94554ef78df34a991a0d6d44a2d86973435ba5d7f8e8d070038afeeee9fb9af2e58ac252ae1cee7a8134d429001a31e5c386d12d0777a50ec440d6a63d7092755529b1c258a15d82ec7c60634af93def0288051febe4732dbcf9f79f10601f405d83afcf714bbdcc9ae907bbf1840997ed9bf9d3153d30257937d713848d10e0f5bfdbd9c3e1edbcb0aca3dd03e6b214c1bb52b372c6da66c872048d4aba03dc97b4322d5b41f47607197f58a8ac3ce1017624356ffe207ae2b72539e4ee6d57db883b2a597165aaa889fb7106da431043d0f7af807a6a81b8e8f2e6b69a9e2d29196f63e4b8af2b116681ad23bc8bf1199a6d108199a6d0726d96a6b21c00df7e4e988e6886ffd8f3429320f41dc52349a15a12eeac8e88ca55597c2e4607b0df35352c21413778767ff79fd9b70b244922d8de425bf1e6637208a8a6a3cfc03c50b7f3bc1e9ce511ab672c8dab98fcb90c3ca58b506e80248d323755eeec399d92187bbfc6840e08b2883ba3ff6c1622cc06ba811f80deb91712c29dcfcce64320b474e80feb9592254180d0a61a3fdd0b809086f4c921649063e62c83254030e0c6d32706bbab866ac953720e186b467b493b6bfeb5446cc27885d90f1a20349453ec9b23561c005f4f7714f340fa941485866ba53c209c6d24ce066abbe5b3c2300c5418bb69ee9b0c3e947712a42e3dead520bb94b414ea6429ad772468837278fe066846cd0a873cedab831c44a905a58cde7170a59a496429bd12ee5493d8f3be3b653c4342df06a58e0e0ec9f2d8240de6e85ca9b8ae799baaf93da9db3a23c0728ba15f51698a6a8e3d4f61ab3ab56f0248fe6eeebf6f3aeafc4e1a466e7f38f7a9b5aaf7cd1fa0e56b81c8004df9c10d709b7092784fc1f24be471cf618f156cfd99466f34d43b163b8c86099576f29c149f039e1983ba906eb4137980f6da615e570f8aad3b53109ba088c0aa86d80a93726c3b97cd2c5dd3bf8c89e70bc8e68f3a5fb7cbac986e3d8ab8500c89f5acba0b31efb135f51a53c4f78d8b8fb6b520d73bdacb07105eac9b0116a8413e495237fba71b22522fc85d78d8dd10fdc4eb34def5dd8fbce97ee57e739ea98b09b852cc832511eaace00719c25dc2d2f6e384e3e186189917141df6057994dfdddf7844055084a7a920033b7b6be00e239027d6e1df3808c38ef587609a718967131e5d9715493053989a1ab5c3642604a62668e2f0431a78c26c0694faddd9a51132479cd2ccc7778c238151f9eba06ec43890e26f8ce798b0db40582fbb84a1734265789b0b10d946e85270dac769ead36e0d454fbdc6b2e427f4d627320cf8a6696d1f943e8faab0b35ce996316b85ad86b0ef7c57a3019a079978525455f051a5d0c2800d8095de6e746b31ab7f6d6ca4b95b43da61b8eb05f936a3241d2a87244c4531ca062196bd03b4da10521fbb3908e0b4201ce07bc664597540de1e700c89698c9791c41ce99cea053c1139ef8154ce4a392810c32dfbd53d7659bc031e644e445d28a5baee3be0cf3446ceabae5a41896087a9aab81721b73c0363b42ba1c72bc56d74f304f93b95543e6f2974e25785737f9a640a945c3d3a5534bf9feb11b3cdd98fa83a76a3987e8a9d0b9bfffa4bf056cef686db3af7bd4af7c187b1e44f1645b09be899021ab6000f98595521ab0798232c020ddfa9e6e32e4d4070766e1c0a08554f7a0ac7c42d93f62cbf18325f1387773bafd1c08d513c02a24d5017470fc08e178da60392ba68ce8266019ed74fbc464ec9c6e69331c4cf1cdd99be079804bd3f39af7fd2841874b655c1be20df1cfd4d533dac26f16dd0bbcf5a36cbc6d7ca05924add2a280f6489ee92dc9158d49e895f4026dc6665ec688b78358109c67e40621ca234159200daf76e79a5d3643c69830cddeb3c6fb36e8612be4c3af4643d6bddc4c5533c52afcfda514c7ad562e44312030e06b80655058b1fd5cd240470f7dd0e42617f16fa4b8b694887d89eb9c449220b4bf6e3ef38229f498d77fda51e08233d55f1a6c9f82ea881c93826f8f69e07887c379fc0d1be7e9a173c3add6625169e37c2646b1aa49af1a39f9b27c8eaea69b7ca95d8f346c79be595a71746b1fdf6c4931bd7e901bde243c9bab1e9c3a4852daabe77988b8d28db83e9de5377c3e470b06daafa15c2b001caebcc2bad6ed552cafd84868ff6f910ddb43886a88d05a4578f8764cee21a21e084042cf9c29acb22f90a9394e79be7acae787b0877bafd228ad2cd8eaa54a684c2a4c1bd7da0b07207506e2a604fb1307d4eaded6f815eec1cba8ea84e2cf808ad1a12f9d7fa309715e09f1083d45c7f25897bf75ca4262f4ce47158ef2848cf1d3f541fabdb663ebe513e5d09a9158ae64119b8d334fae14608a3c918546766b7ae833b2a5827cbf4ac4367585cb94f491fbe9521044be011c0130f383b75ca285bcdf271f61ba03e5302d70a421d45871d47b8e530c4d534e8259a10e5742977928403c6b22da0c2874969f0a003cd1ec3cd0138e3b069ca21e194061b018f80583c0f011f1ff42e1df2382b8f3cbebdf748be80f0d752e92a9cd2eeb661da8f66ea3dc0a5b85266e8cf663846955f8ec59335ff6f1bdb92d640050d5088e4e4df3e18dd832ecaaaf7607f6fa77e6137e724df2d2f7df4b1916faa17584ec086c5692fff46ed3da88158edef84cfc8e3351a206479a4830cc07526749522bf307aebadfe0fbebc7981d0cce57b172fde596032af72a1864e02dbe2a8ca4a009f56d9f61de129bdbd3178daaccaee86cfcd2262589667be4c95e552abd5af8505a26b0cea2d6494781269353b592e13184f1f0de59222e4b3bbb17066e14da84002584aa33d57ea3ee86545a06c4727ef1e66b6521187caa5fcdafd524a5ae74f120c9a9dd8c89bf307fa46c2304a2a4c574b807705649cfa6894adb120ba1345a2faa77ea90fec54256d007636890dde4bb5a3c1eb18fa41f01931f4b30da5d52b011938b0832e96c9b7c8212c784cc9450c257f524e452120953758fd076e1d60e58b526e64dbb62b461a73eb6dd722f26747fa7f081b0436c0224d14861660a7633ea0a36a92e3fc1e79b37c5996fb180b6ddedb14a2c6f9b7b0b9935c407a44d5f22eb4b172c7fe5334c5ed67f297191f3f2781cac2f29d7252927b255e822d0aa48d3fb7ff7d13d4ccb822847dda1722a6c930c71a7d82a25c34761cca6fc15c038296db1c19e5293442e5a412617639cc365b547c3a9c63e4365e8238963b4797523a4ae0d158492eaa581750e0019365eca104a9814b9a188d1d77ff302f68520847daaa2d89c3c9fb980ee133e3329c059a5c7491bb563691ea5160637ca43227b2679e926f476a20e31d0924659d23c42456811bff796c48f9c4cfcde098a1fb1a2e9f634f6b5097e461a1c2405d4a0699bdd1192e00d1d24c5e7727dbaed724504124f8749f2867da4ea3c382c5863d8187394717b75aeba92149d48dcc94b46fd2c5579c969810b16b73e75524062b05ab8a9ae09821ba926023a6408a154491f68cc2a4ac39902cad9bf6a9f7dabe0b7cb2101b7d06ed2e94430b67706575469fd851a6ce57726388f53361f2785beffb70c554e0fa1ff42a948c519fda712a595d05320a384594d641264ec24b18fcf4672d38e55450a3f291e4d9575235cdf3720dc36a92c40555294cac2b588f2f39e0cf1d5830816fb394ebc295949ee283722a9e6d9dd3f5d933870d09f4231d3f539fcfd170514fe179f2308805d0ce3714b624f77dc5ec9688d612b035d87b1dfebee01dbd21bebd54c27b61d37a0e8235beffcc7bdad6e007603e4dcda1c2906ee30f80377240e98cabea6244ba20a887c3f374267bb7bcb58d668bedc918186755daf044d71b407fb4a008e76a5889d30b37c9858eb5a50cddfebc3b9704eae05613bc8f3289f352759ea018453d7bbdf2aaad276c9a446dcb66916b31952923ccdcacde350120ffa9dd7236a4439b4ccd4b11a52cb0423f431a3806c3b73d0d218b1554573d962c78292f84f6a29359a2eef909245c686b98ac5d5b4099a8bbab91cc0ef2fda6e3710547c9740f7ec9a42272896be889b66a3a8079e5fe9412bff48c4961636568dd7cf3d5639f4d8aa398a13d184ba56f6038ce59802a7dc808cac81a17d1732e9604cb245d75930ab1e6698548a65ccc66bf4a6be2f7867c0886754a898030de1a084c84951a5a9031f10d75f805e4f1260bc32319e9a56d0d404493968697c30f885e60f1714cd541f8c1ae0e4543c8062f90244b63c0d9f8cb1f858386e8ef65ffc16e28db6d6a8ef193633c50c79a42301c0346291ac6a0ffc752de26b71d013746c26e980931905d0341a4474aa11ba756326cb21a511da596efed9009a2cabe40628a95d16637a880cd34e39838190e396d3499c90fafae77d3cea0f0d69ccb072ee16ce172b2f8794ac09a04f2916ed4fa9312bcc0cd12627267a833b3610322b87ae32772bedb5948aad5045b5c7cd5ca24ab0520fcd2e4c12bd930bbde93795472d90ea1db9abcb37687efa878389b423d80783aa6d698747b3bd2031315db2b9e90552daab96e980f1a6d38a12dd6800268d4e18cb09509bc2ba63e154c080498255a01b13eff9a73d7a10dc27da21f9120f0b2603b0df1ed32d863af946f0e8028886e5410e50801321fca675fe0adbd3a260bf6baa588280291a75a1b100cda5d4f693f7b55843563c2293ece207200aca42b3fb5689b60334cc5c666fb8ab28e0cbd21dd41da4b9a3b2cf3ec9456b1fe653542590f20eee85bf72a3c5f2670b5075ee9fc628abff8ea1c969dba45964a06459b888a80d7bdc339d64dce1f7b3b32f98719f9155a9207e9664c4d8d0bf883bf6979cb374fe3d724bd6897803cd6d715749e900e2b4db15bf7bc3110fb1b16cc0b69795d248cdf3f325af315193cecaf5e7751d6a35c7e1f6e5aeff5e4f77be695497e9dea9ce1971d5b0a82c406b2231037f89f943c716da33ac2a57c09c3b520ff685a29de105c8cadab1a1dbef597483c86896fe236278207392ceecf723c0095e992910a2e83b9795411dc70c31bbb7f7c62e989fee431faa98fc3c78e1898b7550ca9a47812351fb3ca10af49193367094f82ffe01703428ba49296c2d2e83489b347631af4b7fabbbe5920fc5f0234976c27876f29615b8354892fe1114e995b3719699423ed1bcf7002e9abcd6ce59643c3b81c297bd511ddd4ce967785f9878e80c83d96a0b46c223275bf26c79e6ae62b62d855081078806c535c588ee5f9f8634ab2f5e748b64bfe4d720ec27732221ab7f17128947f1c0336df5bc777618b3b3ae63d467e2d9ac9d3d031f4a1bb556cddb8690dfa7898828f96a26bdb5f3d9ceffc3a132670348c00f571234b8b0739c536a0b7096f3afac51020d761bc12f1bc466825d6576342d52402ef875171a9f4e131a5f9eff28bf61d0a47d98db25dd8600f190ff79feee048905867b96c088eacd2c04c7d5a3798ffe0fa5147fd8b5a7a204738b4e37724dcebd2d872859d495e8c60742aa2dd14562f4ec3a0f952755e6ffaaaab49fc5faff97b15ae32b5b5bf87dd6b2fceb57a53eebaacf3f836addafccda6a402c66e11da0a19835976bc2b3ffdc176bec6975949436739425b6ac25ef8be147228af580904df4d8d518164b124671e48658d5a6263eb525d9adf75c6110efb34c46f2057010be22b823f622ecff11fefd867830320a3d27e31cc50f49d0f0a79edfe2ca3811993afa7d51b8477caa130abe0a22e91a385728260eb7b44d57905af7e2016fe10162cbf38f3765325e8f5f9dbcf91172c5009cc224d819f0db51c56ae829bbff6061ee1fc4c15dba5e6e2f891bbb01c5a266805ee804884d2daed44a39e99dddbab4ee2c2312ba7f4edeb6bb6f8e77dcee62f41994809ada27ba92d6fdec5e5f1fb51cdafaad5b6bbf161410c0a1acaf788c30580ff11260c76c5e98c8893170f04412429095b3c94c37e253da0d3601610519040fbb60ad700bbd34d6fbbcad06da37cc62a245f587cf1af193113d602549a6e232c8104218d71047ec9b6924dc5f7d23b03c46342cc5f3ac7e33b18cabe2a0d886cf180b63779c9eae63efb408e8d8071b877436a41e44f5095fe92175b9703ff9e45039a22c606f819f2eea38888b97da007eea9cbdc158595588f61e92d7e61b8722b5dfa6676240b27c8ed221d57c101c913d280925914583e9e0ad4d7881f62ff371c324a4c21a5f5828d4d1311b933a550ece0872bc3ad6e92f15f6b2a363e63628c32e637af1d7e81d5d8f0b86c9a71130c14f999c7c5fe34fcc7eafa3c08dc25ec478eb4872d106dbcd9690191561ad6c4fa6ccf8d518bdbcb0483a189a465562528657cb474fdf4ab952720c7924b49d1ecd7a7d0a123dac9885ce041a4f668bd23e3bc9b8a8e8d0085e48bea9b203826cfb0a81ba94788e21885768cc99ead59f71d5bc01e4821822a51ff75d367161af9abbc12f048353fafb86b6fafc089fc2d15c9c40941841ff4eb583e5768d018ee6fd9b1caa3dd9d06b84140a534bb78276b0dc819c60b16215987fcf6b03c7773033028741ad8f0de81491341b8e4896153ba01b4b711504ab3f5c8c15d2777e40f11f9409ad2ce25995517ec2f2b3c634953670f06a84374f9d5116dfb2b042dfcfb23dc00afa5d7789687bcad36cc16b7cec0d2bff493430ab5fb0393124ef91f863e00764c92ee4d6ee61df091468e72e4d8cbb211711481d5f96e1ff5d781cf235edd799a857824789ae5183a137fc9ab1bfe63d2e611b9f7d4d14e2f813c5fe7fde0a160d5613ff8782cbd864fc19b9045fde258390f652e620f93428b188e1d18aa8220457e965d18ba4689e79c3c45f3cc59bf5c47ef40c0f2b75f84ee1b181a96258697ac68d3d7dd40434f40eb705c49a56b5e9d356dbe3cffaf5c4163b15fd1f58964a5c4fc85a3d8850fa1c7dd430f96d0cc9c7217a72a23188b8fb7b233ed10d76aebdd79cf8805355a487ea19ee95b85079905a8e957f0185b8d5e438ebd07f014fd7626fb98d0789ae03249c167307df05a74f60f75f59db7b1ce45d8b84983a27f90f736ebf703461532acf63a26905123fde26d35cfca327bf0c325c15e6ca1658a8bf817629f89d0b98950011a7a5c2c5fd650ac131be447657d13ef9e23918b1aad3ed150d6bc7b0fe820531f27a14a8d229ebbe0b78c5aa4f936ce6dec4fadbe3df39ccef112fb94d516a67cfc06d72f48292fd68f6266a4a27cffc6558f0eb12bc39b9a54e69b120140c1f7b9e284e3ba7f2ea699c98d1af83cfcd61781d5a3d375190ecefd6552c5f9e7473ef83c06897448e6181d0d44c99c18113f54badba3966672e81b673e2a4bd282041b64ba0eafbc8464aeb981edf150366ab1792e7873632d54151d599d07ed0380e7af2865a7d644a1c219709535c68985b656e4358583067074a4002e5f3832fd4915be036a7ac56684c5ba5b0537a2005899faa63bc35376e90a5491d08fb9ca91ec7df981169b4a5ccf668a91d18dce8df3b48ad201d22f6ecc90cba5354fb45a10582589dadfe258d036acf98a5821c3735c85d9b8fea8c02d60366c8b7fe72442a603f6d2d92875bb1519eab859cac6e1670eb6da01e256e7a2e39ffe7043d117a3fcd7b1f60357e720923f467f0cfc1af59dba22d96e5f6703d33e57b299748709f3bf3f049068e4eeca820c4247a14f292feb922c8c99a240c9678b2a93800a01657cf4397453fe3a1a40e08194bd0e63160c3a66649c186f8071a7108da0e22506b93ce0f352141ecec0cfa47b439218e33c487cfb453289d8360f89f90b3007f7594147038737bf857a7df5fbefd763f80a6a45f59281af79377709f71522b29a9d0a70c857d6b0d373f47b4aea82afc153a290d9eabd04dd9adb43eff873ba9e41af8b8b950906f99f27c582ef4821b40c29db79856ac770ba9dca07c4e51ce95e4463964f8dc241475ccd1e2252e385b4a401cf59be8369c84d270214060c93583a707c17f9121dc3cb9b7e7e0191f3a125752d947fd84f6db22b9fd32c5c219dd5f84d19689b83e6c56f6fabfc98aaf12623b2368c9240fffa3931ad7ecfd8ac04e2d66d9c847dc86846df23bef33bb7654ef1f35ec72be83b81938d4043e30fb0b188b8a3845d52cb78bd5c0586fa34091d08826ea79c32e784be48eb8b9432a8cadc3662a50dfb6cd2664323c66d16e56c8485228d93e4e8506d34d00a4031619c0333b08cad4126b235401a7bc8b8b8b2294fe14c5e65966b1840a2b12b2ed8f022580d83fc81440a09039ed381f88ab5d235ca013abd3d8c08982e1117eae08363ce116e5be04a79e470799b20c0064d22640433807948ec3499ed34a5bd5b23f3d632b6d07215a5bf2f9a9867f1952ebc2748fca7b15b2793f10cf3e0fccc418486809daf2bbea6840813d2e0d32401eb7a6fe6b3f9604d4b348f93f9d9714e208d1d065e9d878a1308092bb16b939076161990dfd3dbdfe78ca260df94874daa085900b206fb3364cd000068641fdb4ed39906bad80cf4ae783a4ffd36d6dd7904bd64a6ff0e456dae9f8c2cf4a69e292857997b61305ccaa39b5357526c994532b010bcc3336c0e31dcd1b712da1f7f83e60060a767e5f6e281dfa5116961cba54bec65a00dbd1636aea0b1fdbf03a0d218ddfabc85354972263413c881ac10902c04b2f3c371930d2d90e82b83d052beafc52fef2d6f9e8bd88bc756964d20bfce9950887686a3bbb7e94f7fc3f05edb5be9568d31ef7a4f31a2186b6b0146f1edfdade402ca32ebe822d9e6f4f3eb5980267fa57068973f02b0d4e452487164987f3d9e578d135b5523431aad5d1c36fcd279d6e1f4fbbcaa95272e313382d011e23d83a4a0c2106da17d1af0d28f16394a843820dda2fcf115455c74a69fbf19e6c41d3099f97fe6326aea6106160ba16633554b9d870934e6e162717ea4d2e1f27d7a4d9c9a5473a42cb9d3f054b6662372414ce1738d69ae7c02b2679cc42a4f6a2acb0e82b4655d0686b3b051194f3b9a8728b5558f779e92b2703308179359f4951145a533997f4148b74144af2e504c45a6a6bb2dc8916230e5a7fa1843a8833cb1a21a6aa444468132c86426db06d84bcb38912f797583e38b23699293b0c722e33fd02cde55e724868bf0d1e781601be5ef64c6e48718e447cb7e2f13fe015643466a3c5c46079910128b08631620e6ddb988032ea1bac34e20033a7df68a20d80423338fe625f807cb4b434f901e2a26bd051a2afcab188641a9e5b0d6617a8c2d1c49554d01506983c3e033ac1b8385bae84d5b80faa71926b1180e57e94ba7df8ade2c2f13d5b8d2b8fa8f2cb4144c59d82b7f14ebfea0a4d1a926eff03c27e204b951193b2375e132a2294948aef79232f86625b5b41c7d74f9f0baf04022158f33d12587697cdaa261832de2e85f84130d33d0b040d909c25c5b7e6abc5a112a4e14b13642a37b129aee3541941c295b0ac91fc04f116e95edea9fb6c0f00300089956fc5a3a08f745d3357b520f40bfc0d9d1b03e2142fa49c60cc28c9affb200d808269ee085aee4608217c812833c06b52c06fb87729f4806596a0480a9fbebe8ca3370d80029246d292f629691ae8173ee3675807d63b76d0b11cb7c4a43e81fcfff6648dc664650e55d8289a72672aa4dccc3983c2eec68815ac8012076f4031bede0336041c307247d0ecc658116900e2dc56dd67f41ddd61fd9d7a6b5c6568a43d52c1b78ada5ead6a8979197fe649f6cdce16c8b3655d8ed376ed236b04085dca9f39740496074e22798941921308fe8962e18f190fec7e655ab869d2cffc63cc79c90d2a42131bd6e617a55e3bca9403e9c6744439e8fedc4e59242afdf99ef018b1ab004de178e008e1c02e9e6b030eafb2e0d4484e6b93df9253d44ad2f424b6f881778db99f03a59cdfb614d6c3b2adf4c1de0a37ae788829ea92ea19e3d9120ca2fabd528502676bc1fc2e2eb3ea34595d32eb1fd41d514e0b3f915e4fad1e41e00442daab145ddc5edc64be77755c8fb9adf248ecc92f54e5f25f999c231571b9294e586dc82bafae68ce7d70ea83199d8d64c4085d1bfe2e6dee3f9d498da0f0b15076ea39eaf21d9db82f47ce0ac4bdad90b591b34223138731c3597c280d53157a0ed580cd9791703ea75141471128bba941c3e2f148e11c31576d5a77aa60f6855d88454b3d0653dd89f47ea47ec27d41f5f48b21d06f4d70ef9bccd207ac45e82a11a90527b6b9ea1eb3b2f599b4a1e3b3e63cf12014fd7759fbc7b64f5d0b84e06c52d49452733768fb8cefe15fe0a0329d067fb0089a0063a3477c2c98923b64db960dab6ff6e2952b8d46b9517c9059e859e4ce4d8d9ae91a0a056044251410a65d4bc0ad0db0e7b351ac910806c0c56c00c72b8d0c4a4a0890dd8167c17e1c7f97195a949f836a9f312e031e6f32857c8134ed660b90730a0f41707e1d9e8580c7f61b47b40bb1aed00b259470118c076553b4a7e9382322f0054ed1bc873e29cde352cca6a24894b437566ef2d5016052e2326e1af71feb860398048d4dc4d8851443adc62445a894dc3d32ecc27911f7ea405707f2d801f470e29ed7043a0aa561310cebcd661597a940372d97d99f1631abbc023903d9204ecbdc931094d2d3bbfa8c5228b724ee82af4c3e4c1060103edb85d2410a9603edda8562ca9e49e5b6ab8ae7814fc58ea91e58b83ea36d04b62cfec44364ea1e82b61198ba74dd74e6687234d3b3caabe3b311999113af1269c44cea98e89282d4307ab08d54fdad99ce03794245d77ba301c7f64c438c7d0ea0588da20e272cf73c418064e1595bf6d0f7914b691c9aceae8646fe53c2e0b9fd2203215997582f4fee130ca67838096cec2f0f7f836cb26270620844e7d2c0bb248688cfd8f1bcdf10da8a0865f3b4610fbb06e0f4f38ccdf7096e36c6c05d0af1dc0c3ecd0e4259fd5822fe0d54b2364c915258257a2952313b61c14895cf09b746cabe52e47711e64f8ada0f19a2f0ac01ef3f6aa5d680cfc65a52d6803fe9d7427e31272f43c324fcfa97a5a76254b1a3d1772d3eb5385e6da432d24e7cbf85e77a859e6853957e140d14e9550af038e1ddbe9e285a40bc00f104b684560132efba8c62730501cf6e285e89cd0f9c54536bf608bc34d4f706ef9e86788c85549b1ed2d4896e2da595ab383f589bb58b58bca7998ac1dab2b10ef29981770689064ba4940d3186aa7cf40eb0c99b8689311a0336e13f7830044bea6931a21d5593a948a60e0dbded1ea980896e1024a08e632884e5f1295467ad21878e4c1bc8ea413c1d2fbee4c89c3020da02af2808af66c7f071e82e2f16ea3c42c9e11c83d194e53ec95a22ad0f8788916b5abb68e6721b55a5bca29de433445e1e9ff431dca8609efa1b4c63dc15328b3aa4949c9be7152bc1b646f25761847496ac1fe541e0c5327baac1d0f54f11d2494f37ac4160728b9895ab519aa5d5b0ddf965ac9546547408c37989b7f802dfd472a75a294c1b21d84c3930e309061626bbcf854d8cbdf8621fe7f221b1a64408a8d1a21b0480834b4d05c98181be5e4d236159a63e14fb60b4d116a452091bd378685a86320505363e3ff0e0c00835a8c57ee705e11e61803f1e6ae82908d0bad9b5d5602d49c2eca4a4ef7ba99acf88f349f86fe2f17ed1853db519cd1ae41b1c864e558c172ee8d80645aa47cef64c67bc08b30410950b40123f89a2dcac02fa4736b84fe2a36ee8a312a25040b60ae6ac2f0c70e336b072e3d7345652db55c0665f14c4121f00cda76130d8c61de3187a2a887635f2e533d0a2260808f5876cf1c2dbfdcdd0db076a5ff5190ed8cab31455485436754f1e46e018363a24aaca62effdf386468387a593cea54be56c4ada08f0b53aedba7f5f2162e9a93bc6c406625bed766ad336911e3665ad2f3f37f6973ce2185d17f3cbaa9f6d5197fd3f4f1c9da8594ad6722beca7092b794318b4dfdda4529f9c3c72f62a200b9dc52edac738f907c19c311ba7f6ee54756030af70741ca28255bca98789f1dc9329820908a646c5f4c9aeb8ca66cb0f4355171950af6441b33acf4b737fa0bbb0881d9b5f3938a13a26dd7d0166136d0054a12c62726570d7559395dd3ad00b13574008b97f2ced6821654ef2c04f7aea9f86460e0a4649b41ea5f2fd378e68e29cbcbd57779a0c46f90dbc78e333675bab1176d286336e26fccd34f9a418781b03a67d16b3931b0834acde04338b77e890cb7398d85803b25231591efe7cc9da98429c56ee45b5213fee00645b37facd7ed64e4ddad4b54d209c9ddb6f8dfe42197427df04d4a4905d54117a57ad2184f9ebf0e50bdf1fe9f4769c04d88e9582a77863cf1ea296cc0563798d8ca3204a5769e5decb5c9580fd1b11964e5fa78cccb80ca2947f520a68408464c31783a2e6249e0b4e09d85d713f6556d8932885f3f5ef96eb236a9a47d2e5e5d5b38d290db72b3fa06765d0c26ba0a08c83644b8cb340f430413888d94a87bdd2530ca0c4d10a07ba3ffb77c73f51caa67e1f0cab1ce07cfc985070a609189c0965a5e91af5800a16d723f53120a23c6bdc20dfb30ca608608178b3adfaa50817acfbe857a78d3c1a06541006d1e9745e6c2885bd24df3d2a8f47211c47727373fd3d609a46b22613dfc3e1007565443d56bed12b2b97d43e8d04b655bb18e14954a8025d44de5f01a73ae8b6a8d348901a18034a5e216253d0341280f5c7c71cab8e2d3bda19fc7c3196bf729751defab46a417c4c52ce1f63021f5f66d11f27b23bea2a9658f44e901f57e2ec14bd5d2aa16cfb75f2751d0b8493a81a17835b2170ae7f6395169084c441a6881682139f1924ea7a49315c80c159d2c38c2b3f7899d575a8cc07ef7e40e2a40dc84c43f63bca895c204b9821d96921f0dd5daa7a8edf197f3530e625d22be5af8251e779106d60694d3a70ec9393305bc9038d780f84ff58475d5b47f418303327647f8147157e806ab30f3cb869059b581340ca1bf4b07e9699d7dc50551b726926f016d8021540602b518c71f0254f640d741e3d47c2f7814e529d4f8024d57357b47b829dde7996040b18141ada484a3ff1de730b58323f7583ddff29956469ecb8002a239abf8edf617f524acf5551c4da6bb1e99baae4fbad1778a3f42f9d27e1ac3d96818e92eb54afe5589c50ca5cd0027bb52550b2a6b3622a9a252c30f00890292fede0f415f86ce8fc5cb265ebec99fbf0892007a21ae767233a427537bde892e33bb39479d531fa6c9ebf97ba98759f31cf5277eed09461eb083dff2c815fb4c141b82a7a9fc65119462c33df79e5b38892a6211fe0e3e46cfb9f79e446c0726634c810d08c666c210f6f28fac442cd3f2f7d60634a56319ba5565fdcaf5910c8da814ed046996ccbd414a0fbc86afa20f16b2700a2489c375202c8b367799ece97e0067c40852972aa130612ef35620e95e052c7ce9fcd69914e0ca1fef396e80592e5261e6928e73d71d320c90c98a2e951df3faf85484ce1400affdb1b821de51d7f0045487fccdfbe184faee93f01ab92219b612ad37a7f4f329362c6bc200dcb1d7ef522aa60d97c6b08cbefa405803f3c5d95da6892e9088801217393ca7d164eb1975bd610c122862995c25d7799f95f9d9e15e68dca8560023937b53322046aaebca33a68a11d8a781dfa26575ff00fa292e993c2f02b10eda362936cf892a6c1040402caeb0d39e55d2cf8da177b4cfa361cdd34e6b69036ff0c854777a533fe6699a26cd24ec075bdf8399c78b413a0eca2285ec639d8b962af426d823564fef19d647096ee66f237b334e7b80bc6d586b221dd3bcbb9ea322402a57b9cc6e51970202804846d1e6dab9ec291c9ad0ec93776d7aaf57ff2cb67758c836314b4eb6f6359294118acde546d112a5a21137843058771eda4d5b15d80f245622a87a18834037687eb1c565f94484fdea950eb5aacff3d92ebba427dec743c7b8ba8fdd695806eb605bda64b1d6b6d3e1922c9c254b427e74706a0fc323c1006c924de245e6992e2fd583bb4fed0d7b749a64c9eae6a31a09f172d08774905e0a278f109aeee0660a9be1be72ff5fd008f78ad8570473e848a656e1835b73bf87c8ef6e80950a8df3fc8a19d0bf51aeac655f3ff2f3a8087820c31fdfa4fd39ee0ff77c82d1b76cb85cfd01fcb6720724884199782ea3422b170de24318c5c41964613a03b4ece230492b86915b35ef31326b8f422814fe628f053688192770c08b3b10170302ed5f8aeb8b7903655420b42635e114e0f146a5d1dfa8719ec41ccc01e824cd88393851ddcbb48481b28c53b03c82d88e365dc839d2af422c4265837c4fcdb3ee48b30df29455af82bef64fc0dd0135c3c6803a93bbdbd251faec6dfe45afbff9c67470483c307139803230081072fa6202392883dad50768cb05b77adfecc499ef6c51f83bfa691c9dd184b20b4eefab5dc45be5dec35af4e1414d2101db8e5e089cd711a917400393fd57f4bc299d6a01d538608050907c2803b1d5785b5290b1079af0546d0c53758aa66ffd4e17cfa65d00e17b3097c970ec64e9f47a5d423c569f82b30fe3b033903551e60b0ddaa10013dc3b7a8a0cf5a6d3729125209d35685c396c6ff57c5f5e04d10edc90c598526226872d850f371fa75b9109f0675f468a72a3643820dd60ed255445c6ebe1a91fe3a6e4d75b25de9f54b549ff41d95c538bc44c5795270e0ea8e8927e0323b21d25e36981296789ac8164d5e852a1a50c7194904dde49580a54f895d64decbccf8c8c8407db7586c3225ab3343ade553b261bf5184121b5521007f279ff5879978ba519bb70a493eb5b66409311108a230a3176533c37d4a8aca86db855b88f7f9dacd0a8bde5e9ec79bc347a52e284975d3773a3634d55242b27dec7972440eb73c9aa0c885f20938124c789a18845d7e21fcd4b985f493ed4488234245f56a9069c79f7f21d5825fa55045479d8d3be5c996a8166671e6e2284ac4e63272211e9998ba44db91dce5060b1d18e8fa5a6c3e3ab6e8b96fd975d318936a683fbd8ac6409c4ce693752ae453c8eb883afa91406ab8c76f74cc95cee2c447033c4e563ea24c93a11f3803b7ee06acfaf46596c68e826772a80a765360ca153c5a1a71603969ad8949fa51a0ef8040beb923a553bb7332bdfe8c644efd9193e1f56724636a9f150af3a60f96c787c656ac9bfc98928d157cacadc68aaa7fdb2b6b96fae745de62a16766aabd8f2eb4e06c088bcedd5dd16878c89a663f0c4c18a80ad32784df596f3e7f4d1f5006d3479c5c40bfa878fed1d5a792c4ead334762873300a27d39b8f48e6349f24c0b8e8935f37f861e08e6854d6ad1a467b3d36cc58cab5a57d56d9d16779569f64051c3aa435cd84f8f2adb5a36a2a492c6e51f126989000b683120919ef807656706227afd020ab9d08b38eb231e018b8128f489ca98443fabb57d7c5620a41f1e6a7d96868828e755b1f5e7572784d15eba586a4320817094ac7c43bfc64a524452faaa526525606ee1cb3b8f3a88a3bed1d6b2e3f0bd57c32f1eae5d793a46e626b0e468f3f2318147f34988717582de73f3044ad58392c420bb2a4175facec730106a0afc6e926d63ca7dccc90b75f7ac2da57b470426b2fc1c1ac3d1e81e3f12e8758f53da11252e5f5db23f1c57ed4dbfdac0523e5d5922bb9508ecd6530698c290272d1cfa8b7ffa922e6f5820c219239e1474e62fcd1f7e759e5cb12c5dfb504da0dd60a42f2db1163d3a398bbb838bc730ac0ee656b3b5e50731b1727cb7d949716b3ee4a8ebb70d7a7eccb87cda1439638e31f7d26199b0c1b37b2b0c7e0983ed674c96bbf13059ec80c77331e7b566387a7e478345d3f742e0f59f553d94212d5d84581b50eb7e36798320baec8f0a5770e52837b2b23cd9959d95833fdda4d0f51dfecc7d3a5bb3096f9aa565ebc671d55b1c656de9b0c2bb9b2f6fb1692e4c68beaf01132700d4d2ff8c7cf4de8ff2891505dd82f36bd85005297fa6c28561f66dd35311cd786feb40a516b3b9678a70752a9859e10fdf96e47209a6edcabb4d60f9438cdadbc9f329d9234b06fffc721d2dbadb6fdfa54083f5edfb9b385fe330f1f8cab0fb1fde6aa5e0a74b2215fe62e3c4af7495a7edf4b83e53c58ecec66bd1acfa5588bb80a42703db5ac1d9781732cfd9c6d4065b5f6bea1fb7437b10d56d26936f9c0b8c0f89557db99eb5a16b78eee3e80e660ca08a5d51902e414036a2c366e5e5094554d1bb132d851ddf2a323270797f19382ca2fbe11dcaf3e85cdc8855e79c82e2d512bb90f27784b12aaa57f8d808e64285c4208210000208e186f27ccdbbfaed7d554f54a05f461a9f866404e13bf8743ed2684480aadeceeeede01e906ab06b80631f37f90f47befbd3785cf1d7f5078498e0fc87134825ad7d1485a5ee583793097d727b5901fff8c8caf7daf7c99b978fdbca9dc7023d07da4723393e583349bc1787d08b58e5be5a6befc78e77df6d2e85bbba994524a29a5945292a47cefab94525a6bada5a91fdfd618a59c73524a69adb5566badb5f6e727aaa2aad562b138193089c2edc3404199b1565a8c060e714baa248947e4ac28bc6e1af9ff62bea42dc895b00426286982139e300485284861891353a04fd8c0d0010fe088e1032038e59061061d217830fd05dcf5e6c1c47f01ab081e4c7c4a5b4f487c894fe41e515922491c406c45257168d322346df1d8020200a470c310477c5882831e4b5088b3c33582832333bc15fddaa8f8d09dde14f44084128e849f17686cf358e48f339bd9035ce4cbcb329acd609f61d9b78813db339967d90314205fe6e76a6da5f6c2a83df65ad5f67d3014b3d90cbc57be6416e75174798f4d3cb1e8321fdb5836339f3e9787e16c66ce0bcbd995619f310c5fd9032e1835ecef83998fed8b1fc4f1624dfbf760e66b1b726c6f951c340d56e1aa3f645c003eb1a90dfeb976b4b8e42122873f9e214e3c43a438fed986b0db4f8e86014515af56ada2ed284551c127388c8f6f3f0f46c237fa792ce66bd5a5555cfe90bb5318788c36bcd727ad60fffedd34bc9356a0b19bdb18e3a3b4ea145a54a46021ad702a28525046272622bd691996f1652bb5381f383111e94dcbb08caf6b2b9d374d10b10411483811447824220744e8501b7ee50b54c87a0fc20d2184104208218410420821841042082184104208218410c297306a9d86c75d1321018218526180ca9adddc52c5599ef43275f227dfe50c5c4443c2c0557c8f4bfe7b5492e27b4cb244f7d22605a2f83e397c71ef7e79d79262abd85cf7cf3da9c3a1838a4fd12259ac964ec5b3749573dc135ac59ffcbf8abf2a5ec566e9a86449191597392cdde42ddd8e57802e53292457d17129bac723ffee3d98130ef296ce05a777414aa162bb80b4add5b8141df72c2c9de5a4150e4537fa940e859fbc8aca68d441feb2d149d7e2b11e8b75bd4a275972d4c99eaea294e893c7df8da2aa3bf994ce842582a8944ebfa0946ee32a4ab4fe944ee32a4a34fc2eafd15f3edadd053efa942ee328f064bf3091a2aebf24a8f082502caccba8fa912cc9a2215775422969c8d50a91072023b895c51f0054a40887fc567414363cd257a9415340ae384d5671824661f1fa1715635278d4b64b5ff8cad7752940fb2c061a72ed27c57abb36e472f5d486fc9dd220a3e166dd7b5b2db7257b8ba64a712ae13ec919a2fbc98f8ac73ff9b928b962f17892030a9b8b092cbe5da9f5f65a0f1af20dfb1691765b07793c72c9e80bfea5f5d154cda0e01009bf5f5b0fb910e88e6b0efd8c3e463fd3c7f42fac822dfcc2bb6f395c42b61fdb030a49425744408043971ce8a0e1c2e5f38047c812d8a3020488e4e1c30e4e88a0c7138a38114495c09fb35cd97d661abccdf9726fbc7bcde30ec6fc14a01fcf0f0f2edfe4470797af7d5e0172f93516e03524a7d15ecedd51fe99bcc47ef472f793fb3438fb8c3b6a047cfc4b38fe141e2489a15fee4e83aee80c7c229319fe0660bfbdc43bfbac932b88dec60f0bd9cb4dda5e3efed991b6cf3f49db86813f7f7edc751bbaebf92b03a068cbc01e6f1aecb74d93d2c9aecfbd53fe7ddc0f65d790dce4e7e3dd477b9eec1a9277d8f517b8cc9fbb195d309600dfbe6631260b3dfbfcbd869c5fb31892e79d6dbbcfaf2171e4f2aa21b9942b2d2512245c06be7eca95ac350cbde5eb6df76dbf6c468b7eebe6671db692bdceae7f79d35c8f635c1bb370f12c1f3ee6c19cbacb7efed66940fbcaf3a6912b6431e4e3b7f1c3452fdadd96cdfcb3cb5e77dbd6b56d317e17ba2ebb6ea353c181969f5d34e82aebfa0b79f71738fe873d86fcea15686540c56dfcb888119de8c014e88a6b0072e844079ce01085c367a1c0cd08119d2447e68f478826cc2004921e089184bb40081d7ad01b1011e88a031de02852040942d8084183103508a1128205456450e406458a14f14191248a2c5144a8080a3e206b111b4ef1044572d8c103457ef0ae5f48ab7c1f0011210f4823e420a2045504c707789845761491811ec952e8320211254570c4905f1c815e81d6fcf110012242c407911e22acf76080c083ae4187401341810788a82006321082aa88205170021235b8a03ee0f4c70d22e8fa6f100513e81b84199af0c1c1821976c03a6dfcf1ecc048119dd23d3038e91868cb4387e6d13b681d2ca1ff2b7610039c5b9c91f7ce4d9a89537e44bd39f306618c3331e0befbee1e31ce19488a31a77c21cfcfdb9c72ceb949332a9df167648427a876528925a4f0ab950ffe853046296954493b2fbd2ace33671961a657c5365fecbdf7de7bf9f6c820bc32ac49d8c3c258de309d8934134c04df04beaec71c0e610ca2bf5266f4a2dd4df4f71af34da48928b3f3a44f2ba753e9a67453ba29ddcc2d5a267cf01c91d233cbb2ccce9a65735e9f6d14cb3e13fd36e7f626d4cc5ed4d168ff36936dcb5eb4bd689a6c53b489b2956ea7b6fdf67a9b6fc231e198704c3874e5449452ca8d66f4b3d2cd9665382bf1a0c9ff300dc3b0df4a59966dbfed5eea9b267a6dfbed45a7d369e5742add946e4a37a51b8ad2a265010f12423c480491524a8d52fa1133a54c29130e24007e8c611862186218626f42c51612c3af753419db300cbf86bd8663ce8f450ddb1a9611fbacc3e29b704c38261c130e5dd520b42a4ba7928e928e5e639e4efa947ddcbd86d4648278d6b4ac6d1af4b46d5ac6f0ae316de94645e379774859a59552da9a1bf0ad0965da614d3d2e6b4d3b4c3cacad3c48741c71b2488690fc64d8a85b8141d32b636bc251d1415140f15c425d794b285c42897e77fb57dea7fcd9932f77632fdadb63f4c4f5deb4ecaa31b9c5aebc69d013b778cf29034461e9a67453ba41f107805893674d05cd684a418dddb865a4319a529729c5e5ebe007e08fe707407c090f108e441e2008e101028f18e96a6505061a7485adfdfaf2da261c138edc4ea0643f9fae289018a34383d0f9be56534f36a578c6b756ca126b35eda8f9617cdaf5c859bed21c151b680db5d6c8c2c27c966f1161975fdca7ab949189a6abb8fbadbb6b9549573537a20965da114d3d724783210b84a6fca61dd0043f9b7844964d93594c382b93c442da3df26c4af1aaa7c91ccd94f93407e9e796b8c31570dda48c83304d8533a14c3b6a8ca6942995591e5f142827ff44f3e9df1aaf6ca63e4ddc1dcac0787d8de254346c5a530f4b8b38eb93baec57bafcf06d779feb54ba0eeb50265474c1412836ca1e6d1515d16ee11228678c9e28cb84432b5da16e1fb3999a3d0e9232baabe0a07c02ea79c569e6d315b7990cb86d92fdfcf45152c645bec8d7d8ee5ac585b7709d4ff4afcf3a7a7a30f969aa0c8ce77791393c5cdac0e252eb70f9261c68ca7fbd09254d297e6badd5840309405156200a26fcc086637cf4ac1ff4f004f0c7f30314e7312288c7081d1e235a25141fa956341563f0d7a73c628c8af6a03931c6e4ebd39394117dad5f71776dd7dfced21c32abb9f1c206f8fdd2a95483df52d6d16077aba99fddcff68bdb49e9069ae2579aec317882655f42f1fa10ee5e63c6f81d560869b2c7bed66a6b6edc7b4b3c78b7f15fe035bb77f25edac1ed2da170bf77f7128adbaf11efdfbff7bbbdf2de7be39f3cb64b1776fdeb26618fdf3ecdc1427d583ac518fcf74b3aa0a9c65a2aa1743633faba6946b8de1fe18b47777798ddef5b0df7d6fa1863f19c750fab599d3cebead778af13eca115fd2b9d300d34a7a5061a381582f0fa2e2323c423ccaeec7be9be7d932a13c4ebb6bdc6c40f37cd63086dac0fe1265db56f3570bab5846a4c9efdf65d0688639b7b42d37851a7bb275f6a4c19206e332cd3b253e9069ab0cf5f3ae5afd98b5c42715caf5a6b85fc95ee4895258a139d8c524aa952cd3925a594ca5a6bad125a6b2d9702192451121559ad1654492e055af6cee7dc30c2ee3d494a15961c1e7994913d487104288047950f3c3d28c2059cb4083c12e105d1a06204e7811a1cc9411047747064881c47783802e40810468e6064e8884a020dc99064861c3e49787033043d2a01effae70d64d0f908a9a123363ff820c708124400c2880c466e8c94c0080f233718c931a2c3ff0a233f240146187766e64508796429e703f0b5af7c89a391d6544a3b33f79c14fff578d327c1a79198d327c14cad32f2fa59ced44da59c1fbfa6d6f933726f1a2b08f25b1169fd6b6dced65a1b6f1781624c0a3783ee973f2e87266d4fa37dfe6dafc8a0b59db5cf5ab74119e326a5dc7ece39b7a79452ba7dae98d6d55a6baddb678b691d87d2d85f54d56ab1262aa84bab6d0ea5efdb6cfbfc339bf6976bbbbbc0635c9efff2bcbb0b1c7f0d8fa17dfe196daf6c7eedb727e5afe1db7641db305078566d60a0707cd9ef72d56ffc701a3297e0677c89af72024d83aee27e6f45cfac39edcf98e21307a983be97aa285351f225c78ba9aae8828a32b5f2f05ee68b2ac6d49c8ac38ba930782e3353150c5ecc7b35e7e72fa0e6cccf8931f2c443cda9da0cd7ee929fee9e2f45f064eacfef9be576d3ee4894a148e2cb7c24489658224912254af89c73ca4d81b893ee559e322bd648595590613cd9821c8d5858b47e31278cb9c16d621847c3cd26cc60c47761cc241905e1ff7020d0fdd2972f26a594c5a22854adb5b258d65a7befbd17931da7e3f5fb82d3f1fae5ad168b055110d555e0efbb0a98e4746431ee8b1519f4ccd6d1254a9b53e6020c140e9f6e182840317725f3c7fc38ff95ffe6b7597eaa1363b00c637ede34d3557c4a83d42534e8fa782c2c8f4fe991f848281784960f371705fdf1838ec05141767676767676767676767676767676767676767676767676767676767676767676767676f263188cbed26d08f1b93b7d6d463fa5c99a52fa73effe1ed75259e2f927f3b798ed53feb270f2337b949fbb8ffe6e9d9f7eee6ace8f62fe89e8a7ce33dad4f267af5fe6ad7d9d264f5fd4751b422c683f3749fffcfcb423e9c79e92f486911f7becf38bdee6ec4db68cecf3a6c95e6f1a14bf378a1828f64bd935243f79fa7977943d47bb86e4dbd33fd95d7f814f0ccb33eba93df6799b4f27965f9b79caf958093dbfdf991f633fbb0d21fc7945247a98c1d07ebe48ebd75babacb8c9eb8ebed665269de8356d467bd1ee34c5b58df167bc7b9e39ffa45da7294ebb0aedb494062017bdc5f2671b42dce44d76a741358a3dedb417755a77916b5ddf36d6e5fc2ed4741b42dc0cf49cdaec727f01c60c3496533ccb7287bf85c416d0c9c08f0d21fefe6a49a50c4320024d4919863004baf28154628912499028814409225b7601e6cdfdb0713f704a045122c8b6cddd6bf4ce769675dbcf395f703fe83937257c703fbce07ee07ed09ce038ae061c0d381b70a4c77e467efc31c0196f8450434869fd99b9e9a96e7a8230ca49299dd5d28ed3d1f1e5c5e9e0999bd13d73e926ddbf9e44bf865fdb85bb616c182ab885911088c8a70442f2392710934f4a291094cf5a6badd75a6b2d1033964f0e081d5f0605b55a40e83df75cadf4111c90201617007f3c49c8fd2001eacf8eeee0d3840222cf8df341ce4929a5b5d66aadb596f32128a8d5ba32ce0739a37cbaeb573aa79c516edbcab2aceb5897b54ef4f463d0a416444f7f8668cfd0d89c915e59b5c57e7eb6f7b7b7afc5d85e0f1adb3d7f7c99cde4acc39e065dc997b3eb340797ff42acb986b04ac11927fe12ea2aa12efbf3ef9cd7e9743a9574944ea59b1e392a8c922ff42d4e71bac36987d349076a156be6a44dc82b38a935a5ac69870965c2c14b60211c145d28a7035dd115bd35a478c0fc90a24e37689f412d6b81ee54a592325807eba8b08e0aeba87a64af838360161612632a26827f624c060484e46658e954ba299d7ce4e0558e0fbcca51e5a87ae4f85801d1c12bee868be6a872543d727cac80e8f450a97ae4f85801d129dd9476609d1c15eec951f558f9c03a38880aeb946e4a37d8ee7077ed957d3a9d54a51b1c642536c3ea29100ea18ad636e4cbb14077da934ff245febd3d1c0e9ae67a9a730c2aef88319547ce393927fbb828f82d64851b2fc1435db6c14331663eb58931165ed986b3e1a29c0b680f0e1ade8ff9a14762cc7c494d38261e3c404a78cf43a918d331ed49c2e553221428c6c8c74bf229c63c1e201db4279f4c38f47b56bb9b70f22ad3207336681afb9457fbdb949ad828c6d0b98a2e1508af0fe105218570beb5f6e596516371525476d7ee35efbe0965daf14ca969e98570b6f85cc12d5ee1a4fbe29e0753bf45e470a06bdddc139ab2387cb95bc8f953aaae14acdd6ab5b65a4b1f6a2d03dbb0ab757bacdb2c84d0ee5e3a554abf8482755a9bd5d2cd0f4be96f2cf922b1eff40797f5c4e5db70193198c590ffb05eaa8a2e92f6801f334e88ee374e9d3e7fc0abf358b4704de8b1b82f1f2b02877fe9c0e8f0afaceaf72d67d39ce8f2d447dc7a2ed6e112ded8b4d65a2b9d239b88fdd0aa29b569918989486fd08432e198522da4edb4dd6b4c3826d433e19852269409e746ab915aa49694c93707915abdc584634205e0c70652cb84c362431a623999522425a4252c38482d134e121a7b2e09dd738acf7f2214bcd18a744c3e0749991456d752a5b7f09e8352844e263b00390f663e4b8a766fd129ac2d8595c2faa107335f84ca3ff3dbdd5be2ee2abbeb0745283e492d1d3e9fd381eea4169f39c84381cfc7a4560072082284c811244778bf4141a694094787d5f30314801f5d44ce0f03f2410f8f11d6c4fba55b429d9cd82de305bc812da4cc5e98bced4cee7da5d397743c5ebf84baddab313297d9624fca8874bddbdfbd020366937328c1152d3e61123b38c71f0f12433c438cbb41d3dca7bcafb06450bef21363567a56a290152231e6480d6745593b0e098d3d87c4fcb7e91fa213ec39079e82d9ac08ea5e06a385c3eb3b4a102b4e0f66e2ef29283e5fd4b5f4c89799e5effa878ad683992ba2139fcff1e84e57587cbe109f9f75bde587671c7c7e66b500f19983cfcf38e2d59177641e3df708329fb26ab79f2da14a37fb02d2be94a42a3e21d5e1f357b4f8030026b129505d7363dbbeb49574064b27fd255ddab1416aa2b5fe5289874947a36da26dcbde647b9392d6dae44d762fc998fc9332271fa44e62c1e88186d90b0cccbc071363ed525635022942527af8944035f99cf3f578055ed763d26392a471a6a474c951fc53791453c5a720098931fd059174484148ac18d332fa95a0c7223ecaa7ac628cf6f35380a4e8a4048931d84f20257817e9d4c75fe32256157fed147fed1a177577192039fa8e0284f2fd0df1f95a97b24a01d2a5e8a404892e33622d123a00878a4ff199030204ae677908e02771408719299ef41c50b149f559fe7a160ea4d824b95b60f9eb4975cf60d977c6e327ed9cbbfa2fd3a49471c36ab02d4720bae4bc2510775e3d98f9b8cbf207bad79cd563a193b7b4ff543859b32cfba96d9dcd5e6cd889eb41d7ef3566adbdec6a78d4323d5ba765a0eb45addbe0317b8a92a2e7ba2eb9caee3487ad3934474a97d2753deafa8a127cfe4927aaa7e8322b8e6a63135d6610ef653e0d34d450830d36c4fd96301f561b29332776c2b824747f4314a6ac483a8f457df9f9df93d9fe3d19fdefc964a09a9381e4ebddefd53250b6b1dd55f8961b28083edf767593743670c3e614e3ebb06ed471487415207d3dfe3e81e8329fa4235f2689c5e74a106744f70cc4e7f714a018139104c518a12431a69587eef7b8ef7ef14dadcef7209c5aad5b0224fd42aca1f483e4a352aa975722f61d4575f9c5ec856846fb3a9f94d98c96492de0c77e06de33f2a628f962a245a2afd90bd168341a755c0f3a3e7dd8751b439a9b9cc41a0e3b1af4c44ff49b745da687ebed7577d1d7cdbda23a74f543c7af377fd7fc6658d7c50ece6b5f9835565e4ed0a718a343a3f40eecb755b6f9947540d5755d33b26cd7b8b8ae0b1fdb7dfbb1ad1e4cccbb6754c6115de2e32e9fb28ee8128dd03f190361a1d68e5fba26c61842f8f6efd7705bb30dd7ab4b7990b9242030010660d1a5bf1edd5f0f845c119c0f34ac58844ff00b097abecc7fbfc605be36a9057bf70d1dae3be386dd352ee811343784eeaf47856508084c80010d608003628c44f23ec6c4f82e0850009574285e2daad2a0f0e063619393a383f7abf572befc19f3e5bea1c3fb1bf2e7ee32413330decfc5dbd04a41cb9fda9094792ee26f38548f05fc07845b97926be85a0205a8da5d9b50d0540f4675b5ae5674b9aea1ab5f2d3e4351dddc54fc01e00508e76c41a60f61dca49473ce4969134d3451adb5d65adb44134d34d144134dac80227fa6ca1a0ee9cb9fa192da2138214e8813e28438214e8813e28438214e88133282119828421223aca838fcbcc21981f1b8b1b00ed6c13acf0d49197a922ff051a854aa8f5a4230682638d563215fb36ce0f0fbc60487a3168b434ef560e0a8c5e184f3922b2a20461c50df56a20ba4f3b9ecaf053d18f91c0c3a8713a1c7a27eb5b1bb9f30c1a5d4a93627420f46e2d5a623e7cf83a1b31bb1a2502a073d302115c40571414798502694291565ed4a27593395a5538cb55a4a29a525d4ab32f8b90fc20e66324c38549a703286b515ad66196509892eb4e7081ddf662f4c3810c223b4a9629032711521040757813f9e228ef02596582cc6dc1d8a44f752284324eae24f19a0d722966eaeeb35764d6a446ef2787b9148f4f955447f4df6156d9acd88de7e165115ed4173ea532a0334e3e3fb367b51ba8104887f751abb25db5d8c43a24b28987df5c96f85a6c34c0b85bb84da3aaa7596af80d85abaf76659bef6a3ac2bc736535b3cc26d4ac5bf31268707dd5bb096c7e2713f34acbc058790f3128d837838082f818570121c81781ce2e9e0d9c0250855ad6c8795e096ea3dec2a57125fe2c7d60fdde90ab7a40c0ee2bd00b50ca1a2036c83214e83a8a60dc5a1ebf75cac7052d0d306e3883127ace3ca792ce2ff1c8a314ba80dc531658230defb7adce58fd9ee570e57ce83a12a8eed4e4f38a20b8db5cf219bcdc02778ce39c61ebfe2182bac7308a3ae2897d4b7bfc2179850261c2274f0bdc01cf4fceec207a0428431ca185f655ed7857a5706c523f25b91c33bfe788cc0c133c474e98477a96e39dccad28cd1dc83c63a3de19bbb1a8eebc63f5109fd81c1fc792c5e27082b2544061a0a3f3247a6719f4eb4847a25e415ee89b81242cb94aac10f6a099b202ca10283868fed6025d2f09eb59f03453d16d7ae66b45d6a469e8173469e23bd33525e57a72b9a63e6baee75efbe007c62f532ae4757acf36022ddf434572b242b2274a73ab3a652267c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c2cb43fdf7e8e6f5de40765d47af4d5f36022cd2e909c11dda9100ebf734634942fab19b8003937040e89700894120e8172c22150523804ca0a87402987403904ca21500e8143b821ad163724de11bc0370c480c54051f2256730de7371c92dbcf792bda05aafc936b649302a7f29a57c491fcbb4d31efb8c611886e57f1af6b4ab9ceac4984e5795e70a54b18eea4417989fae709d6ea8f4edf46635ecd4d0000040004315000030140e078442813896c69aa0c91e14800b9194425c4c170aa36112e430083292306388214000400040666466040275e349c7a61204379926993c7d9fc9dbc544776fa0d341cfeb2dbcd9a8562c2e9ab686c00de70f51aa4a3c63622f188668a4044ec6bef81bcebbc5fb1da9bede04c045d6678de7dcfc17773db766f8aebe57336e4b08020c00526b014cfd4d84653ef2c8dc97c87777e14e733290efb1f9d11d11472001efa0612e48042704b99197582ad4d3409e715127ecfbd478e5c2c1dc2747880a3aecd61317a1a4fa494fead146712a2b8286b8e46e6de410cf57d1de184749627e8a64330706f3e32b2106387cbed573ab9b3e60ba3913aebdd43c05fdca96e703c5098137c80d77cb69778bedfe32d1269ffa6442445b33f0b949a8a9ecaf3505d06f7c2234672d06ab8eb15d7f14ae65471e33249cc4e6380d92ff97d39efd8ac4ab29f1bffdbf46246c488eb46aed0801496309c75768f57452fc442bcf72e2e1e6af227c0ef9880c6c11db27b7f9cf84f0a6cd0c69915058c2e6c0e24f1b77b5ff08ffa19b306a1a599dc1e6a30134d948f3bb5f27f88688aaea1f067de4a8485b833ef91f7d72c5e709745281b0c8eff654b6f624e94480fac4314c0df7dc13b75f098f6a7aa70ef8363eb9e5cfa4b6e473e2e0ccddaab8752ad08cb73961e22ed0d406392979e53f41ce8f2bc5934026c71bd215df00056b25fbef4f1321c85ef72e4abe4687dac2c1f84546909da21f66d152b57f009d699187daf335c0589c928e044ca0e37d75f9cdda1acb4e8bf647f9bbe09bbbba2909a1c2832757e580b01c1c6c813ad68221e5052a9a7e499f637134069d1225fcedcf00c06368bf4291d93ec531337d51abfc39b9a660bf9d0f4c4d63d15e2e39bacac38e2c12ca738f9b73c22559da16809ade299f1f3d5fb17a400d81aaacd1ccb1db560e9d3572f04edb818351549eed9221a71500705b9d40814275077c1fb2cb200e06e116c89a6023b7ce0947d9098cd79121776a8e46f4a98042193724f721fbc80a97e5daca689327a3a3c53884015dc4e51474030455ca1976cf2022f532aeb19406f49a0995083af11660ca74ceb7bb8de4ad8654a2bd9000189713dfb60d4a5a910946476371c81e34d72a54a58516595eea06e89135257869243736d8311497d6715c486f6a007468f0f86dae4e8a448df37f0d340b4080c161dcf574d27b869baf8bb092f98377e7359348974feb4fc57510862ca050d8b2ab7b3561bc9a2b37c832b1cb4bc12a6c9561f89dd51d822e68b9086c05646c3be116e99ff49d8a15110cc442d519b2b1659db17690ed37da466077570a7945d05566f20cc4bad23991dd0728712c0b49b85749c58e8241c5dd8bde2642f2dc8eb2589a477436839c917aeb24ebb20578472ab85a9ecebbeff5e218ec4c20d88d59b6d54d4c10ebc5af2318b68703c614e7746f2033c40ddafdfe0752ab0e4f568f72d89c48314a50fa3064ea6a78887369b08b23b189b0d52c537db28a04e384143e422c04e3b2520c204783402b6a63c4fbdd5c27432d2325737e24b89449c03e48fbf21488a6ac4453cbfc3228523882d298bf3dad0797d58ea6c6ce71ba01fbc70c4c11fee11cd35b33f63c78aa4601f155d22d59f838e6a87b92339be2826e91db8d2ef7a29d32bd8441847c131900ff27e25b3dd64a98a7112b9c6f0de2e05d14af2c11559ed11d2818a200a00fd91848848f6bbb2ce0d86ef2119075de433a3a6282a7d4dad795e4000bfd1929748deecfa7adec7d1f6f240ca369329233666560f6a3518a552f46154177897118bb34bfae6a4587dc94cd3b44a7a52ec5f189157c9f7275f697e37f396984b7865e2295b7ad299b1adddb9cbef288c3d8376047e96aef7fa4265f476140624158d07953b70bdd92f00a8282ef8b9a31985c78fbf45ca3d3ca3e528a78a0cffecc40b9833a95759c541084a0beab995d6997752e45abfa28c949ef95a85dc2bd546ef57720053e74760baceeadfa0e6b03ca7cfa363e40e14beba0e72d513c1c9a11f60a017231cb2736480955df81766cd4650fcd72872551ac3ba2c931aa07c580935a994d405e03329e7d1002c03c09a450cd84a5eed019337fc2578c3b12f03da7e9a7d4a32178db791f459f6f0db6d894c77b5d9821d50f004c3af09b1fdb02bf8d05aaf626f8772d19dcb3d17f0b2587dfcb5a728c5c0f346e7210d3a1b2a74fb96c1f43ade9c0c6ad9b8d138aaefaa00cf48906224ebd6888d17771c59d2b74773a2618d6dfd84e5a016aa997751bdecd287ab12fde2ca370108269e66fdd415531ae6f4f41ffa48b10f3f2fdb119dcd2081cf80d94b7c75b970e260b2ed8fb8435a5a1baf0c84eb43682de1e841bcfdcc354dcf977810d51af29ab04e0a1eb71e70e0025e668bd3c5703f91026c1c2ff1a9c3fc64c4c2804308a6d15fc54dff2be26803991f37b215b7a631064ce68bfe144d6f66cd4005979d714453e90d73ebb3ea577fa70d9aac030cc41c45fec1ae5d4501e33457e4800ecd8b1a0eb112c92e499e2717d6a4cd83d01db0fee4a2bbdb3927fe0a8791c799899db603c561895dcc378121a066f41ba4846213bb92aa446be3c7a6f1639aedc35f08061baca2bdd38eefce8890e948f1dc428bfed59d847a5c2288bf029860744516ecd2a7cc3ec8da20d6b318a1c3c1e806aeee613a1b51f5400e9860a1484c61ad485455ff0041a1d457924103f3a4c43db89b38e1c8a7bb382eab8aa38029510c177953d879c9c74dc29a78880e8078ab08448fb82fdb1989407878712a62d217fa9aab54ff9835f06071a784b4ef3090d79f7fb9458142687d05d9501a605e4edb037e1a8cbfa073d468b95eb0a873e9a3c0c85c4d1e3b18696caf129a32ff49bee5f031e9a597cc072d439c1269f235528eddfc1be1e8116ddfef54dafd95a2af86df4a8d8d5b71028b172621e01b1dff0815e5afc8a4068837dceea8cb8dccad20699c8c74a8923c31e6f9550af0e64fc15011bf3f69772008ef54ca66e0e02cc72df58ebfd5ab9bd3579e820e80befa8018db914edbd08e303a2f05a7c063a86a3b3464c6f243894e6756589db56aebdc7e04625f1723319d60963aa5c7e4e207408e26afcad55d18929280a23ec791c51c2829ee2b9d3b8f1f49c62823e7d0c3a8f5db74633807f525e9e2e8da63598c90ec74841ce8c7ac2ac21bfbab9be8db2fef22671c9636c37df41db822acbf0fa58420593b51b70acda7f3d2fb493ce1399799c9c6242fe4d1ddca8a3553f9bbf0ca2667caf15d684af35ecd1a93dd50f6bc12158ef5d31d9fbe1368fb65e8721ff947862f14f24ab4f285fbe94ed0f53a6194ec6c4b80b5d83180337ee2dad861a28155f6c1db006b722ade20d2a78590ed0d86fcf9ab84514f4496ccb694811cbc5467b9bda8be9edac7b9dafe4b0e7debb71b21ad97d127eda3e4dea0d1e8ae75023cc4707ffb9a071c04f48e67271870c249ad4fb35dbdbd801492fd42b5198cc821599c4602551379df5e840e3485e4ad61838ca3e6f41055777b6ad08265963237e031bd21a9f5035dd50ea16c3cfc681e491337354a251e3d33d4a1df0c2046d8b18607d5ffa40f1595a8490ba2ba40a96aa5dce19c098dca74bc1381b0183f416a3298837f842d024c331007a4442b6448184e9b8f59029a45f583257610e4c9723b6b5692b86af27cc069ffb03d2aa198ee163d9896d25fe5ed9de16628f4e20607d2b59d7ed3f9ac75c930381fedd804909b48212082fc501e7651742f95277adf524941c447c7a7e4fd877dd6eb5b37c24928c0ff2be21f346efc122b1d5bb84cc33645ec87d02fd414386dc642f8432ffe017e496f13f2bd906602e25a650bf3e60f632ed5a201c3de2648d2885d1ece25aba54ecc00c500d97e1c5d6ffa8a6b46f9225c2b29e20dd147d74fad377147f4f3bdd7f3e221e7f00d6f6773788b7368bddac3d591eb5425a687398a214faad09c73ef9ba080bd3d34f235d6766f141a72e06b9f2d1cebe737d32b04fbbae7fb6a5c88ce472e3987165eefc7c610515d9980d992698187732771cb0325eb98d2bd7c1318357c78e59fe9ecbb2f175f9b990af081454d37c4a0539882770d51265fabd8010b89c7d0219de173a80f22b6ecfbe6977e6996d888a06da270034c92073f1c0185920a4799e8ba5a798fb73e769979fe550d1240da34f463e749c5124f6afc1864eed79fea9c569fbbc1a2d194c79dec6e106073e3066b1fed3a98944d8928fae622683f18b28134627085da1d36c005f9c651f1314f2ae3910726d8cc7269f984a2106c831f2acbb407eb0ebc510a0254b1cd4e21865cda390ba99878b28ab53fdeae6d548678251ddf8846a32eccb587c67c1fb762d85bafe3dbb8a80c5b89b46b42ed03849480b58ec736741aebf5cda7ca8ba7b388277e1a9a6b49913cd3d849be1040430c350aa50406b5942576d27384d7c245eb47319d7c00c649af2aa249de727097408801c8301f58c4e58caa6d5dcb2f5c17dbd1303fd0e3251389e1bcf802de3dd345be803ff051efa36fb9776eb47483b79617031acc85bf37dcfc96ebe2e9ee4c51677788cbc210595f03fac5c85164f12274912ed708d52837ba9998f5499e7bd9e6d14eaf5597d8040084ef2d66c1e1088619b29bed642feb53747a95bcf8f8d84f0c995e712d7c64c1b44e5e72ebae40c25e5b615c81ab858c7cfdc1199a301050fe05d3c5894e13fd4a46d123aa4f24f8e30f5712b0d183e9f7865a2380a081362b6f27a4a3383cedd17ae81411bf68c07482d757df7899a1fc733dbc930a514992dfc8e496114c2784cc4eb63e877523e814be3b212f18b2ca436be9bdef198245082bc83a8ec90c3bcada4675912d30b489b741f7d64fc678c2db13fc65364bc546139a87f627253438066f3d3556e1b7f345eab928ff5e265c4ea1881c161f4a68a1a16d76065a8092492e07bc20a1f97271d5f4b57b12dd62a2403e91bcae3ae09b0f8f0fb18ce2a68545f8d62953e120c2219b8923be60a390a245641ea35a7e942fe28a27e42fd4ea390d06c9ec078c2e5096a8d340e460af580d3f0647e799866c1cf77a5f366082d5f5e4bd9d430a455db8e68acb6920a8b05336146e2cca3ca45f8405b41b9e1e2409b45c7a5644423e0c1ef423cb0bd1d60e8a0e3142bee102fc2b0e4f92635479b301f629b98155f4979d51763e6bfca5ac8d95500afefb80063de39147c3aa17baeb5134b3c9e4bad074c3818aba64fe9e901bcfa9873f138fc03084e729c3d513e596735af3880f1dff704b85250148266117668554eedc10b5433114600f9803d8cd5618bb8bef8ee561eec0229024bfb220a51c30198701cab7000e1c76d98382e49aa12477ded9b39597196fac08ec32254b7a4d1b0b70c56e1d34122d341627bdf698e57801e0093502cbf677782f23f6155c4505c9376ff2ace3e07c508a8b921d3a77f82cb3bc23ea82fe418c79ac6d9cd835a19dedfa663b9eb29b05613da6f470fd040d70978f403c6c0e5ae22191ba37b409fde095f4dae12e2d237663f7a18bcd5382cff71e3a20bb34b074653640fc7b91ccec6f6ebba04bb8db859a42bab0798a3aad00baa7d30910b5bced35f3dedaa4752a596afb332fdedcfc4605446851b5b794113d3ffbad7680c340a0985b6989c8a027351165d86037252f656b6d13769e251009a7b95080fc01e059e8377425d5f5fee7de28bcdcdd3bfba91cad07821f2b4513087154b6bd5bd2b272eae51e2a31c35a96ede1b052648620361fb4fbac108700489c1c828982efead0eb84154090d180281107fe8532b4e5c7f4146ff81035de19d305b9e96bfa0e5d9118efb3a3ffc4b6a7bcc32048e521f33b768f30bc237f55e346dcb05e1f7cfa30c1757d5975252a2beb51be6c18a56e4df97462030f2a97263ebb4cb4f5dbf00848ef4616eca54af80a9864395887573efcf7cc1c86a74cf70d09b97576681db47351f166e42b17dd95967f61dfb6c35680604c7efbfccc3a8e6765216a8eb49347f830e08d3fcacedd2eea4157c2615d140197434c6c02ec7df75dc0f8f9ca4bf799492270cdd60b134c6ffe79b96b6251641c234a9194a59fa336210932508264d74bf24524a93ee217ab03ce758a4118cdf4f17a60cc9163401f023acd9b7ba9f088056e67229d72f96b97cc9c5b297aebe72b5e4a5eb2f5cbd58eae22b97afbcbabac4e56b2faebe7059ae5d0539dc62c4f19be1a705c55fa44dc8fd11f3e907c09756c4588bfaaa8ba1778d459bde2dceabb21f8a88af8bb96cb9bc725006f8c14aa6c7429b616cc39602f73986808760ecda759bac4a7e297f580f831843aab3fa686bd78c5e5bdeeef9ca9cdb4cd52e523ecc7dc300bd8b049bc23d85a13c915593679f2a70313a9655cb8c8d04547844f43905edab11b17e6240f5bc969e4411e8061401249ade2de82989b78648bc0bc909c385b5670feef2d19757b71f024c28fe1570aaeeb1083ac7206b5cf50d5cd1c1b90357b538b3d3f840094e1b63c54c50a6a9a8fd7ae702ba576ca9f039ab32b4d0aa1ef2daa459d53070ed336ee692f1e7839841be7a5f703b854eac9ac181946f1a00680a37d3716632cd36f6f494c395de0f595055cd3e9f9d4057726471cd15b9f4d9dffa3e825f200f8f4f8fa5b52801a09d4ae0c3ed152eed7947c830d1ef92811ab3b3df95409f63f8c2b663670e02c64134f957ff333369047e393f8be9757efd4b87d0eea3975d8e3593663c6ca7028afa07e95ee362370896cd9c45246991e4b68bf33822d928b46ca9cfd242dd5cb173c9eaa7503d10f6fd2b5d5d8983186cc0a0ec48498f794020f01c1aaf81606fb89922e72bcc3483b9071557bdf6db0d991bb84771e110c5b25fa0b091e90981777d8a57cec89f5799046f5ee4406f4cf3c2c57f2c445ab0543b598528c2faa67f5fcaccb080970a1ac911d1172845105c24d1a957090283304bbe9e7b5d9b01da7207c42d722fa06ec267bc2dabe7510103149da60455db2e812cc286022d8a3e4b5424e5601b17efe41778c9427de0c12525afd81c3bf2bef443a83a37f6460eca01f10834b26a488ccaa234f8a96e3138384ac1c0b614bc7e5ff343f57c96b6e2110c4e48826c17e1b0dd54b840969fb1c2533dd0e4dfbd9587a1f3e57bba2be12d52de73ab3ae8ed9cf02fc924b51fa1cbbea2fcc28b922fb9bcb0d4f52bae2f2c71f98acb4b2f94bfe4ead52b972f297ff1cae54b2e5eddb5d82fc5efd9621e7f105ffe3d935885c1e1ca9bc0b815c4dad2e65238fcf656aad2f0acec90e9bfad99dd877fc7948e8a797d2f7519693927301a2b93d9e0b6879f8ce3cfa552567826772ef9bbd2e711463b8eb5a1f8080f3f78e917e73de3eac3cf80bc8a896800664d5d039bad398441f4f0e34a215263e26761c10f3f95347daae9bc9a2afe001f7e5e3c4f059fafc68102a853686bc6fefb37a414fa76ed0753e13b19d9dfc30f56823d17a9eea0516a18e9f50f3f1e3d6ffee36420a5d8caa1674a616daf0b14acd76a485cacdc117e16f690c36fcff31338f94c3bbb1fd0f5c378499d061d1f134395007cf801409fc89c3960d6ef712181dd19861f9426242c5d145890f525deb9de10071732ce9b436de48b121fc1456ee6a61e0c72431f7e74533c94e3879fe52704407a3fac956488fc37710961b30fda54378f2d6dda50dcd86b0065e4cf8f5df5eb71b4c72a974b869f3dc9b12e63a3d09c0fc8ab580f2d4822e1742290f411565b2c9cd018ca03861f090e85ed8103cd3594737f57072597ebe28c46ffc8bd0110a6a943bb0b13380c3fa81e20189b632ad794683be51c65f8b153b558f9e46c5ed0cb1cc0a7e383659cc0cf78809ca6d2d97ffb8fd9452eee46ecbced4e0d66961d0c3fffba16d35cafdb1d39dacb3307ce71626eb0b16d984086ac1254287e6975f58954e8e206e2a2b20f97a4c81b980590e72a3a113d806107c1973a2bd2e8427ef348789f6519903acca66f9a8444a00066b5fbdfdb068038a7f38b965a2b33fcb0ee8e0ac361b341d2844da4a6e132f9d4d48069db0eca36bda6fa73a505ad6560e688f4c72c62c0417a0c3fb0859b9bb4ed187dfedc8cf3514ba39b95bfeb41c32ff18cbc8b6685cb3cc6e4616cd38d44a71b3d4d7dc0b0b25837ac346bbcf71ed91a75650003bba197b6fc8876434752d9658713e56e35fcd393fbb6c352373bdf6eb093f0fd7723a62bafacdd8dc572fc6e8c8148b0f0663f356cbcb957594a11d3ca5be4f4030f888590f7dd0d8de0e8d3d23aec8293e0494dcaf47b427ab48c9452eb2954d228bb21d716c3b982cabb7d5de9b3ca67ae3106067152583b4a230b69ce0aced66bd2bc0fda912f85df07dd1ef3dfbe04019c09faebb51dcb7d2281f7eb8396c55cf725b4b791abb28e20c9d9423f2778bbd6eaf24994d247ab4523cfd7474a46eec28cab1d2127f0a0febb5e1b949090ea038ccca6d72df153fc64b04c245ed7b999a7733ccf9de757caa918207081b2267ed246fddbfda00ae91912939d502e9d9db60178943fe9dc55c09b61fec6390af146436faac0ac1cc2ff7ae9dce5e1f6a07174e804f5f4cebf9e7a4d032a9060fe40e9b64077b5e4bfd6c51fad23e1da6ff5a73c248223ab4ff4f40e7dbd866b50367a05f9fa82083ac618e002657ed75eb58c88924b75f7c00e77b57bff6c8a12cda34f027adf35f4bb2ea684f32be2a4beaf5a8a37ba08c2ace49c033132df5793d42b6c18d234bde4d1c0e77640871b0b0450ccbe93daac6f7e908e9f24eb0b52b218a34c6e89f443f6fa294002242a8e73228a90b2bb8844320c0941c03ea5f8c92f2f826538afe64795ca96bc1645fc3dc2e8fdb6442a871d6dcceef43a58e412f7f84ccd8adfffabc27aff088e6e780fea1e4b60467c0efe3010630c059dfe5c8c5073d1fb2ba61162ec635b137805f11ddcf12d6719e3766d471455dc0d21062ee94916e31bca8a246110d4fb2826a10bca90cfcd26a17b605abae6d293d02126599154476324c61abf9c482ad4104352c2444321be8814356208ed9450be8e12fd55ef41e970cab51178c3abd0b581fa7d176d3c708b5b091fdd4f75cfd8f8d22c5f7b78bed0a95b5b3e65f3e96af8ffdb80d74a981e85af75b048dc5b3edb0dced5bbbd4520775538de3dd04687a3e93a91f859d40817a028299690265c9caa0382fc051093750981abf5219414f61f9e2a9c60c4eaa1a8ff21339c7d5a74c6ccf886f4bcfc02f5cd698a6a52b1c93af8901d63e5464214602ee88f24d22f623db9c64602886a27665102dac2b708d80c9a89afb48c9a2ff727a71f4c33e72daf4f5ef4defde13d50036131761faf3c7621b280472586c1584be84bcffda4a1bb51f3990278b06b5b45e084b8fc1361156d4185c2116f5945df9febcb6b49000eca16132de1edae56f7ad418bffbafda9a93772fab8cfe1d106e36e73638b2d44809675fba3b10e20ebe3c4bce98c3434118606645384db3ec35bad56efc32825fb3bc3c0ed14a0b2526113b2d389c0299e401d2ccb14d1060d6cf14033b821912afa6c8f270fbdf1b236ac4b939344a962bcc33428f734a79e671b9441db4f1ba510c5053224fe29cea8981c0c2f1c888f7d881071887133bf504985e531324a9e51a1879803f48e0cc2520264fe5d51279c8a991170cbf685030d0f736209b8814b5031866dbc9d0e6cd20cb619dabe236365834147cb85a3d0ee9ddb881cc2ad70dd94d5342eca1310800e316d87f1fd51c012720a81dcc45394c0a7b02f0ce1ecdaa73b8fc4ef9232d9dff5b78fd3152c2456d00ca982a640aa83102e9116e1f81acce8285e563168a08742ea1e2394d4e1774eed8e3246a434291bc6e23171ec42fde19dd10409166c6641ddc30a947280e622450e8ccbcf623058fb0e84c1c8e63018c329328f6108c230d1413578b84a72bcc159cbc32b0c91b3ef361384a8480be0255a0bf9d78b02cf415fd375b828ef718935414c13734bc212d3f601565e3bae63b88d1a88e2d898f258796e08658fad25f1d8c76626fcf88c6a44b0d02582d18c61b2ca574851bccf02207c0621c3e17a797815a86785fb268b51dfaaaa725344945522160ea45fbd67ec789a0bb1c4c60e87b69a16444e34b6315334e0baa8b2a0915c4132e9fd4e4ef3f4d200e509840e7e087ee5c45f16ee190cd412d4f3c288b99a30dd92267e6de525a61c026302877bf2b9d076a0fb04ece82200bea82adce7b4052e0cc2dbb4faa5b552e5814dab3c243c64a31b8cd1336012dc41c6eecb2c479540898dc7a8d4225efe29e3c3670f2f56cb798068c4378249fb06a28800fcfa5ebcc9337a1b4ec25dfd4131b426559523d5da98dceda60766da4108535418f143028e7d3ca3dbb4c3506168626bef1b3616c923a81aa7ec298c97176fb8906834f98aa4cf2034ea29802bf903fe0816685903ac2c3e76605569528b2b32ff0e3444c7076e1e47770ed0cf8ae01b34520e2b0cae90d71615033249d90316bbd95e633d222362ad55d7ea077b81e5dab50e97be09cef2bb4a585a3750eb9ffdedb3087990847c3477a1e306cbfb30319994c1dc96b4b2c4b395418440cdbbce530999f7c8a129730f924c35e63c8e3c754650ea8129bdca7675705b3ca990e634364cf299277ec7d83e90982451236b3fafe7a5f9623e43826ab0fc39b125e5f6e09e2b19aa388898b41c931d32f9db075872214ba9a26d2b138b99433fcc3d67d8626dce7a045ec102e5ea17df35800425843aa7f7cc2da6e862f41f5603b77850caff99215f7664858d57632acd0c01a840f966ffde03f616654bb8f725c3bfc6c70fb489dc6fade43e7378a547e0755bb847aff4b81275c6f416a5a945b1ae2757b269b4f1c240cc029cb76230fae6eacfa335925c50f581508649455759771e6dd8a6765fa59eb662acee948e4e5ad4564c75113a76f276b1e0109f86470bfeb9f3b4fef03aaa2d94c77044127b74958cdd9983f8d9c3be8f7fc1f2c103822ed802abd683b507842aa6ad14016da52a6891e213a42c5016ef264681f9ca1958188f1e75cfef38aeb681ad0585b5b859a4cd3e3aa17e5f1fcab3b2cbb1f6535fe5cf231bd08b01ae0c5e400127b1a76e73beea4fb6176769350da36687721a2fff51b036c09fb907b4553e983d040cf3a0fe1698973fcde0e24e69ae4704dea97fdc83e9d5b97e5233e69c3bf00838a8846a67722a02927138d922916d0955706a33ccbccbc7757c6fad80045a9ab0574afc3e9c912d4f137c4bf3a43ebe45043cb3f176b37d87479c5b01c29285f1734fd6cd36fbf47081a4be9d50b5e10258b442cbce4b6f2a6a69155fbf4caad31caac4b7fb944ec99eedbaee06301772d8655b8f1e557e3c9ca3de3f87fb7f6f507f4820373793089cb922d7be0f1312c8a82b1c58862f38ec9c8a1b3d1bc5f300fe474cb6ba55b057e76c2dbc23d13595ed5288697cb9a09505c202e5c840fc89707fb3b8d2402a9a048d3350dcc70d60f0fea93f7909c24d7aedd95b144d978726f4e9be074fad51c24116507e49e1d33cd511f43b75c57653007df102f7e7dd79c59782be2765b2cb756051dc174f662377a60f37330a7d9fdc23a8e819e2e64c8b685f63b9044cdad35cadec784c4be37b65394ddd6abfa383b8bf10d746d8e03da9006d651c187ea86d07a0748705de20f21213aa1844f736d8ae579765c393de1b524b40f4a76b65c559e7e65643f4185802e8773b536e2a553eabc2800047df99ce9d6b5e9ab2a1d2097ce8de856e4f6551007eb4d08414dc3be5586a5fee57b4a9bfbf75319070b78f60a7a926b9cf9268cf2b8f092d66d975eacf5ac38b89a45dc1b8e4444127b26904f391d52ef8ab29e5faff5d74499aa305557cfc2b39603213609a250360407424c6329cf8279a6ae084397cea073c4f300080876dbf417de50b78e411247a94660665f1727ae042f7402b073dfbb9a65ccc847612435802b32dd104449ee03d2e86b66d2d2681253956ec12fb50bd5bce14877f56622ffbc8f20d6e8132d756b55f5983c968fe9e6fff8ce5b8f69cea062671c9d04f096cf88d9919b3839686a00df017837fbb02c20a763aae832ab14d4392e1f9eb3540c33e64b56c4132db3bba6750649c34615bea25dd8a09ea480fd72691c7ebc24ca0e82385761e543a7077edf9f8149645baabc14fd4f5fd47783bf10fae6666a0bb37a55094ad3f6995f096b441c130d00c6fc71bf5e44850b5197182069e217984a01d4d14f7f606d9e56475cbe658f339d035e0821a7c882e89a6046507303a927f13c09041b5fa1b6151803c688811530e192025714354edd695e3d49902703cc59cd3b306adfb1541c65298a1adc5821b310f6765164507a29952a7947e014fad969339f9fb71d4c5f7e495edf4b12a4542ff5b824ed8b56e2083512115b0921462eda40204993cbfa68cc0c2b6df52fcd389316b425d6f8587d8110f69b73735756e6279ad091c1601f2ee9f60fb35067762d1a9873f4aad833f2834efdc9039364273710acb6a3d0dc91a79bef05079b4f6c82099fc9649ef98b5dd3cba04e4f2ef72c33d49c2683cbb71c48b376620c7adf7fb888f753f23dfa174a2d741bc6d98d18511a5112fbc2805318ed4bafcc133294a11dbadd07d7b5cf65bcbbaa2f7fdcee4efa689524c7d0bb36cd2aa18d13df755c8ae807ce198a4a35fcf6ecec45606e0e8b1a01cee8b123f63b4d8f1c8eb106d36a6e94732680ced3e3fa114354d2d4c374e202848e35b603924a985a7efc892c66794f34524e500203a7ee168fdcfe3253a901a9d9d02cd31acc1ad79387dae418de32cfb5310de662352a61382cb607c905127227a71143beac4fe5332d5dae6284b0a51ce1f887c5134e8fb0a04b89d689f4e3006a2c941d1904e834378a79b95ab68506ed316e80d7a3a91fcaba68c9789f22400edf883c8970a1c80615eb53ff50aed91700460cd4204c0960935c60ea28523260605e20911d3d12f4925404df0329dc8f2c9711dc0fd54dbf5ffe4be2c2935b13bcb8cd38c09c3995d308023899d27e618d0679032f44473e7132dc3d4dfb75ec98c574f1f50229ff9507c9ca96d66a816665181c7fa7280e165da089e19db41301eaa31351711fd813111063f38fb5401d753c0d4e8f50c0a4d81264131eb41fec851e4a837d021dc896762f55f12e25119c269baea7eef5248c3f8b1ff90a6603fa2e2d49ad64159e12fc64a055b62f4dbdc72dbb09c8bc81a54e75c862acaaa83433db7c0a80dfb26906ab0952914ffb26be8bf76cd5203bf170240e54a80dc0e5c42a14d42cc74a92768cdf329e195c8171fb18f336a643a3dd12fe54ac36db7d865a5210d53b3536b80cae512b8edc786c6438f4842c8884d99c3613093bfcfa58b736f9663cb4af1dc8b81776d2ef9130469704f978acd47320978bdf4228800bda16d13e39c4710085c4a91ef63adf8d4e86dff50f779f615f16fb7805a2c0226f04009093da5143e9d700926102cccfcc5907fc2559b3003a595eb8ddabd189547c3f3e58edfdb0d741d19903b07148d838b3c6b37776ab674442c0cb6586e6e7c79cc2e83cb938ae2a0fd0492c185c80c7eeea49c6e46b77d0c98c72079aeff0b587db0df20f823788b0da80d2060633722dfa41ab4ab9e35bcf9e9c9d0c319c6c8b8566869253718910f455b12e001e35efbb5558e79b53d7e6b7b5ecb0ad21e3716e682d1856981c6d04f239facca1407dd764dac063fda6fcac89468d8130a4043585b740b09282bd6bf8de66267f93b6a42a189f18f758b051eb7c3ac73461803d850cf08457c6e0d4b281c1c9d500f01278f4a2882c60b52bc861a456d71b13a3c70b6ac426f930a029525672ba5236b6ebce27d948717596d8b1d0dccd96d4f5b573cb8ec2250967ac47ce0567c9b160aa96a66c5baea69142ba63259012b6a25f52546b9159cc928b67b8c250e89b387b4ba28c2f5a082186d07063630214c7377a0ded840d711025492880a7455df73ffb6636dd859d7073e87df7d08a433a5af304791e2f397e4b82ddda33a236025d02561330ba86e95afecea7a76c5e330540d7f4ac4680d3e14e7cd049487aa72bff612b3073193448885a18457420c20d47fcfe791c159fc3451fd04217743b5068a1dd82505c1d4697dcd1647ff3c22bb9bc860bbc7b5aab354094b88018cab0cfc280081eb77b6af23cad9cb0f94c06dd4c60fb51e9bcbf5058daebbefa9c6f1871e954780f602441f85f33f5a462909403213500fa8c42629f451f4731ea636273e1945c2096e73c6aa657a8e6a594965d79b90168a06980d9ff79f87480d63b096985385c22535b192294689a9e3203c7b473ca16154e6fcc40b09ef892ec6733b14b959d44ea7669c159c3810ee056571a410084b9100911ee9599329b533a0cfdc71e398a9507bf4d5e87f5f2fa72d00510894e23ffa94fcdc36bcf167ffc15f3fc8455f384e914ee0e05fb59709a9b38c64011053ea7ca74aa2d62ebeec17a65495c3e72a9ceab843cb7eaa410d9c72737d37945cb18acb9c4da9d1b23da8e21f1250d07219400ef3704af7f040c290115aef02a1770b8127066f5254f77e200f704986e753c9610d196fbaa686d5157b0c3ed01593f1e8b39a9a7855259c1bbb2d6d8084b360d93cc8ccf10b189beeb268bf6cf2f4eef8287cb145eb69c5346c1c8e57b923554f3077725835e26e8b507b9ba811c0789a0b1abba2e0169727ed7a256584a30e9a01da42a61d90afaaf80284c93686eef6b193b6f805b603e27316561c053b2011bb4b183cae0532d20526904fb565784cc831a8410fadde9df156952b72d78b28c75036ac98810daf33ca249c1a043938cda2c3a6efd4a9c17996a48544fcfea763fde8f957cc3ec800965dec85a47ba146087d64cdb14743713aa1441c1763057b7840a90beeb8da9e881a0bd4e389240086e80424ec7f077eaa5fb26b44d4aa6b214526a06c62f8126a4295929df20655b34fc2f9c30dad1f3393e7ed06b2dbeabf3f4e38aca8c53901d9dc1aaa8c8af323b065670d75051220830ccf81a08a6f127d121c54cf0dd08d85e8c8dfa0e67a12f90133a743efd6007fa3dee57f67a886a112ea2445dcc7915b604782b562eb6f16a0b333e2446db90cc760df76933d7fadbbfc8e385af7be08465b89ecee0f1bffd0dc62876fb0388774d8de30222abc4a91b8d083d4e7938038158d3b2138c4e116082cbc78d9c9e709495f0d1e7f557eadf63d1dcc1755b562e6f52aff8ea51171824bce63f340517514958bf491ccabd30bb6d602fa368ecb077e66c86c86959223a0898fd402c98ccd76b28a62bb4083b49f732f877a16f2e0a2d51ce846223cc891747ff8734b6d09b2008e2651798078cc4ca353021b7d7881aef9a0c56d2c1d40f89cc3ef0d7840295f3d62ccc9fd0751003d2191bcd318ed64e346363c84c264f9bd8ee0fcaa3ff40be0458db5068fbdda9457b369accce3c524d65d25c13ced5eca9e82ff9d01503e9509a7e04f1fe125ad52b933dcf1ee3d392a0c2bf4d43fd07802f27b7402dc76badf904b66478cfaaa094d8b83c4c661d40ab3423133093f5cb1c1b599c478957ec881860a63cd8078a4020cb003771481ec52e67f1f7f1fdf6ff26787ac55f19f128eed445205d21c07c69973bd0ed7fe5761af01e099dc963e3461906fe3f623c2a9f8a50f6bbe236aca2a62beb903ca88333c1a024db5344d2ade301b9531fc10abf15fb7723f8d93d06e226f3a1f11ec3d127e3fd3e9a535eab01ca1205ec3e9fc8d0314405c850d81281f419c5868e968e95c2aedf40d14c70bb1638c71553c15b1f0b72ade5d01c06ff671b7932280ca020114e472e47b0df75f4c96c3971a4b87c94e69702095005e1e0092a4b5c30299d973510463d97004f33834b9807238eddafa9901e94d8213287e084ac9dcbff7e59427c1355f035010131248aa8708502ef2c65f5a2057ef2379d45aca0eaec3d8b9bf3c69710c4b226f5735444d8fde87074cb8913a37d22ab6a49a20b97abf375df843671b2f3e54cd7c4604035a41c7b321b41af4dc6776c4f63ee61581af69cb245761febd75d095a1bc8d407c1dae329491924522227ef489d80249230e472b69e854133b26f82f6b8bf351c794160ec25cb7ae263dab1e10b07ce638681ece44a8552008e833eb9148c55f3d1a432024c56d8764cc4737a327c92d1ff3726b2d134262d90706e985ba4123c339873300211980ad8f52922a5ad4fcf2de576d4075a0eec4e705373c6396c8c668a33aecb53f5094abe317df679c4e915f103fd5fb385bc2ab7841b2b0d8fa1ba3296995c49644c86aa6d08de4526ef6cbaf72337b3a96506c65e2bc6c29ce08e2f01e62390b50c24c6e3fc457e836d73f22fc70a274a4f52723884f2fc6dfe3c0aa99967be46546c28b9ad4407713256d27ec495f4e76b1a4ca9c3586470308508101600d96c5cf917e0b8b4e7b7608d8abae584e08ede32c48e12407821cd05cbbf7862d8ea13daef452a63b0850a57826a8a4c47ceaa3951a87868a84613aa5edfa7cb558b0d10f58c1c298a5776ea1038b1d69096efb93494c970a8c902bcc46f50a0ca31b0aa582f23f4b43b8334804885470b9377554009fb70437611d8aa8b04648ef740b15b6c540b1f4ebaca3bac2a780e68bc2cfcd5348d63a3c29ad5337755457da14d07cb9f8dc6a0ac9aaacf92734ab4eebaa13ad905967b21a64f8b9c2ece5c252a8eb4b1806ef7863485d451e3ac93f82268d0aebe6ba0b624b9db433138c5058969cd0c77f2bdce9c368edac8e18e4d8021938dd342e96a08b16882c35963410aca4b0cd87baa248a1be4663b8a2178230644c30a3ec4c80959619407495b4d006027568d3c7b3e492b8cd4723a0242150e33cba4b891459f694951b468029dba9b16e4fa3ece5846322af691f3e7db2499196f121f6bb5f0861d911fbb0fb5f65bb0a095020859d4a45573412f3704a0cb5067b2bd6a877df203b915840958d10a69605d523544225d1ea1f8a955e50f3e63404a8615079083bf414e1c1e4f4107546d600dd5da8eef35545b05865a4e5eac67ef4379474a00a23bb97eeaf45a05f7db4405f93d8deb0d7b17115f4a0ec935497edc99e8743138b85da2372f9dc84b910250cd2748927dc0a67562be965f422ca887749af626e164b3cacdfaaa28964579106c808e142c52d26d0b610219ba2d43dc62c9c7e4869674d0384bf8756354e8d42e53be01c699b4d6d622520d8c3535838810f263f451a17c0a4d7c91d50cf8591207e7a1e1c5a4d3104312f9254a42bdedcb40ba326ac37cd305c748aea3c97bb36c7b3235986089f13a8a12e082a904e77e7900682f7379aed97967bbe30c42e67cfc6c0051ab1c4076d6651fcaa27da159970280cc3a5c22ea97c34b4903d8a5082a033d9b3ade7d31a3d8011f66ae6de8e261dccfefbda52e99f0337fac2bbce140d79f8b71f52bb036955d65c991c43540ae5624c847c68db5977094748141e3eb4bf1f6f96083a2be3ddcfb12eed6d1894e3f8054822505d75bd394434024dfc0402fb9c05626fd02dcbd586ac52fc3b8d5bdf8e6e114e3a6373a5dc0e80dcf12db132706c4dfcb74b463f54a427495ac0901e992aefa212d7163819300ab4d6bb5314e3765338dc6aff410ae050ed368aed8a9eed3bbcf8d2527b9b499fb9a016e6da4c51d7b3326c09634cd7ed6d1982ecfd914e441001d9e5530bbc392b5992f4ec3fde1acc28f2cda41e46a422c0cf51ee7fd1a4a035fead2f62b9d19155bae222560ce09a458c1b1caee8b26b282304085c93f26fa3632b3a74e90a169fc256702d5311a9a8a0a0d42d5d47cbc1759e434ec06b9716f22fde6fd1fa943762672d59b5b07dd0dcf28ce334f35bf5f48c0729e2689f846bb921a795b44668a423343d371c3c43a4aff6b52c9575dd6f412c14bb7f872269ea8392a26eae4de9c7998a60bfac0950f78c3fb3dc0eb6575913980b14e35a3b42dd16241c5fc771504a97373f1dea81133f3c21fe84bca4f347d3f9df691ed7d600e406ee0c1b1d7d35a05e179580cba320ceff67c519131150409c7b6ac2f1c4628c9c5c72c99421721a004acb25eb32618e6e598768354035efdd5342553123a0fe10faec6830050d1cff6056ebff7333e946f7cf32fe448593052014e1ca664d2f75b97c8183418a57a6bb2acabfeaf468643948aa371fa9feaff9a36bc88690defde47e5a3364b537d726d8066ded258ee9abd37d0fe481154726d3e4476d1b33da047299c88199ee5f61f19fe229bce6c5a3e392d93c70bc05416868d4866afd5be2fce11149749c713aa1574fcef59853587268f8c8f267c21c0f7334eb4739518db13b22422aca9b4f5d9a245bd5ab1a153a46bf3a3c0e3b1ac27654b0e97b91f838913a58f5bc5fc9d9043a3aa04771dcb9108d54745c9b7803d230ab965da20d38c183e3842a98eefd91047fc9ad307875f3eb8723ec5f773e5538b97289806ebeec47f4dbe5147709305605a41b5c5377292c9a8bcdb777b06e8bc80b8e7d4bd36527e0b97f8178cf47e4fdcacb48f57a141e49f28b2ae31adb096347835c015a04635b6faf9f09ada760da0572560fab42f9404a747ce33ee0a255c091d05c11b720e25cb9119eb10b6ff010c9ad753de4b161de4505c68797d242a7f7b44ffa1b86e522b1d1b9817d3d288a08050767eacafda7469ccc881e03c029a78a356ec3fdbd679137c0011a0471dbdf3133f44d9b9099c11f4e9dda229e0383dfd14f67e46de2d3b82cf16867337fe5a1d5353162073fc841cb737db709c9a19049449dc71530c90c5201bf15864ba7360d057195251ddd60d823f06fb48038eee428c027cc3e6a83f1d884f0480f3c1f524be91f9b6617e3a4f6928cc68397f6dae527c4274cc0d4a9df892545900d863f0460428b52b89ed70192e0585918113f77903f04511bbe430621fdd8be8bd55f516ea1373fd2ca4b3294572f4e958ec8dde15daf10d8c8cdfc476e0b91dc8209b37a827655f4ad4dc40141bb554cbf8baf25f70570ba66c0c658d2583340d5bee968ecc61ef7c7bd12cafc0690ea3f7fe18eaa11a5e9b1944902785b28fcadee9fee1838c5b8328017783bbdd84e673532fa0d57a9ab5ff10f2c4bcb3c26d1aac3c5d2d25ddfaa731f7becc6250aa37104eb3a8db95c9031817f86512d620572255ff8ca1055eb5d997eb090df5874e0563e2370cf6277193bbe3d3dab068ff9739e752a904b13fa13eac195c26dc1a64dc85c33e6a4cbdd8daaf65422984c85c06dc9f51a04f4845396886d93e46259dc8e72963124dfe4d3d20aa21790cab9acb897fc1882a17b248910e83abc38fce5975cfe2b3ff2a22005a06ca4712d11de8238a0ebe97573ab7b79c9d737855cd8939ab215bc03f3ad0fee49c883cad684cec9cb24abb072ca3da55815fa9148fb385c4fdef84fc90de33e3f09eaf31c126d475798eaad90f5721504349403e435e41b8d67d9f265e7ef12b59e0e4d8f538ece3a8042578d454e94a2c40263d43f340464548d9c565651bd947849551db36716e62297ffaa635f46ee644d4789de91f3fe9938355afb5598dccb00c94d467201b280677bcb5c70dc1e80a7665062adda8ce92a675192976a1aa168e0c2129ae2e08dac5afcae66498bfdb4f02c26516ad6e1cce2a73cadef1016fccf62442ebce5cab00c198a49dd3a53e61ed09623ae0b9810e49a69aefbede835b4123d7033abc19c9530d552547191a67b7f58613bc6c260a74a42af6f3c64e3811b3fd8e2c166265e41026fdb3ebee5115b0fdcfec026faa0aa6fd87a2af83bfcfdf66b0d7f23f7459336650e973667b3a23f7255530e39af1d8da0d2faa4f30e17f7af3197ab674c0c7dc612382494fd19fb2456db8e477bfa0c586971f1ae7b76ed0ff4b1d96f284adceaa6ca1d85750670b57ea0d8c19947013ec455d037834124e6544f86edc841ea8b545cf36804e822537a3bfc7853c21a7f050bd09f9906868812805bc00e3c2849159aeb81a107fe1abd85f4272b75e9107aab015f1057d5a052e2a6bce7ba6f136cec0356be6afa9ab8f56aa2a80751a4bb989e1994aef9556ee979324033580043c8b75dd9e33e030dbef7fbde98c057b3a8ec9c573021dd3e0fcd6b33d27b3ac2893ab722a82c8367d406f91b03f350051d202d9d558126c744516cff926f0e1f608689171cd6a1c81e49e3bd4526cdbd4e944df41550a9dc4a3dad4254b7146883326b81a2eae5da3288c56f2de2b7ae251e58cb52895b8a5cd09518324c7ca275eb6e5e4861746ad68be3291452745d825c8220bd233f1c74bb09e01e7fd8d13585beb8b4d9faca89b9462828e65baf3a8d3211106f2154b5de3a80cbc04b669312112ea0dd215de1fb2e168643946a6f4cada62efc184199d21b55a2d0e8eb7142e38c618babc65cc462379082be08e1a8c3244b04e2c9b7c5098c8e860d9a96b4a2de985e66329f9011df63eb4b93f21d2d14530fad0809a6b0aab1419bc2a629c8dde255db64fdfaae9616623658e697512ea76a592920ba406079971c032f13279127dba13a5bdcae549053e082c284b4e1088b04bf3e33e59f59803c0cd181f591ec604f635a7c71ab0fe27e40b82a10f95d2e954ce7101eabd1740816353e886ac85435e15574cbac2c3fb8c798e4c20a8df3fb2c8e11ee518c736a78f99438755fcc424244c511613ccaaff183acff9b07a13af0bd2b0b238e9a4158a825aaaa8807b424c04e093d4867382fcc6eeb8f18af0162deb5cb83ac785762157f0992bce844701703d4ce708b1a9dc189f6b43242a9ae192d47880aed5ef10e08601b31a6711067a73648fb45cabfa6aa2e9d76d2d141c5fb1ce71b0f9811ff53c7123746027c6d45ba6a75f84112e4501bf3be55bee599635e05a1e42456138eeaaeff8666b9f5eb82b1d2b651e57a2dbd3e98f17f768fe84063ea09c885e11ab5edb4aa58761fd41052fc1b24384e127373c919c8a6a7a88fa910ddec13b95961467a1698a491e5266960c96cba599990068b66a76189897497791eeda1b33b8e0a1370b10cc690df33ba19f84b3bfff3e5667f782ffdef59dcd25de0879925febec105f82c31213d4bcca5cb629374b06036fdaccc49c97c5dd07876c17297c102dcab82fd358533af38a59dca6d378b42151a67c180cb5ca544610ed31a76b22be7ce33ba6f1099c4f0d2ae70006daf2f29b072d81510c7ef73a0d5c0f0d8e1c8e4d180d78a3c094ed7e37eb2cbdaccd0d4769e46e80899853671477ca0578d4d68ff9189dc106bc586f6a31573d3a8608e4d4646cb2201be02e2bbb25538301eab4da651c1d971ac359b4605c67c61adc9b42a30f11babcd4c57a165378c79b630304eca4365e81b7dbf7da0d2922f73925f86c3f9663a1c227c9c56dc6788152d6a57deac1c895f948c8a5d427d4a6dd8cf7cb5747a1de4c6b45c5ca64249b72adbd2510d153cc5904ed90c62c1d5cb137d1c7ee6f986ac9cfc1b6f0ef2a8e38f7d7de61e6654b4170815866f900b57962a4f2f47ca42822c43f5d9be58f22a4040ddba5c53a76457e73738f0ab49a5ddad6704a3661c814789a533aa87066c38feb59d00587447c857029c4a69c248bee4d6ba6adc4c2d727a545514d7b41693c6c9cabda1b63c5b1c7c6763346b3c08464734b6e1ccd3d2e7bf600dd582e3fb0bf1d8b99e99cb6d9a87506df2ef4243d650d1470cdffcd9a06e1a8b59c71999ecd7a1f52733076e596f2d8e21812829be9ed9fc96ee2b0caa080c3cb82dd40de51d932ce5dce12844913035517517aac1d82a69a0134e6b35200da29f2b0f0eaa9e52f62b524d04ed5cecde385e530bd9cfa2c7f306f0edc1bffd329cc014d3f78ac21197407b791bc82829607f6f599b7f0dac788bccd943b375755c7d54ac04e8090ae44c141eff5daddb43e8787bc0327d60bc4432e9421ed0a4c3d56d2e0c566edfe2ef3a2432dce1fef302323e0404ccc7b73dee6b4e0f7d5854e02941906ae5d9731b1b6fff7623217c26de3258b1a57682f94c516602246f72db766670ce8bbcae992257ebbfcb0000a62cf0419eebb98de1f1df7131d3aa7ec33bc2ffcd36b40f7f41653af3ddd6b6ab53b26ea875349301956bb0ed9de214e0576567e0feac23fce2492131d79eeec0fdf476d090848184400210cbe7bbed3f8918368858f5f8eedaab92fad42b4067bb15dab587fcd01113e3a6a7229fc10b42afeac8c5b5f93ac5c3ea33ce34fbd091fc0ff07bdfaf3a9938f07ec2a8911ed6be80d7453a74b3cea2d4be81f20e527c6ea30d8940dabd9f304df3ff0095aa7a709138092a4c7700b3a4d0ea7b6713ac8e09b00a0865edb4b5487db4d09eb85ab9285004c09201df73fdfdf51faa03f603b22929b0b37c56fe26fb51af238fb0061093fcda1da3707b1defb20a60fd3be41311f4ef8e7fdb80de5e9c0e02066d879f5780df73967e9958d364b003e03d63d9687e420ddc190b52141e1710a4dd2de22e218fdddadd47a00311e30cf17f052c1ef5d0e41cb5ef968bef5341d74c38f45a231aabeb41f93e3bec43693c4c7f207295a25b55a3bf2d9769c886ce1e9b13d560441573f6f1bf6208bd84ad19ee0651f67787bf23d39db2e8db19fa9363dc5c0a5d991dc53662e6ccaaf351ba519a8df8511628046b7e7bf394cc3e952adcdbced1b8f357414d99f779a7d922a4d8a3e257b053f339e03a61ee4c4c1027496e27643512d0bd7ca967f4da3f3a13226286867e5781c73a979c8cd51102180a9d5e999f8dae026882fbf224f1896ceb660e8a82ec35420b89d2192af6182d3730005f02e6d59f833457214b17d2c1408f5ee3f4bbcaf294549c98e714e3d63e6f90cad5da68e9cf8245d42ff3adfa308f644fa6b77e8b0112d89d0370c8c40bd791a0aeb6f24e525e8559d8cf2d741ace90ee6877981e988c2e599c0fdae703da7259d06095fb498f4cff9107d69c476c2bf6ca779b544be307f09012ef963c88651940f7e6febd5b940e4e571bdd8d93f1e2f0e5f4433e185979a72251c972a43a48a2525febfe3f1c2a1dbe1a1e5b60d19d0202e977b64da59cedd691623db94a76a3bdb5f53aa826d05bc9a41cd0196b43a8324ccdc5c3057b4cd481f2b9e9ca1e5c31bd9eea3ae9b7089ba9f8097397d713916ef5462fa728330b9964db953851e720913ad2d1a094d2208e40c30aacc880fe1bf4c68a5f44ca92854f3a378f41aaa3c96685b5b42b5c2dce65eae094289bb2ba31e564928e49725e2d0e1ea5abf8bcc652956357064487ac1ed5a0f922b3acbd096633b10ad6ca2504638c9fa4cdf2342e0efcb8acfbf5d3ff29adb20543b0156e1adc3aee5edbcd48de8d3f15a39f591f04bf943fb63d7ec7c5d4be8e9fd62d82b66649b9102a354f0cf174541bc3c534126887134294d1ba00516bdf69809a7328afdcbd98d5aa1f4094f77ced21e799ce8d8e2aa9d904b54b18bc90c9cde616f5591d1c5398affc7917b1c9c1c3884ccd92c33674b668bf5a4ca76c910f91ca30f5806529edb4b2e6124b440a22ef8d2516bad65416b9603808ec5c8ffc41842d0e1f1a6f663cc5f710c8c444e6a8e53611a934d4a7080da09e67232d794f5e768f8a813d2f74997230d5425258dbe18ab8fb669d3a4ba62341465d59056bfdad7a8cb608d6c066456743faea2557c1a3192c7569cbd6c7b42618581aa0df3e5e4806698419d76b75af2ed574bb8e41beb45e944b7afae72454cc5e5a83b68fa2a026ddc2a292f89bfc32fe47565d0f8cdad92c4874f72b0febab4848433241a3cf42d6c8c6fab3e4b1f77169d3168d7df91c4c0188ba965ee52fd55db3062c38446efbc91a28f795a52acbc62132d0695345d24447399bb9e744bd1431e7d491a7eb2c2ed17db57f5a874b19661d0cc38efbdd4f3e7ba6edc837a3210e789391e330c2c47143dd51998e2d15cd09c9f2708468fa912b9f36b48a67a583b430379b250eaf9ddcac04ac283071a9e9e3608d6fc00e17637d81c2b20cc47a7fed9473ec9c78eadc78315a5bd2171349756cac4e79c23e80d2d5fb758deac4501d39234fbd4afd0808b6e0ba05c6aacb999dc6276225221cf8213f93e2682efca96a31b6fcf2a5bbc84a675cf3b0006d2b4533e0c4c9a18cd549a499e9d4b965c39f0b669202f0e9967a9ee18e4459d5a55541e8b38a8fdd581aa03ef48d0ba9591c87260d6ac836b216c3785f23a897b415b8593b7febc3b529bed275658a6f874dbbbd00921066ef72feef12be215d87886c4aa93525dfc52e7b77939afe5f7aaae8c517ffc69128889cdbe89c45d0eb3dae6ec3414ecdef62b90b3e337e40c29903a9556c30d0a5e007bf32c2d9827c9d89b4b5041cf228d1b996994cad9ed2bbdd3547c9491ca2b9a950063082a7449a0829d2b5e6220b0cf7051992eb109d764ed4ad0664a566bf449f9fb8df6db5b3cf67563902b7330c076df238a43b6500e54afb6295eac6c75dd40d7e807b9ad50986ce7e9fe29ba0c1a5e2a09058f87250f43f20d86b21f0ef726e4850d9a9a32e8ef41032389f2f1baf99873dc336832eb2836b5ba528186b2f045f053bd088a6090b2411c680ae96481251617c802b7b6c1ff9989efab8f85c3a7df66f8ef63344509ea7437a642aa97be80dd88897b871d21b8ca99ca9bbeecdee7a0c4277ee2a4d68d64a0c45e1699ffc1e8cb03f2733c8b4d3df6bac2a8e9a7d13dd0ab3c1b2d0d944a144968f142ada2fb0f55471f862d74c1e5b400fae19ebd6e2ba778cc52b2f7135947c63d27e99055d88dfd3c28798a015a97138bf84aaa9547db563af77b8175a58a57252e6686ae4b13ccfd74a75dabe89d459ee62dfe8b25ac42d1e5055989707c8c7e1a437c1140a061d63a07c634241bb2b538d678655b411177208e81851af4101d9d70f2135c257886caa12f56f3af030daa964103184007e7f2f4b10373abf8b424c0fb4884e59b5cdc41b77e38f6641e79e28784b2d5d8eca22ed8ce382442558693552eedb08c19be541a98f475dbd2d6400284565000a9854925ba8de95e1f70d72c301e2e45660abf7a5922690a6c92a1e719f9102197ef8a482460a0401346d6020ab29a1302ace04e122ade2483e4e42f8f37ce25c68812106bb37bb2f5bbbe76feb479f0d1529ede23b564d8196b38ec531447d3877310291750cec84d9d40a2338fd63ca1bbc0c90c4458dc763fe2b93b53c98c72f4fd3ea1a35bc98dba84cc4387eff7559fd9f856dfc29d8aaa6d1d874dd577036f41df97fe5bc7ccb9376dc70b8d819a987121949e0cb09d3949831e856adf199d05a611cd8d78a493e006988d55ea1d59497af59788fe80485e15877a4128f6c3e010ce2a6d0119b3e7438226bf32256613f9a1d62f6c9162b54a8f00df7a67fa890cff77da84c4b8e72df91528f706898ed44d22bd2723f934aa91f41a902885b5d2c155740c76d9581bbef60d60240b233c1f838f072d334d2da63de5197caf0f0e300b1e8e58d93de11b1062fa4ff52bf1600a18d72077496051e65c5f31155106857361d00bec4096c60a00bc7620d21e42d1810660436208c56c0baf883a1fa690d82b694ac5b64875cba91b4bb454f486cdb8e51dc1a69d99291cd358260c8cf26235b0f9de4e4481226eb681a4130a891bd538d115f10c111f49235b61ab5f974b13efefa78865ea98b8ea9fbf86a3d4fafb4f633c7d5afa57df9a50f0ee2db67ea3b393b3b3cf7f3fc5b735b1d676fcdf1df4a8ca77f11076532973d29c9a6ffcc332e6ffe177f9e21f5fab6ecad71964b1e7f48c54adde2f29183d6339e63c697d26a95de6cf8b317bda65a1bbf4ffecc7363b59f67ed6fad36dd411c3a75b17e9375ba8546c42914f56215a750a6372ae43e945291af470cddde22dd6607d2de043a35d3e91492941290544eca758313d290919a25a421235eab14e30fed5d6aaf4886ee94946e1b7ed060ad5292b457f4dc98eb8f143be895ba7f10d65f3c241d2531c18a60ec83f8194f2196568f307e0aad106cb484df86a791d2cc93a8689cb90f92a33ec986a7d1fd8a53ca7d102cde89ab384b243eae43f2bc5d0b8407025a5a5a5cc4cf99dede8ff8db9c5a5a5a5c727da8221eacaf062d422b645222b546ff3e946dfa6a8862a956e938a34dfd84ce058c9ec2aabb9ffa935142fda770ce2ab585f356f379666cab4c7f3215e86e578e78d4842f6863f1eb756dcc07ddaf1ce20b561bc66266f8625262a54f3309c32a9db62127fc7a613cc721a2fb4b0e81cf81102994bde4db702ece174358ecf619e7f0474314ddf76f25ae36f45aa56129fef54a5dfc3a24a4352ba4091aee4783117f7901fa00ed806f862eba3bea6be62de70c34e8ee98988cbf46451fe7639f4c25ac529a675b878e3e1986746ee6e263598dd2aa1d92e172863ff3eb35e4975f2e899132b0be18a6f0c5f0811836d02d9339947f6d5e9b31867299cc6baa928334ef38645b8dde70705b486db819c7f8ef1cc317dd95a8020a4f38e008f7a16cec430a75ff63b7304aac4fe21293f68ab712ff15476bc37f6d3937c3176339a437e3bfb1719695c6a2e62d46dec2a8d8e7299225b6b1c19feded6f0c9763792bc31d4bdddd4d749fde2f970c098de30c33b6b7589eb14bcb25b04a9f62b1a879fb20288edd624f491afebd2fd6c7d7d2dc9d34a4b4af3f1be78d46cd5bae13f750b47677d021d31b1d63f89fd6dd41f99f429a312e16356fddfd43a4b1dcfd72497703f978595aa5b17b6da5b14a6fd72a99b7d8e779c3bfcd1abb75f7cfdf66ed6e9feeeee1d9d1a934e39ca76b71f9ebfd32dfaf387bff29b46377c7905db14c957437ce8e1b1bb0bbf1a4232ca4a40d29699df237bd6891f03f90fc4d3fcee6679b34d6214fa11592af1d27be0f45eb105ce6999b9672f8645451bda5d038cea19c560da984544039ba553e3374ab6c5431dddd437f367c9a450554b3a850a25954bc4009cda2c246b3a8006a161544ba5b4777f774778c8d86a1bbbdbb866e19ddeddd4debafc692c617c41fd211c76a955e9b2f97bc180b1f0ac8457aa399c175c8cfe80df1ac6c226f748e435154ac4f92b0fc4df747b14ea36fc2e548deb249ac25ed99aaa5b90c3faa28570ad52a15476bf479d24ceb51ad4e5b1855c424d60ff11329ddf9e350ad526cc72118fe72c933d52795c6af75da86709f44deaed17d1aae96c4cf94bb5ffaf0e33b3d14fdf099c4277aa8fbf899f2630c2b52de8ceb910bea161a69a2bbedfd203c37f317291415dd66cfe4ed739edda3db5b4077dbfa6351a1868c89df84efd3fcb53b9d72349a6d52b514df2782c5f9b954c27f84bf36317e24f2161aa9475f7f2c207f65fca2e7d2b272747777abf4f75de183c2f749b1d95bfebad94bf396bfeed3709d37c73e29ba1b37af68cb14f741f998b4bfac0de7adfc18cfd88bf88235b730cae61646d9e4e04cd78d28dacc7ac2d99ccd7470c2998333c59f9afbb15b18753f93539cb15cf87addc2a8ee66e96f061633a274b7bff2b4e1bce5af4a6bf45aaf3416ab33ecee5b7f33765a5a5a5c404c6dd969b6a5a5c585c6a606e7fa506f83432bd6543a6f369682200ead6863f3c264ceb69924f888df6cc6e5504b4b8b4b771ff527438bee06438a2bbd392a37f39f799d619e24a662ae93de09e55fade34a67ae76681ce71028c3d92af3bf96f63397e1780e8e65fe779235e37c31d7f937c7d4e6e4e05338678e9d30cd8f7dfadfc7ddb7e5b71571f096a1d3ac3bf65cffce253a4ea87a94bf09e7ea44b2d60953fcf579b0d5a891b54ee28be518d6a39c38ff8eb01fc33a441c95c4ff302cb1e3dcc48dd3c791e2fc1df7d5a7d72ab5e192748b31cdba58a7b730f56aebd7fc3ee9e267f2c370d2ac674c477f5beeee1839889fefccd81fe775dab2fb5f7b735ba638274b17eb7bee5167e88ff35aa5b5bca4dfcfb4d9dd45dd9d14dd3da3bb7de08201f197b80f7f5bf64afd6fb3fa743c6dee0f4573383d771faae8451bbed8dd20e86ea8fe5e2ad0dd36f4f7d2eaee95fe5e206063135e6c831f8a667aa3977cc175378ffe5e9aba1b143f874fe2bf98ba3f6943ec95fab5397c28f7e934ebb65cab3f3639850f158b3dcdc7bd5eb3d70b874f447cb19656e8697e915cff969b4b13d7799dbe76a76d085f7046471b1b1e1f9d9cefd9b1c9993c3a37379336fbcf71e9e8d06a3ecf9bf1acc4363897f6297c5b689369edf522a90dc7f28fe324f3b52459e29abfcd4b7bbd5c8068bf96766bf4e6b6fa00207004fc6358cb587eecb97842ba3bd65fcb0d3acf1a859a5f8e13ca655f6d92cca757eb329a19725e5c2d5faf6a692695642efe38ceb09bb1e0beae860c5191b45a3f827e384d94486732ed85bc5ed956a34c5729d35e88bb7b452284478934f635f1634fe1bcd96d619eb729b8a0052b1038f91144fb51003f803cd7d2869e83d71f63d956a337268e36354513ad22a2b2838c45fff1c36966ea8f607831d28d07d1d0adcac10bfda918e9176df55c266fdfdd38f4a7c2a3db5f2fdad07f9c4d5b38ffd6d2c5bf9febfb6b9cf57eaed40004c0f11adaf8d87ffcf06cabd1b7815f133f88ee3ed2dfcad6f8822fda6af37ff3cc06574b73f8e368431b974b878633d3c971f1f8dce4d07278fe47a7e7469ce18e4ee8baa1ddcc8943bba1f57c6813665c5e9b1f9c8e86604bb02107f22891fa4bfc914e1a9edd3dcedbb5492f5aa45aa579de2795428aa7d0e7711afd8de65ade1839bbf6dea21f4bd1227d4da45748fc8a53a2597b97b038dfe8f5fa6bc358b6e27c1a2d192367621050f844827c4aa0f089b8724a554c5b5a3d9a772ee17927192367589c6fe4ca29aba599624bc63e5f4bde682c477db5e16924cecff8856219ffcc86a7d1bc73c986a72d26ceb7253d857614cacdf199eecf6e658d3d85769cc57273fc1bcb29974aa72d8cdd17e7db62e42c7f937894bf49a4b1fc38632a2407c34f33bdd696cbd98d82eefea13f6f87eec61714475ace6e4017e8aab1b44a6b7e9c21b6c971f984ff33137f7e5c3c61f83835371b82b3d296692d7733a6b5bf36b6766708dadc661073b4f99bb5d9885f6b14e3e9d956b7e5b914243e49d29bf1eb86270e68650636faeba674a623895d96a7ec467f5dabbb69f4d785a07b9c50f6f6b7bb6374afc26ee8eb84ba1b477f1deeee1afd75b1ee1f512f567a1f578a2f28da9b4df850348ee98cd71f1dc87fe6e2585a1b769aee6ee9f6c4eeced15fa7c217cc379a4a7777edf5b5f7bed7e9e2d75be6bf5e2dcd33d7bffd48dea8673a6ff65aa539cfdbb5349f9e4a77c7d05f477537abbf66a2bb7176a4dd87b29ecb349ce40cf105c3878ae1e6b5092fb609ed384e9bdc9d7746c7ee5ea554ddede9e084072210010f2d385630050d0962c06469082970a2dd4067093063d14c4c127ca4bc806301b3e0841749231251b4f8c8d04c0047ca0b3a0a00a6484cc44307254f065001161dac740181975e419326b04ead3481062f2f3010c07be23d71f2442573c411318a523dbcb4b724e5799e7baaee974aa55279d775dd53a784c53b7f7961a2a4f31e885071151328cf5739e52bdf21b54aad98f440048c65c553aa98a795cc1226b0169995130153e279b712f3c4a252a55229554aa552a952a9f64ed575de7e81974e79312f2fdd2b999ba13c57f1952a66490ed8932793612e324b98187530961619252aef5864963081adc8ac545454324b52b0a7f68679ced27956b074774477849327542cd1a1f3235432542cd10156d42b28f202c30058523d4c9804c0f39407a0739533a1828993272eb4726af1950780c55d64981ce1422b19260150711799169900b0c83c71a100a8c8ac6452de1e0095aebec0e0e44507173e95fbca3b2096548f4b8e2fd50313e3f94bd70979ae22d3a4855e5e9ce565a545e52d9e2ac649cc102cde792c304e044bcc113a08a5bc25c6c9934e28e547e820a452398c3ba55e626060bcc5c59f74422d4e5e589cc40cd1e25d8b773e848b7b1fcc0e20317ce529860c06729eafc8602097721d5460625ac825c6898b3f71a11657897102e347b8508b7730324d649c08604618618411c6100148798b4c939493272e04e3a91840a41c26668800a4dc45a689ab62f810c329e59eefe0b5c43ce9845a9c25e64927c4e22e314e8ed041a8c59f74422e318a76f8a072180722e52f45881072f1ce8768f198215cbc6ba1969823a858a2036cc53b21188cf8967418b01599270ce43cc7404ee52a4fedf041e59e3b2d79aeb2a4f28eb1945aad54ac980105d6f9cb8b0f283e98681658a752f980e2a3091f4eacae5881076595c5ca8a273e25ab299dafa4a47a5651ba2a60ed4a565ab0acbc98951521788192aa421707504213584b8bb7976a15d84affb474de8a8aaa8aae59e998c0e7f9aa8a5e55d1dede2c3d5aa47a3a6fafdb57de15ad9838800b6eaca0083ba8ac56562cab9655d46a35c347c7117c80a2be950643a5c17822072a53a84489a212250b1e19402b15169528291aef894e254a2a8a2a0acc9bf24411583c99af0b2ebe1b4ef42bc5e2e2dd2f5a1875322c322a322b1925344051d1a2f36e389103161498e7513005a83105f632a3c7a77d52322b2b3e48f83a588a072095566142a5b35061a2bd304ab1b07c5f8e1c1e0b5014996e8aaac614180c58807944909931658b4fc99745162a4e64015332c308308fc5091616161616161b5e7c91f379dd1351409082adc828a12bce92ea21619e121bcc731894043105e6c92849b9d1113e1516cff35858603aef1727bc8be2e29d7720e852dd132b2f4c8c68013362042646c98b141729b0979718c05ea6646124c4e351e960462ad0333cdd13de6a05c5091696186f45c55351716f46a55fb2905284cf731edecc8b142d609d1b49f5bc4c798941771578a101acf3eea50b58d7ada08a94cc4b165dd3793c7410e1ebdae50b1da8ac40e68b159706c020095fc02009454882141b291c375ebc26de8c124b4b8dee05967bbe038def53d921f57d346ac428f95e60a1e1a918452b1b365636563656366260be26df13311e6c15d3b0af89c7e321b1e14a3e26602927602923de4cea6bd204caf7446a081f1707e86084f079de3529b22103d3244abfe09deaa1d1a203041fcb53fef232e5a58b55674366b5b221b36425a6b778e9624a131b9ef216254bde4cca916881e3860c0f6fe685982f46c9474386f532c586bb4cf9628b2e3c1e2a783c60783c9ecc4a87944fe6f1789e7252b4e5a8ae894a05c946d72a192b7fb9e13860e86ee0f01bee84c36fdc701c381c870c131c5f13d8937e79a91e1c9e92e14a58603362ccf0183770f80d1b4c783c369ed838800d281ecf0e4fa92c5143a58baf89c7c3f2947f4e3c1e1b9ef2efc97700249680a55ea674014bad1cc74a66870f371c873bad9c090ebfe194c36ff80d1926373c87b71263c3bb183cd0e003246a44e99a942b7999f23589f99c7833295fc57c4f0ee0cd78303b525dcc0ecf71780ebf11b3c3071c9ec37778324e383cc70e271d7ec375f88d291f8e181d61acb0582bef182c1b366cd88851e2f98ad35059a9d0304ac908f16652bec211b3fa224593f21b313de57bf235f1c0a7a4a728f99ae8f08f05872bf97278a774c4348e984ed1dc50d25368d8701afe42cc4a8ca21754649ee8f8c0b772d6ca8adbb06123a6c62a8686dba841c35768f80ea91768b058322c4a564f9078028604cc17d3a486074616992c565432562a21292da2a888782135430cdaf36e002b9fe103506149f50c41858b0fb1f219cea2e23276f8b0f219eeb4f219323b78b883b9b8caebe91a40f85652a458a2b8a45c5c6494a854aa18312e2d2a6f51398bca93b2928597e6a164c79422e491f4665437a2dc90a2ba1145d55ecf47d352a3a7f48b0d2d6cac6072b0aa5851d260b0aa6055c1aa825505ab8a172d72d8d0c28616368460638a8d2e6c7861e30b1b60a47404780a39021421e82e05c591d7eb7b824977e340b9cda538e24abb4f964fb4fa27b083be131040778b197f9e2311bb64b3385a4b71d81f9733bd7756eab6708650f79bfc0709d448327241bd5e4188409e9d109820dd5d0f4f7477471d0d08b8a03ec61f1b9bc4cf93de3cc967aa43c4266c85645c12898fb3622d7a40151e503e40c751d444aa5113e946337e173ddb2407eb93d6c9ff27b7f906bc015d50e2df669283ff97566bcda66add47f255e4a494e4606eac16cb64b894d9ecb93b458a6bd58e532673dcc2349a7d71debfd9b0147250262bed0f43b8070501cd3c89bc7597bc1504f47ad1c66f02ba515b133991fc873f99cbeeee42b85f4b73ff9192371bda2a93c98abc0607afcd78246f33ea1d675a4bc7b94c455a2ea1f5e8a1e80823ff83d48814cd27d2e24380164e00b00024dd3ddd892c60c6866766e28b6ef03e941b81891080229d158189fc0326748870a0802bba1f8a7e0a78d20d8ad8eba1001dddbdb404d9dddd1975773c7445dd1d517f0980f225c04937d4dd09c13c9804e4e85696a0422c718efad8bce55c42d538280e2012849c48ffd749a873283caed8a183a0f8568eb35cf28fa1e83863e4ffd3381640000048e20b6cef07f1e3fcd16d769c290d298d566217bfc897c44e124edce400c095abd3f1dfeef4257cc11007748139353579d2fcab75ba010144dd9d8feeeed5ddfd74773e1d9880146c41e7f56173b53ebb9537f4e9f86db8246336571a13a15e3171a4b6f0d5ddf574773cddcdd2ed74773a5d8e0ba7bbe9409a197cc11c2803cc4cc07ffcf05b862919cdcc938d610ad260674213f3169eb98189b76672ff14cbcd62311063fcb75669518d02f8587b3f24b6d0dd4c29182466600bf3bc2d8977869812d19124614d409e60f1a408ac9656fa7ac98ef87f9383b2b19ca1c31cfc6a5b44ee6fafa5555a7b7f9c5827cdfaf46b6979da70a28f23cdd8ffcefb385f42623272904c7210878e5d7ca8895dac73da519cd9c7b988270ecf3bfebd596cc3e4acd867938e33e3796b91ad3b49cf8fa9cd738f8adf8a9ee938efdfde463e18fadbf2bdf4e654fc9c44263b22fb9b6d92d11616afc5d52307475c435aaf3007df1f574bef1771f0e9cdd7668c7b644ae6fcd893c830c5f7976447acdb6a0be73bcd7a4d6eac36cc33a4f9b193d85f0ebe0d1d53d186498a8894865eafd8383eee4812d98b367cc98ed0dbaab1b5d2f143d15ab5163b8639288e4f692dc73e8f1c2be584c4cf74466df62ed5a890b54eb06c93c41f4bf16d6153b6493649ac439810c10449cb644e19c02407f1f1eb355f2f0208c184041d180417dd313ac8706cfc9abf26f35aa5e33843f128a6822064318fff92fe336f4677e7facbf500c7c4cfcdf13e9495656b6f4c365dfc9f81f8b17bb5ad794b185803dfc1bfd15c1ffba44fd7e21c97b87e7d6c6da1dfd2fee020be9324a78bf6928e030e79e25ec8414cfa4d074af4379a41f0e1a4f44c8263a2fbf1cc766cc201000704facb63aa155f08d7f2172e050506540da07283b97fcac165e2d19339a7dc0791f9db72ff94fba7298b552bbbf3c7d071fe9ae723076de19d8f3d7767a5334c1d873d93ff143e94e8349bfb27dce32bbdddcf8f71ee9f447ced53dbcfbc56e9d34c3da4375b6df85eab34373a39e820d4e73e883892a54d4afa3093eecef6ced4d01d73bdc0779ca1ed1ebb9340e2ca70409c9ab1da4ba1ea57a094c0dfaffdc5a4c4c454e431313139fa5b3283253258126589146d8bb56cfc25feb5c1b171669bbd15fb9638f11f3f7e74774696dc74777777448e80d400092553f163b7308ab42412d207b245139b717fe5a86ff9cb6d5ea92711b3511608abc3d2e80665b16a659882f396f396b306a9341dd6f2666fd9fb41584ca1ee8b95cedbdf2e2829cb98154b1c133f081acefa0a188da0a452dc93938343710edf5bf4a454c4f437dbf4d52639750c88c20451bac55fe2639ccd5cde6ed4f1c479a69446457b1b27bdd969d44b4ba1fe924eb34edaec79baf899463dae4be2f338a3185bd27363c5b73bfda1ec6cfadbb2c5fe77fad18b364988e8e97572b1ced76b762bef5057c4a7040736cffb99e2796f3995b4bab9682d50279d4d77776077e7c61b72667ca319e7fe29f74fb1afdd92c4170c31bdd98498dea2faab6581bfc498dad8c2cfb94cef246d3ccfeb8fe78fa4fb10ff7ead466f7e288aebd35ef4599be2ab4dd1ed2f9c5aebab3df5e339fbeb5194e635fe35fe144e9bbf9488949c72998e34975f6d86bfc447ea36c2d16c1f4de7e9cdb7f937637f7af36bd1bdd03e3496ba5b092f30f83e48c7fa6674326866ba27b16c7cc1d12607ca26bcb82949e9c8692869480f312330247ac58f1511750cb8754c646c6d4a544b9904e92fec86591ebc5529cd6b4973c77d759bd4e1c4f9b6e9b6e6657753429ed9c8cca7bbbfb9859e5d8cee0ea6bb7be95aba3b96ee6ea5bb53e9ee565daabbf3babb2e8ceeee427773a1bbb7d0dd5ae8ee2c743716aed0dd56a08214ba3b0addfd457743c109dded457737a1bb99d0dd4be86e552b01ca65334f310615cb8f65dd46b22d1afc5bd62ab54343b22ada86279038df47c6a3f1056d669ea2a5d523287b7b26518425e11ef749dd1d902152be185077a5b3361d3fdd79b385f5675ecbeba5cdb90f270ff2a8798be568ff6e05ddcd437f429e747717e3c499d6ee1c7d2cf15febe4bcc568e0533867d773e3e749c5bfdda84ce6b84eff9b0ddde6db04e2600eff5e1b2691454da4d7eb8857fca3ffec45ea0e663c8538e84ee3ad5ca6b5bcdd6c38237e14a5790be31e88b54e1567eb531d426ff797b24dc2e2519e34b418c36c92f84cd9267d4862488cba3b7fce7377a3e03b12f51d39728408898eef4817dd31a2e7c6b1a41db1a2bbdd7337bbb53cc4be23ddfec2f7f3dfe69da4d72ac5d971b434d1ebff6ca9bbb15042370e8068d28dc74973fd72c963afffb8fc330f122dbdbf742b6fb4522420cf78fee8f98695de00882fdbdcf4f7831661b5e2acdaea18e9071e9e83dbfb41545bf105f3e3d8fd6ca17870c09b9a5a4d2eaffafb4186112eba06cfd3dd715ff1ed319e4fa11532f324baf5c5a3596ce649f48ac592948e9a988a88c48a788031293935c562af1efd19d1a17b697ca817425aa7af4d929c6f7bdd8acce0662f10914eb20ec1d97184e10b3e912d1ce3498a54ec6e9afe887c20f74f9efba7588efa4a040922b6f697e7e934addc3fcda41ad0dd627f43a2d0ddb1211fc09fc5587e2c563b8be5c2fc612cd7cf95c61e6afe882f88efd37be7cce989ee96d19f53029c8cf89c68e82d7f61eaad4cc5bf43ff349d6c781ae5321d67be1649a47309ff03b97548d3482710929cd709ffd3747a7c5fcc894f73e2ccf59bc4873121f9408398f8658de5b19cb417c36b691867b13e5eba80d338cea1217fcb914e2062fda69102f97136eb901f67f428db24a25c266f45d7dac23a9f29db5ae71016a7904d12715648b649f991b24dc2ff40b24dc243ea51b78a0b41c0502a4229b7581f2b11515a6addc035d70a71eaf657185513840341a004510a62344368875417562c9b98988a20651ab53511dd422343b7d0c85254d1bc256cde12f64dd70ec1f087758e53c8d2ea11799b19ff8d3a3559d1dd2730417797a0bbabc0f3cea5efa84677471d1439785105130c50c0457777385abe5c022159c041777b33fac34601090a60427777525020034b2f9c208b9eeeee7448c01125284844c08aeeee0cd0041a68ee0a6803dded0929d2810b03f430c015ba3b654309294f60801356e4a0bb551cb00030ba1dbcbab04377775b546109265b6000127aba5bf500d94a036c33505284eeee763073618104014b86d0ddb159698be1dc07694af130a2e96e128ca0bb69d65ed2df96b31db1fbfcbb92417727f5c7c314f733b5b93644000f4bdd45f0d01f102d309030babbbd3a532800f1e71f5f3c52b9408f1a952260adda8bcb7fb34d0ee25989dd16d6798554a050b20c43ebb498f8b8543932438a46afc4d5035ae50415a6403313c314ccfd53ac56694c2a8c21ad2aeac175abba97ac08c9a5cdd42df6d68bb68a9f71d9437797dd9d7177df253dc537d445b70debe33e69024353543a56ea3f2362826fc809bc9686a4830d752fe9dc7d281b624a741fe9161a79da6caa427717f52714a4e673584b5b2b2c08fab5b944deaed18f24bd4e389b447b2657ce10eb248a43e2389bf7eb5842d5235cab54acbf24fea5f889f050c6d6e8e9cedbfd3c8b90b76b84a7906cc7a6d00851add23aa17e7ca6cf3337bd68871e4fdbd2382d650aeb2c313e1a67b64ef5858c230c3f15ab15ca8539ea93f0b5e28fb3892911908c5f489c6f142e415a7223ca780a997912353511e1dc0711c5f946363c8d443b542d2582fb21f168884889722ded90cd53a4629d432fce5b238730653c85bc482f7923126d781a8df8823636363c6db57cbd7e48a0bb3d4f2fefb5434e353536f87e6dd679cb346ac34036f8b0daa41a9091c617cce5ad53752f3c086249045abe3f1f241f346830749bd4ddb55ba5244a4d77d70326b8a0bf97159dab9346a3a24f7fa86913ff6b2b77eded6f6e739d767c1b626acba5ffacce1000ad6e4f004f394ff131b5e169f3176d9db22fc749e4f57ab1168eddfd04d3dd4a50b0c30f13f88264f964edd84d001da80ea903e22fe9a2b5e5bf750cf0527d4377e783ee46d29fcf07ba451bc65eb495dedc5df9f504a1f1057337d7b721e7ad878951b514dbf034ba7d3ecadd5c8b7e0c6b23d1dd44fae3a941e30bd6e0253e8cc0905e2f1d9ed2df96ab8f638fc7392e6dd907fe1bcd9efbdbc49f2d947b9da1586de8a4b5e3dbb28f6c8984f533a651ffb9ce3bcb9a635ae3b63aa6ee4284c068d66fa592d72ba4349a71d9c324c53a2f0ff3e6a8a2bf659eb5469bf05f518495af5748c5af43c227712c2a3bd55e2f5c2dc5f573d4bf5e9748f8645394d353f8b6a1ee4e7577add2a9d12de65061b4612c478beec6239df9c5d83fcda5bf9c2bba2dade64cd1dd1f565b63134a0ed9dd38f74ff871ec8df4fda76b71311acde6007d9e393c1ae7b034cef5efdb2afd5c5f747f2e2d3a961b4972bab298798a33ffcd1525083aba9ce8ee773de96abbfba53f576eefbb1b00fdb954dd36b4a1bd35b3d25633b3c11f8e17dd2dc6b21df187e3031c2b669ea2e792acd4c529d4dd33dd0d04ce52e7802e1cf0a6a6e2ec9dd9f0b4e1b4b2dbf0348a92c513a02084ee2174b791fe6e32bee04c7c7ab38e0bbca99989354f6f0ebb7bd63d652d3ecede6b67229d4bff40aaa520c6240d2779a3343a92b7f938cfe5cc5b329975f1f19c39792b673e3d7fedadd8b24d0eca489a6d768bc7e9f8e94892d3efd3b7e1d21fe71f568a3fdb2497c964ee832607f7719f22fd03711c3c07ffa7c5aaa578c67f3849732cffe88386a479261c6718fbe19e7fbc1688dbd8f85f5b6bb495717965329983feb64cd22c966358a9e766dea22169765b673c088a6b72e39d23bdd5f2d6e46a2f6d9cb73b96e138c33996b5bff5ab2569f6298a739cb69967d66d76d93f2d36ce4a69b291a4792c6f8ea9cb647e29b676e636bb0c49b38fb3cc9742c9f8e792bcd9d06d762c1b67b5b2fb7424331dcb9bffd31ce62d24b1a916e5fa4db97e9378278e9c425368a41308c61f8665138891868c54eab54a5d7cc7d4f3ffa58d74424dcf515fa7e31f67a515dde211e6a04c265e1bfef5fb50ef5826f349a15c7ccf8de32cebbce5de967da4b9964ece5b6e66ecf86b6239738f9a37175ffc0fa31e87f4366b4ec4a3bdb9dad0cb10579883f5a168cda7579aeb63ff995b3a71d331f5bfd4bfbcd931f5d1da19f65ca6d706ad83160d4e211dc429a4c312af57f850af9798718cc4b1980e3382aae86e30084a502b48f5234a83b5fab61f4bd67f7c0dd61c280a0ea8493708b4046226a019e7017b6b2688c65be21150f8447c087961e1658157f8d2c9cd7e2693f9db68f386393b922f7aeb435a2393c968d69264e90465bda956a9f8b88c8947f4e7899fa56e104fd6cfc4d52af559818fadbb1bd31e2931e871d2f3430f13abe78507073c50f00cd16055f2970c97fe92c9bce685c17f9ab95afab2282b140b0a3af29613d07da0d7eb3e5962dcd79dbf95799c463bb21d169d0fe8cc747254eab42350ac3683e2b561387d12c9a1400e92eb0a2e20b05c4f5c372c1c2dc04996365727a633cfdbfddc18ce4bdeec13c4d9fa443d3fa6b33b3d57a7380b6b0ebbcd5308870038b73b37cb4d245026f3d70bc7c19b2fdae9064a93df88372a9b28dd4d238a778a75665c536dc5ff361b1274b2b9692a638162a53827f0896e10fce1e2681d8bf36d31284bc6a2e62d890fab268a0691868cd4b2268348434658353ed478d7b08098d592d2b97ed87aa2bb59ada8a62d23ddcd6add7483ac16cbeb15c37dcd2106363c8d8280eadf2cd2ebe584b3b5093804010701e0d083834723051a0ad0dc6872b068522028cb8f634ef34f33bf5e32a7d919c7b9d1fc4c96bfa92ac9665c96ebdfb7d523994c969bf96cdaea24dfc5a020f1c85b4dde32b2e1991494e42d2320b2493cb2f9662fedffd258333af4c0d88e9ecb96e77286c3d686eb5145370863f518afa550d3f6334c6f3d6ab8410a99cb2076d60d509a7503c9bae106a4b981c55a1b5600d2d83ce34d36900dd2e07f1a9e381b6e680dca612c1e5df0906276770ecde2413a8f1daccd3c3e9b1abae8b66e6dae8104dd357f492111f775b476f67a39fd5f5a0cf7955583901abc41272525a621f0c3d910191a315160c598208c81f11fb05c0907721c581e030d5268d8000d4d34c898e1083348608624ac1966bac19b1091dbea16573a2a794d9d38fc742dce6332b196e3ccb8246292c5705f63d5be5e55c9b1d7b883a2acce3026f31af71713587a541d670df84d4e33bd612e67b7b276670d78ad4cd6ea71c52a93210b960c0e60c96000197cc01ac65bb681a0bfc05c27ce4b9bb3e1e7077c9a498ef8db6298410c0760c5c0c30e2dec08c10e280dca1c531ad98ce3ec78ade38ec0dc48da4b92d36964329ac1ff0b44e6f7f38e1d9c0003126080d260a5e3146793d780b925c3d373630e6754114b4716744ca123081d3c72e0208710399a72d490e3051c2dc0f184e305d60d14dc60d25d833b98f1a5b8c77d7c9de6cb9b67dc6d6e6592e6f138693378da683e8e3326abf3476b6b40100cf37bab85ef43d919f9976cd1279f797472f0672d1ad99737cbbc3ed4fd8ffd389bfe97e6fafeb68cabc533c4f4f67f691f84bdb4d83f8dd231160b32028ab242a5bd41e251abc9a5e5add72b08287c2231fcd942d9c8d138a90618a093d4867b34ab46160d8a49359ec8fd538da56e9b710f283ae63ec80b1e7801891792582fccd0d88245230a8b0665d1b8e96e39cde2fc1894e1dc3f35b19260b168ac1c581eebd300eb1b00ebc3990184190360cd18c29a5143cb5ee20b7fa6b7c88cc86b642d99cc5b329a9278449d64fec2e27ca61991d780e247b92da43358a4b1dc3f0579bd9c600ee2277395ba8c0667c86b409a8c9de6c7d1de89cb19273a72fab3f069b4bf78c6699c363c6d96568f5832a090f32fd932965832bcbbbbc26280d13d7130560c29dddd558915e34983219e369186b46c62c510028a306038c13cd1b55bb2608658302cdd0dcaaebd76a8e8592f5bb05e9800731f2428f74fe34867f7975eb461d007010a9f880e4f89bf2643b35ebcbb592e5e7477a6b767b904e1d262b5dc320c71ee9f6625b6962957c471c001071cc03108ab250956cb0fabe5068b458a5b116c160b6ce5092b5bacec80b50202d6ca05582b43582b2d2c952f1afc9b8b3898bb0f454151463334a324c3f11a90e6a128ae8ea9ecf5b2344662d98ccb689a669464feb219c76b64b13a4399d3a2644d32996c4826a369f21c7c08f72a597c134ba5493748ceb739eee139d8ec8b34a9dcd0602c9566ad9220a3c1995192790df84f13e9f1b42d3d9e36b208cf3c451ce2798b5e2fa720f1c829268e96a5028346bdc43e72e2274172df8a253e82c4dfe6c45265d1dd479038f8642d8ca58280b5e1a2acd038ce30c80988a624443caaacd411ac9465a562ac144c83373737ad1b1ccf619ca10c8ade178b780d4833e33d617948ba3b48b33c16abbba2c1fb8e6bc09c2d675c65d95a26564759dd0fdd9da3591d8cd5bd58dd0dabb9c06a2cb09a0241405e032844e45f95bc06f41a90066746c96b8280bc063016f39f81b97fb2624923c37d95cdb89873ae6fcb252c97a40d4fa32027a0f08944bd5893dcb892b872dc874f79240c2b9a67c765740b8d28bd5e3e65d0511740d0855817589220f1161764c0050770414848f3bcd95cceca85d5166ab00527792c69be5e3b9bf14c6936d70adb024c0b5e34388e33b4657ab540012d0801f1b469e1862c6ca19320c9e5c4d1ec09b200812c3490feb0b004168c6081058c289d048987d35b792c33a660e0c0e8810a9d0489131db58e20016f69dde69bc513376db02b58e00a4a5bb0c21456f8c10a2af067b599bd647da8fbb9ce7a1fcad61cc941f173b8b4f76b758ab9b43693e2fc3b03e6c62a40008bf5ab70a40a3a508109546022091217a9b74aeb62eedaf2b5de127dd62a9d930a45a8b0432bd7875141c614a62441d23a820424e7cd4d53809a82638a338581b95a9bf878daa4a0032970400a46a4a06a45bd586534de128fbc25c350d3568a3e64e33887643ec5281c210a516614a2a2e0499078c6363c8204c4b8744cdae9c5174f741224ad49fa1124208dec161a11eb37c93ed3ec4c9df81fc8173d7c6103052040c149771224ad2348c06ac969f3c5b96b674b8aa0a004059b2768d1dd4990b444ea479080b6c6fe696699db329da23fa1c9dfae15e9249f60e4ce27e8e0842eba3b37716e6b0b4f9cf8d709517a38e109e80423272fbee8ee9abf9f1752745399ccbd30824b2ca43f2f724dc802acf3e6d6e62690ddf771297e137280d53aa638cfd18ce39cf18ab34cf8e263c213200dd29011f192514f67eadb9c9860c4614c98691033a1bbc16b69df12a6e8bfa4d312c825e4e806c3493e8573f489c969bbbfe460a6a9559a4b7bade8164fdcd78510fa671f024505058de31c7abd8270aec9f17427a0a0209a19a05b682408c8955366fc62a6372aa44850105094154a420a92408238379f308c8b237041002e2e17316cc1822d48b085f86d71d3dd200e7fb318071fd5d2fc636ec413f738645b71c0742c6930f0c579b3f569e6b7654cab7dc799ca6449606e1ce7b53f8a3f93d5f04d462b0c6c796b9c75fa238107ddadd41f128e400214127834f838cf8dd966c74843463c57e79db5bf8e4bcfddaf553bbaad4c4798c1082830021423f818e185292b98e3385be2e37f1a883464645e4b73b1ce293c7c536028c215dd0dfe389b78969b3799d78034a5633a73fdd0de66484346666813371561a9083014a12382143e223c016297790df822a632af01b3b54c484346ae2d8fa3cd5145dded437f43c0620829e8eea6fe86f034842042982204187c427821cc08c214419805018820a8803045773bf5070423403002841ad316de92441a3292abd36b40fc4f73cf4df2f6b906cc4d2407ff69af1ad09553a7fbcfa21ce62f99cc6bc0191c2332190d0d08ce786bc6654436ce9092b9ece3488b38e82f59c62fcabc06acd369969c2192bf6432af019f30bd9e67c55426cb4db2565ab2f496ecf699c6fe693297e117e7cd31fd0702fadfac8c1f406025a705994952862f8863f7499aff4631cd13df70dedc872c6ade783890d344cd1b0f9b253323fdd06d7c898c8820dcd7eea6bb9b460fe9af07adeeae01f716c9bc26e184133d98d2831d249144125d13dddd023a05f4d703285f0f9c7409e8ee9680f901716ac2871ac779c33e1d149f1f4da4532289ae0b1e20d932bdd3069376bed18f16cf9472588fc4f94614e999c4af4376f0c40e9ad8410076207320b77907383b681d2821053f82fc679ea75f4bc3e27c5b1e6b9562505623cb14cb6e5ee99228a3998991cd3b4999bf18ded2fe131d1d3f592b040748a444e94e724c1d07413870e140d5ed18cafd153e89b3942d5ed0dd529ab494212df6982f937d8dbf2cad5207f1ed6dd95aa68ccb698b2aaa411360d4d764b0821e6840051a7c410329edafbf13f7491ee336bbd3e0043470c269403483191c3991ff41ac13bdb9bed76923af759b739d19579bcb19dac2f2e678e646f246718de3cfb452da8bb776c9fb3f4e30ac4ad33159b6c671d22772d065b8da71da649e1b6b73a4f9ad14ffbe5eb970ded0de5e74f0413a83dd268f9ec3bf54f44b33f5bfd571daa683e374d81251128c8907eaa0789568b472e63007f1cfc87933cef69225aeef3657fba2638aa9adde926199e34c6d2e93613c2a89b630cf1ba648d95a269bbf46bb14248909c892ef8a1add1d23dafc820ba48098b495ce4be481138ca05b88e8cf0533ba5b5caab558acb3da9048ce2b6ce8a829c9e8ef520b6eba053558f045fbcb5bb15c397bbdf090e6800ac49d4f250537b7306a0541e8f617c65854c11562b14c6fb312dfafb8582c3ffe544044b32a90f114249182dca0db9bd76a93121f0a42802f78c313174edafd5c690d696ffda7996768f5033a1e6a1401d28d53a43f2bace8c1016f6ab2b577d60dc104051e0a4ef001217273b4fd2d92010f035d100d3eb663e851197f59f3222d2ee0df793d4f202d2e20cef476b3a1ed5a5aa5f938e984ea007043d7483f2f30c1075f096810f5b91ee9a4655b8b80d0450954dd36aa83cadd920e7777b7bb5bd2dd59b2bbb3dd46474220d103d338d220b84f125fb44324e88204582cf9ec34354dcf4ff3e636bf9d4dc7d9fa84e73339183e8973f93e692fe9323e3b18cb847324ffbacf4e53938b8f5f047b3c0f3e2302ffc942558a67342b7ec62f8e76e6e648429e423b8218bff00deea08df4b7c9fe665b9d469d66c61f8f13888332574e2993d527c969f3bfcd2acbd88efeb3ead886b4d2a88ca6f57a39cdc86cf8efe7a7d1ec0dd33a6de1dfbc56297ea81fe918d6645b8d8e8e71761c674833a6e308037a9a9d34363c8d5e2fa7bf331e52d2669f480e664c4797053901c96ee2fc5beddf2c5642b335b9b46e6b9e6d8db2420ed654ea93885da255ea8f29ae5ab1963627ec608bb4e169e4a04cd6e4e0cc69669ea2cdb80726e76dc61dc413a955b3d973498e93067fcecdbcd219776c9d1c243a0ae91cc792769b49e1934d47d6098bff49a175bad921a26a93c671ae64199061c3d3f6e52d6b94e62fda241d5081022a3840c51454384185013e2a9c7c540841c5122ac8ee26ed8cdb388d0ddeb2c15fa4f5960d33ee2f5baed51f3dc9918f8a231f15b06e7fd1cccc571193a5d5fbf78388555b67480145777fb5441866c5b229f741442a964d53ec4c01d3dd562c9b66cc8aa5154bbc8102745bb1c4325166c5b289095f500363fb2b776d9e629db7fbf781a836a9c88dcb95839b4ce251d48b755ab1c431298c6051c791d26224c6b9b42fdafc388ad2fe92b171a44fb1a73b6f7f61ac0d0ca1591bd842b3a6f8c1d44d43f70d8de387a5811acdd2400ecdd2c010cdd24015cdd2800f9ab581ae591be8d1ac0d0069d60692346b034e9ab50114c0d460e2447f4ca2e88f8908fa63c282fe9810a13f2660f4c7a40bfd0d41e3c60b302edd1f1227fa43c281fe9060d11f122dfa43e2457f48c0e82f094b7f4968f49744477f49685a585668c0f470eaaf47d85f8fb2bf1e42f4d78300dd0df3c20d1b28b18111fdd96089fe6c10457f36584177c3d080a1e58a03f4778513fd5db181feae40417f57cca0bf2bb8e8ef0a29f4778516fac3c2a53f2c64a801f3c528a2bf1848f4172302fdc5c8407f3144d05f8c16f4172307dd3d03070d1b0bc8a2bf05f8a0bf051ca1bf0560a1bf26bcfe9aa8d15f133dfa6b024805071e417f5805fd612cfac339e80f2fa1bb615e808103b93f0e34e98f034af4c7010af4c70115f4c7811f74f70a0646864b8cd58b14fd2951d19f5209fa5372417f4a3de84fe908fd2979d19f1218fd2db5f4b784a3bf2599fe965afd2dfdf4b704a4bf25a7fe967ee86f69497f4b02e86fc900fd2d35f1020dd63783c502b35b40608527b59712dc4d77370b18432b90428d293360a102336872de58905001ef6e97e3e0e40c63e268c919f6024c70644401227ae6107111113f07f8455b670004d60c823476cadfc492811332a08901ad1dd0442744add2274e96fc8562c1c08a2e8802909ff113ceefa2d3934a36a97bf0ce6b69ad96969616973a8572f9d22f1dc67a010f09b0b01059578c50b8a009dd3608e1029deee6b1b3440b0ed0b1264c8460c194ee9e194f24c487010b843ce9c6ac15dc74770d20000f1538e98e52814fb78dee24cd4ac10152b0848e122bbeac138cac13f8b04cc0a49b67c765714eff34472172c838bfe92ecdfbf316edecececececece8e8e8e8e8e8e8e8e8e8e4e4e4e4e4e4e4e4e4e4b85c2e97cbe572b95c3838383838383838383837ae1bd78debc675e3ba71ddb86e5c37ae1bd7cfcfcfcfcfcfcfcfcf8f8f8f8f8f8f8f8f8f8f4f4f4f4f4f4f4f4f4f4f0f0f0f0f0f0f0f0f0f0fcfcecececececececece8e8e8e8e8e8e8e8e8e8e4e4e4e4e4e4e4e4e4e4e8ecbe572b95c2e97cb85838383838383837373737373737373737383f3f3f3f3f3f3f3f3f3f3e3e3e3e3e3e3e3e3e3e3d3d3d3d3d3d3d3d3d3d3c3c3c3c3c3c3c3c3c3c3b3b3b3b3b3b3b3b3b3b3a3a3a3a3a3a3a3a3a3a393939393939393939393e372b95c2e97cbe572e1e0e0e0e0e0e0e0e0dce0fcf8f4f0ece8e4b870906e47fd4314273defdb847488bd0f444ec8263d2512c7d21641c2d9f185863e087b810ce9f686b86fcb4ade00986c1276d2dd9e00ba3d23987253884deaf68ae8f69a747b2511dd138b615191df217c6d5285dd320c6d8551284a349f8ab3899dea077181885f7193c43ece6e6f88af36c963e20591ebfeecb5a499cd7af4b7cc25ed9679e6b67ce9cca757b04e1f4b3c7f364718883fdb8a7dd428d6004cb717d5ede1ba3da86665600bafccdde32c97242902e2a829465444a434945414635262622a8a15091942a4841433024382d57fb24e3e65d0edf351cd8ee2d1575aab4543522ca09b66a6bbb1186f270506a2687cc1dc2c86f124655e2cd759e98dde1053d16f657d7cc313e7d6a85669be760673f031d0f27e0075e36ecf87f7fae96e22f1afbdb7689c7902b9c00e929caf57cc6632774357cec462f8e2ff4da2b3c3a313c511ec88b31d9b7478ca288474c74872be5e3112ffe0ececb82eaec0113e2e9c212c11a0c014dd15c8f17ac548dc6d9b0505129ace7c5c38317c9f1cdfe6ca297d5c3825144b9486babd1b9beef1dea2d08e23ec0916d4709f50a962604dc089989513ab034c004737757b602bc792c01311f8a29b26019d49dae4b257e3b53093d71ebb2dc35eaf0810018125b02050c40153da01483c3b2e2307b4fa024d8ed02cde802c1a904577d4cd11ac0608c117cc955a32d6009a201b9e4640359c9be38b1567c5d1da30f6d716bb95358a014eba7bc905f57a351145133a4de4d8b18029f00529396358f470e5940bc0d134b3a39058740b8d7c9e61b543afd70f5180c584aabb3d1b14f084029050800dc8fd259379c6938412272b0153580990352b01deedd9d01e8f1ada45a4c27088e03e89b584534349021427ba1bccf86bb5123f99cb78da926e62373117919bd84d0c87087ea67184859822fd0351f151428aeefe5be6f93497e15ac6a09e9ede9adc3ff5e0803ae04d4dcd8e32dd5e4cb7e7cd52a28947432bb1a4bb2dad1ee5469284d52abdb929879374e59441b1fb64d4bc0145cddbe3390b72e5944051f3364e270424717f091f40eca63e64b16b63321ba695d25c085f107b3bbabb83a1bbdbbb6183568f90727309ff0f36499c7f716e2efdd3623837498ce7181a80e7ff02c1f59f6c2e400ebee0385f48f86810047be41ff1f850aff1a17e7c757b2cefc317b4e169b3e1697b12d59dafb561b736bf1edf87ea9e2735f005451f2c0244d18d2f5863f7338ddd9ec436e14311e0c90708c0eaeefa65a5370f72b1be6dde3c2b31eb0802b006c02212246614140434ce5c6d50ecf57a7a738cc45fc3b799a7e8ca29938c8c8840133d29b972ca39f2e094a3b5b3181e83688078ab49fd201a1146e3ca29c325b7d8929bc822a224a285150024c4f94ce2d8f441d0a67a44deaed1501179bb46dd5d185d17b8d01d7bbd624d4eb0c00b42e0810e145802173190e20740476ca810c122021796086eb0849f2008af4e6104310c000955d8306300421a100325866b01110841086aa490a3891c36e0410688029003a702fc20824a16b2c062e22c21a2785610527458e8eec0e8aad05da1bbb34215583926f882232b67c48a12022baa8a2ea2766828b070594c294421c7c2dd4069d1dd31b8fba2bb83c21358504d5ab002f8144e7bfd6736e90656b9430d981f8fb328138068e6491413eb7f6d2eb130043a27d41a1de70741712e6d6e0ad1a1fb8b26b25eb9adb4aa553fba5e79a21357ac6ea56774ab1b9d8c8ec5a5eae91413aa19563c1696952ad5758dc303dbebce07950e5daff858f50c95aebbae5bad74bc17afbbae4b79465db7d2795d975209c2ebbc6ec5bbc1d4752add4a7b5d0c30e5a9a8ccb878a7f23c15944ed5759e4b5705af6be955a7f252df754abcaef35886783bbcce5351795d13bcae53f13a1f3a1c5d97f2585cb04aadc8e86e745dd7793f9e0d96541781ceeb54bcaef352b1ce53b9b4e878305d104bb7e291fad1ad789e0a27e5e2791dcd0c9e4e0815ce0b90d492d7aaf62cd0a95233aa95d7a57ab580ee085d7bfd7d5f77dda93aaf7b42c7e2e1d075decaf3525d97d3d9742c1d8ce7799eca50d7c3f3545e7793025f74b0dcd0c5a00183c7806ee5e9e8545daa6b6fc973974ed51e556997750848758c0bbadee1055eaa53e9542b5d8c2ee584e7b1bc529d4faaf33c957b302d33ddaaeb566e543834dc749e8b0a8bd7755db7d275afcef3829052e95a3c550aaa93f1caae4bd5742d2f5ec7b2d2793b783378337832a43ad5dd8c4ec5f3ba18aa2fa5f274789ed7a9742a5ad7c2a37b5979ddaa93d1d3755dd7edf08e565e5abc6ec602b3e2753e742c5d97f23a5567d4c9e0fd7831782fde8ab7ea529dd779ddab93c17bf156545d6a95eaba9e2ed5cd742d1d4cd7753eba193c97d44ac7a2eabcae7b759dca0daf46a7f2ba95aafbf15eba943743974a799eca53753f9ed7c1e0b9742d1d8bcaebbaeed5c9e0b9aca43a158baaebba1eef72e0c00294ca61d5a5ba964e95a36b6979bd1cd931435783aaf33c156fe5bbcfc6cd8b51d7799dd7755d4daf34d94010d17df1c313da7f80a20b293fc46ce1459179040e0c31c294768a0142374181836f9a37682628ac8800538c55f2010a255f2c4d9d1d844031c3148c660c373f50e4b0f9993236d06384183c7a625c3a1e23745ce0918521011eef4283322dcc502393c26b4db9806df90d0e393cd182020d3287a1c171d0e8275e48d2b2d512ed2a0174e745507717b151e00baa3871b161064d565a565a54325878565a5232c038159124c332a3e2c2e2a38299d1a96054622a5c2a468aa5d242000e68a222c35b6951b562c8e0c381262e446044554b46119ece8ed51055a702d482263f8410b104e0a95c544268aed0240946e020a5886ef562d1b043119e0a86540dd9c504ad72e0411315151a969cd898213480140f4510a0898e96223035606a0401c406c9825dacd00428b7f0204bc0b7d2928ab17a52217180225854325464e866e414a1040ab4e0d003272c60304086f7b2da81b5f2a30af247a218f1c26ac60588cb0d2a485438acfc18510228783042034adcf86e9cc4088514e08a262f25ed010288e19ad1118921e56345074f112e2b3f2a2e2c32523d473ca46470016285262e2b47319a588c761401716931826912814ad00487aac5e3a2f27a21868acb4b910c002ed0c485c5688699019aac52dfaa53f141c565e543058720a197b2a5078b918acb8a8e18442d3c54492d386ac470030c377060c92108d30d45b8b0802b3931727a8e965660542b30ac5e299e1618550fdf61a565c54565b30aa2f2d9a15bed48c5ac6edcb4b084a99f221f39585c6eb4f4143194f4a2c332a3e2a1e2a1a2812638c85011ad7258cdd0c24315c30c9c940c4634016dba142d55a34aad849001800252606b0738206505513aa081280a40c41051f889f6a10c00488030426448d392901e8c5e3e393337d85083ab5e80451850f042093a04d92003838e95276a40030c5cc087221d5e32d8807951b9c20a1350210506a2a800144f4460c84c0f1bf0010e706a0a630b549801018527a8206659095082d2827cb8ac48b104949e961f689101295e3f3c386abcb8b0acc4b083042340f2a252520f47408a2490041204a0316507051de08006882842a48727470d150ea44c8148d00f9e9615150f1c61820f50a00905184088094840024a282505f9e801d3692195050f0cd517fe04af0b95123c23aca67440f07ce0f5a08507303bf070e0ddc0b3c2f340aa03de934a49c7a3a3814586550c317478383c1b5d0dd60b2b1a1eabfb5432bc97958bd7c2c2e2adacac3c5597ea3a958e3123b5239582262a0434510951c1c1bb952a14f15da0c96a05b37203108a504181a3c20113c506540ad04425a4e5864b0c3904d0a4e5071510a9195eb20a0c0b10161f1597948f1c2eab193461a9a2490d2445b4f4a42220059822345985d1640503430f9ac06499143469c921c35bf1b1b25931f1c4c68b0e0618a13a40931931336252352c1b961cac308c58e5c0e2b302949a91026289a2490a09c746c545658a26a917151854ad179c54a78251b55c80ac7258f9410506151715253035606ab4e468b9b1f2438a0b46a8c0908a62c42a8b26362851dd3c66830caa17cc0c1596aa4bd5a486544b303854bc95222b3b5692563664742a2fa9950a26954a79a995144baac52506556a65c80a8d194b2eac1595952eb5a2d251d9a83a161da91d45b4d0b0da4013195314c15251ed602132425504236462de617441093fd0020752629045596b5a024a91900e3f7a6c85c2136c7a7c3358568e00a19414860d7690031a94b87182013b38a0083eb8010c5440820e60c006bca189322b816f15a2f0851078b0032c4440440000800408234a493c00a161061c37ac3801111de040008688c91083ad34c828021196dc9a84d1852b5801c9111c37583e80c10b58a002128cc0034a2481440c003f14514a3aba810d5e5003143861140420dc000658a820051788c20003104014bef0010f5490022b3ee0a4c98b4745ac185f70b145116280c5094c10820c2c81801763004072a4c851918f170d2bc60d6c1083083c6009272f02a0872320453e5e3262a89a8b2d6e600319bce00426088107329080249c24e9e1e8878f578f0e0e0d0d96aac3e0620b22c820062fc0e20426f0400612b04412087072845812243ad8805bd082902033640c21062807308091223714408001725154b48491ea82c7859516bc2cb06c40a5012f031e063a978793bae9c0979a540e29bf41831743b7630586948e2e870a4777a3e5051a34529f6a068c8c2f060c303a5e627061b5acb0a8ac782a2aab55aa6b185ce894aef492362fda90d672858640a559f4081a006a44e633ee2377e7b4853ee90d5486d6e896d111d3f80f209ff901e4b62d6c3fe8ceb9591210048f1c19a9c0d02b1f32ab1e36a7fb394fd2e6661babb6059a1a116a59b4e799a7e39a141ef5e2d7a06aa25f4bbbb69a0ebee0b585b7bfd9748b11dd2244f79737777745d8c2864683ee9ad02d41b5eaeeb8e81f1c0ff27bcb10879f1f9f9f9e1f9e9f9d1f9d9f9c1fd70fcecfcdcf8f8f8f4f8f0f8fcf8e8f8e4f8e8fcb07c7e7c6e7a7c7a7a7a787a767a747a727a7c7d583d373d3f3c3e3c3d3c3c3c3b3c3a3c393c3e3e2c1e1b9e1f9d9f1d9e9d9e1d9d9d9d1d9c9d971ede0ecdcecfce8f8e8f4e8f0e8ece8e8e8e4e8b87470746e747e727c727a7278727672747272725c39383937393f2e1f578f8bc7b5e3d271e5b85c2e1cd78deb07c707a7078707670747072707c78583837383f373e373d373c373b373a3739373e3bac1b9b9b989bae9fe8752569b7ca32539e2d29a719919c2877021dc5b2e2d1721dcddbd53427797842d5e88bf7e46f69e68852dc419c864fe7269c964fecab41fdd5d6434ee99f6e38cecc78f1f31f86d619eb7ee8e8b2d58e20298f0568f1e4fe2d8fd128f449a764cbf44126289aa6e1064854c08911056d1340e04e43833ae8110092bbc79aaf4da30ed3bab41777740e8ee7ed0dd6991aa424a099d0f7ac083ee6e07dd9d0ebabb1c74522030ad9855cc3e7fe118be204e8dff00f2eeee06dd9d0dbabb1a74b715cb2626a65b68a405742d1097d7ebf58ae11763f54591da62949c3112e3205710ce910b8ebd5eaf575048c759b9449230a7d78b87e3e9f39630a079cbb92488a6e5ca29677a108f922cad1ecd87a2b5fc4f21cdf89f7684a3e6ed1646899506793297634140e113714101dd4223af570e94f84b6e22111b8cc8cc489243ba00873c316487212cddddb359e32445e38c4927d2e9a61bcc8df4a8594d5a383595a0c9d61487a9cfec38239b72749797298beeae553a521bce614c0f68d02689369c791231adf8a0021f0cc0f281faa083b58405f06f362c5b222c8324cd4b582c5950666d3899e766ad7f9ab764ff34192c6adec46a674a539422a064d4a00c578a24c3592cab7522a518ba671eb2921ec04a024202a31bcc4ddcad89ef79c2c0bf65cd56a3b6f2624a3b0269709e719a459a00921124966e10cb646e73b54243b27146339b79a869a39933a010290de26b673c26563b13a9901023dd58c80e2fe46ba2230ff245102941a4e82051dd418c34ee696ead53901dba3b488ef63af110560f1760f5d0430f99867424a3dec59bad81b979cbd579c44437d8a365b40423278c603c74818718f020458332af196fa11172de642f99cc6b90868c601e7a000246cb4807c2049000b080e4e896d9ece26833ab4803b459453022596ec4719a85e149bb8fb3a3c3405b38494c6400a225d69017b2dbcdce5b9e33d3b014ff96430d18ea612886eea15c5a1b1069c808661222819003849ab0847674cbc0992791ece535e0dbf22d69a4bd22cde232cf58ae8f836c781ad9f0b45d7befc3aa8041b10e8b0261302fbdc276e802c45e613b48d1dd3b2c813bb0e4251dbee80ea9b5a85450eaf6b19e05994268464406000000531100204024188cc60362d980d04bc70d1480026dc270a24c9a49a32c4929648c31841000000000000002323310009ccc1df8388f8ce95cc9ced1e00c421a59ef3b3ac87fd1c1664a8b0b551e97e6b154de76308558c78b81b21cc3591f2193b7d19d21ab51581a20bb78b76b8ac79e080e52e373e312bb9e2614b438eb69e171412471612ab95f6b6fff4ded37549eda44bc55c825d0060f6a1e45f6fa5a8fcedcfce5c3a16b39bb68876ff16def1de1dab527a0cf59f27a3cce88c01240fd77b11f67ea8b6f005247faac5957f1ecb41a1c1209e5e08142b758d9e60a4cce756bdefd87e26b5e771f3031eb9a9053a5fd04647a522505148e6caccb44d3bf78e13dd068cd4ca91237562d165f5134f9b2a76465e13f9c607bf96384a0c641bd251f6d02c9a2eecce869728ccc8ce79f7a1f7fbcfc84eb4ac043637c2650987223a038c5b9f798fe2b8172298f25d09876c4ee51901c5290413a5eeb665b9cd0f48acd260f35fcf27f33ed1e07eb493fe8b1fa3e66bdf4ceca4c9649518dc91929a7e0dd9fcc5e7318c9dcbc16e2d0f8436350fb67cfde8c71bbd762b8e7a763881d188a4c8a55a850fb57273f9a6ee18c17b4dbbd2821ddeedc0866462e35a2dfc73f1c5d616d3549a7e26f1e510d81d541cc0dbcd1b98dea49ba988fa27e43495f2f6da303b533ff0ff0e1f5de1aaf2d39b7706b3027a01e666367bf639f72e6f9d413c3dae6d7d72b341146abed5a3279bdc436635b6681190d5c239e7e2ca5a4c96799371ab27ac44ba6250992584349df3628d62dcd76d1010f7fce09f4293bcce49c4cd226ecab72b0555a77be29bb5bedf4a8d6a95ae8bcbda7a9236cb30fc1b2dcb55a9b71f37296e316d0932419c228e8a2b6eee3cf4fa65ede34f8de854d03353a4265f7aff17bfb6e3cc750163f84d31504858c4d2952d60a73c495b5018255f02cbac7d16195a2ad3c01b0f7d7c8d512163b85d0d8d584c2a2361c06d5e1d8a80ac1565d00d6ea61b052098b9d4268ec6a42e18458fd5278baf161e07aa72d7302a0bdd31165652c3e10b4f3b551a503edbe05b5ebd3802512163d85d4d8e58402a3371d0cd5e1d8a80ac1565d00d6ea61b052098b9d4268ec6a4261511b0e83ea706c548560ab2e006bf53058a984c54e21347635a1b0a80d8741753836aa4262f7c265682dafe1566d1b216d2364c305dbc16b255ba3566d506a28d40963d94b80ba3b3b4381f16bba19a981774395106dd115c0b57a305e751916bb87a2795f0394176b87cb80ba386d5409c1b63a025aa71bd3ab1560bf6b60f4763446d9b236380cad09d7461590dab21390b57b63b64239b63b0e436377d330e926d5142900d5ee51caa942a31a53d1a2c550af4ce4eac340ad7f4dbf78b48f356f3d56ef96ddd48bb5bb414f3ebfdd407547d82d0a2ef0f8be30a9650a31a00028c9431f5eda0575c3b1a8319a297ed3ea6de19f4f1964445098eaef298189c4a3125c2dfaeb9e237eca7b6cf2d07199770dfd1a4665120c54ad235c6d6c8274e47ffe1b1acf47f8154598a7bc8bdaca3545400c37eb5658286effd531cf8f91c5b6b3d44cbe5a004b121cfe614bb2202e8d387740d162422091545db56b6597d1eaff65f55dc61be80a9506f93c5d0e8fa82258fa59bbeb9ca8e2c458d27c0a0d371980384d8dc0b97942e7d5d571bf427f68dbdf7b0b4f67a84c7acd449035a5d40600fac91e37e7b7171a9288f332990c107797f47b788d32dc215caa805b9ea57e25893cc92aa380892a8bf4f450a00be744b80b75f1f2c02112942ed70f32f18e030bbfc1acb3aa5f3a9f883395c6fdbe05cc03ddfc7b65580caf8d8d1bd8603727f554de3bfd93f588b7124262df8d4b1744105e9b8cc75da3d80fbfd15100125ae9ecd436b480281e60a581da58a06c188d0e9c6ddea881401af46cce1a5160682d014304cf18d1ebe1595d29a762d9074e608a7b40d5bf80c47753100e68a72189d55430a0c9447b8d2aa99368f7c90f245cedca76f38a987781ca093f92341ea91e396dc87854f5dfe721183275bd06e8e695a2637fdf120f344edc387eae4120f9c947afd0d0b92dc335d2ee675c4d7dbe2d28d9f3b8d926f6c4d787b883716a950654307c30ff38a087d7ed22a99fbccc4660a6fd648072d88814f42dcdc900b12d51545c4a2d500e1eecb0957ff4d7e993bb678361209cfc7e38176dc397652526423606ca6543d9d6fb0e479be6d384962dd70a7301f849c5a535e6ce90f8484da0d0e9c90591ddebd64d4a41c8d3e8ca1b2c9dcb7171f4c7a3a68290eb2ffba12f52b0c1c75b1763c59c775b697a4ed78e02796617f44bdbdc85f0795a0f8f6bdd048414f5798e6d567c1185c5f726cb00738a9993d29873d174fa41a0155006a3f98c9788f763bbd4b7e134a9abdbbbb8be57cb098192ff15964853104b281f90150e7cb6de9b29b118cf9d3c5c200c7b4a71fd8412464f1300359ca85b3b4795be25007f5bce6ab8712c13fb81bea1b934d09c84377a8477a57053efd4bd407f77e93e2268d8e3ae262253854c8be0b501979acbc9409734c8766b4df8df6fa17b1b31a8bca8cb0414b5099ab2275479ff59e1cbd484f7c6d15ebe6cc6e1252c8ee7bd93c7136873fde58683bd5df9e188afdd55fc05e51dca5358a7df7c031e022b2beae6f757837e5bdad93b71f847bd54db8ae18ebbf84807fb7e5395723d7d587a8f8bdba68654be6263a29abff4d4b6e9ab30425515a99bd8b7c59890e953aec2385994e4289aed3c3249f8586e436d402d5360662782804e5a395d6a61912e887750eca426b15e16422231fbdf44fdc2f1b2549c8fe4da4eed3c641a7a01ce9b2a9bd0e93305f39ff531b5e38ea23b155567731bfd3226fd06760d0e6eefe620eda361c8a590ff50a8ca6b083ebab3150c2a9ca46b1258556ae6c720e2ebc6108dc2704c83282acdb5e467f43cc44e28b95b92784058fb9e2197c22749a6b1d529b40388e9141f520cacecad3fab9cf1870985fa4aa2f72a485fe8f43990fcc0f168ce019b5ab91a71c8b1fbc0d3e0187c73a056f64df34b9da364967fe569fc8693169fe7f8e4fc5aa9a4d3ef2cac8f01d152c0571dc3d9872b558fcdd790a1f22b61351ee63b8cabe00bd5202415bc15bc2a1badefda4c87fcd7a0435901591d993a5d08fad765a68daf1eb21e5a54cbf0f6268c1cadf2beed56aef2b4f5e7fc7574f8c7c1d8f3b89933e4714bc15e09c6b0f52373ae4cdefc0d5aab24051e6bb2e5949acf42d78d313f50e8ddfb406b840b81d16f977dfccf45088d2e0ee4b2cc62fb1c92fc05b241ac7c2e307e29e18fbaacd0943555c32365b6b64153ded6ca4e88ac6437159869398556ed6a5ae11dd1a4cd5d7b132503c4cfb2edb0e1cbfa8f6e9a3e8866da914959fb4561441654d9917903c4daf826662e1d5db500baec674e10bf9ac549260e7c60326a1408efe0fe0b1e2b00ebaea3ab63e3073bb193f4feb897a33a34513e968e1e4cce249bc2a09a8b32203e4526c51aaaf0c5293b52a64e5d4e2b0c570a9b013509cd136ecd4fe09603f50635b5f493b59eb45e27527c72ef40e6a5435a59e7bc623a9cd92613d2ef58f1a023a03b093cbe67e5e85512203886d4da04c3c552dad3d3c696bc16e87eeeb93f49ef34dab2d7ebb5725bbd9b0793ec61b4c9a52b39bb53f82f89c048a3fc5d3459a32bfc2e863a5ac10a703567a7b5fd4a66f5d61f5f7c6231908ac657889d047a4ec097b54af175a1faaa5fbea3cd54041f9784d5caa26659a552e80df2fcd6ccb6ab7444c5ceaba90427aef83eb24972b59acba4cb737ee27213ac8e387195a457d2c8013e5591a9c974bfa3becd4d1cdd2553eb3d443990f2f0e265cc17aec664b2a6fbc0bdc49a7f1aae9c64b5d3e2d857c5a8bf150a0819d0462eddf0d7fb5f1f8b9f85fb8e406374a1e92da0419afd13d38a6ef44264f9ddb5ce84d55281c83eca3b27a63d2983df4a82e4871f200210ad09c3dc7a89907e8b0a70cb61beb7d93d79b01bf1e8fc680b0a60c6503d87f462b5644666b4b6fc7e111d8b309f15be87f02d064cd422c152ea03c402a273cd36af4eccc403901a5ea4a639c6391348bb2982833f35615a36cc2a6ca5d9fcabfd7ead37ac7fead5cd3d2c57ded7ce52346d9e60d487a1d5936a5638218112c0518b7d3ed4ea7a17784c238f9eb6bb8d284a0dea935f023c044568c824390700a2032b4a872e601dec331c1d1fc7b8f2a746a712ad3dbb6095e2ee2160ad7963f90ab489f25ebdbda0a51cf101845695eb0f5526127ff576ad4f59d750218b197d1ac9d89b3d7275de3e18e0b7802c7d5f9759be988a729a2047178024e04cad0a3595419433c514ffa2c46fff6f7c433bcb375408b7c67073f512f5b803fa2a56bb00e474c7765adad22d1762c50ca6c5a0013ebec1415dc49a4ca8f291c32f7111e1384d37fe584fb6cee3c720576a0cf8287172396b3e84a6fba3d008b67a5ee68b2350f985758feb9117d28ef34fe2258c8d761793a43df192110bcef6a361671a6f0b79821be965974e993ce440d1fbbaca5999edecbb28cd81dec8d16f347ad6569e770b5c790db555d2f27b66a8098aa7f9381abbac770b1046404683b80cae6c1b16851dd6c4170bba7747ad91498f5a01d0ab66a607fb2f7ac75e2ef47b9c00a2797a6210d6e11b477686a5853e9b1320138353b94910b5cb2f4d77646bab7894c09e632d7e568ce0c9d6e3fe3347d200a37aff69f181ab1be37efb4095461b66c77e5282696dffe4fd90981dd67ff2c46ac0c03fcfdbb92fd9d4c8316d4cd29a1bf98f476d4f19c5cb0fcdefd1692dc7f96167f404ab9ba3968303f606ae8e68fdd8e73ef6602ab6f98885610656e345a6a1d3307c578ebad8e0869399abde7f559f81f37479a3979da1c96d5846eb4b24f64d8eabbce3fb7844d6301735aec70fd8ed744e683a70c896b13c9c077f7f1ace9082816393f8bbb42701e0d61626e618e09e29864dce1f1510986c4f071faf5952ec924f1bff899b4f6a27a2a2bb070e8a118cce35cf34df0cda7b9ce2fdc85df7f078226870405c338a8915656a7c34d8826969fd12e67714f3ca72db2dcb09f22d32a9c59805ba5a18dbb23e7f82a5a71c4b8dcf7a9dbd6a8c4d72b9c6d56fa93c7cf91f5618fe5a9ffc8448af75c3e4be83c1fd3e4bfa32ec0edf7ec1ea912b29dad53cb5b3fd8dffb859c4697221e0fa8520c9ea73c2e9f3c5fa13e373e87988c2b951227ecf38e5f225fa0da6c0847597666cb4f8f1fec682f7cdd124ae498b75ef025cbf001663aa0c48fa6977876893e92796c95a15cf2b0e2ce566abdcb549bcb65c8a0d639df56f3142901760bc9e421361912a6be80bbfa0f3db4a78604ff44cc4e3f768dba303d966a4710ab80d78ec9b390f675b0e08ff6a6cb8fa2671b77cad452c78ca8af95b78441665cc9b2de96f65f01d66696da7ea6829edc958197cf310d3aa7c437ae3972e082979751efe21c9f16fcbbcafd525a03dd6838758c69ff2d6d7ce0bbdfa66906f0f903fc3597f20d986caf8b9fc5766eadcacf017caaf86b4f685e3a6f4ddf7e592779e3902f7f35b4040bed9ff9af7d38f721da3009eeaed0a9a47de6367bc41a04925d897b9ad11c512263e217aba2557bc0f7f066f9e68f919ba73dbd7230d36356ef61ac6955ff08797ee91d7371293a53ee909bbdaa6a20e25031c79f582c6645113c74c51cc33eab45ddb3265c56c4eb980e52fc1e1175ad4e715eb1763051ce881a33cbac09bfe00e1391829b2fe9caf1a31d420757549c154cf76c0059494f1be805af279d4bba61db5636061f28833ea1b4fe30769bf054b1bb2d769f049b2a9b8e9b35cc04ae790bea9eb4e26a1eeefabe2c12d9159edc5178270f3ea595903f57f72d0e16edfc92523a353d81701bfa752301e889957238f47c4f4e91379b878e5494d98a4c89fbeca04cb1032bbc512025cc0120ea21316e529f158f5e866f5155f4fba9480445c761b2dee2a9841e582678c19636e060b12a5829998681f8e992800936dc65f4758ac115e8bff45617ee899e156c4bf9ee0c7c68581ac29001aa468b9fa0d527587a8ed9941b4d338fae460842b1a0a4332b5b819a00b3a2937600d1e951c9ac214d16ec2fb414ce424c0b934152a702cc71ce10d92c02dcc2f5fc5fd873c1c37ccd97ea9346bfcc078c08f0aee0f77db32cd5eee319cfc8180932343e6f8fd816a4678002fa51861144f3c89c6539fa6717edc2b3efca94fe50f0914f451b0187b780a541435569a1edb1510ae73be6dc83d37300291b0f5e8d73c18b0a339e6984984b01ef3b87ceb34ca58e7a8e2cf81d4535e773c6da8c3639e280129aa92e36bb618b20e411303acac8d6bb5d9745190dd4ad8d6edb38423f30389932948aa530a6bfaac72c3d4f4e8f26a4f906f0286a6415a55c1e05d2dc7ee408af39e41f80ef858f0290cb08869c12a67748979661c10c112226d95e7e78e3b871f55646c82ba33cdaf8f424604f419982dd28f89cdfd34ffba5d866262006f00faf361b95cdb341d5320b695997ce962d04c9d796345da619856fef07a27dba830d888f5173ac75f3a81afa610c3f66b39fab7c655c32df4bfc107ca3d0e9a352583e1919d047756309897671845e7776494cc54500a0eb145f68be268fb5749e89798ca3023cb1de1532b3a66a6d3b90cba0124d0763174862067fab0d8b39c1df6a5288f94ba64d474aaab1b809363c96cb79787b7426a0d88c65e39a9e630512d329021bcd1b7feacb52cc9dc7fe542e0a9d3b9cca21678584688ec13521f9fd23f3166ea55f9f2e0e3bab23406fffaaf7810d556483db1ab8485b8e0dd0a6999ab3031593195177104bb4c608442e02ac6b30a8f494b57ff01f69323fb3fa4dcbcaa7ea8c386dfb35809787284a5ffc66cdfcc7d027e688ae02210c243c23ae94017d48849ac1a2085e8da22f06ca56c25e89d5cab4176089da3dd4ac477d5e6bd01381b3ae0815ffc0ffcb58ef01795b8b0ebcf6ee058bea54d2712e4567109564633c35e5e8230cfd087e86b745f25fe9eec89ea4d145dd82805c9378bc3ac8f362ff6a20b2a1bee0d94e1a5f164ee895af90b0bf8fba500a4b8e8b62612d5ad996ba8d54b7062acddb3e7ddab9114eb513fdb203233e0e5ba11de02cceae9f2a7adcfe391b7c06a06e22b9f54d5ed3fa0231efe200444a82d68d8630646df0a7cb802fa2a14c873c5e14ed3c04a192ca9ea40e81f2415493bd0f1cf027f270c16508e63bc2469022263089b64cdb8b9f8958fc0078bc1cb8011631e974aee99ef81ade18afc0b74e09bac3600f6abdcf11f177a143eb75a2432ea11a2ac87f8170cde15eec0258ea823603875cc66442879332f70beaccc07daeebc9f36b22d19514d3db0da03d99abaf34a39de2262766168ac602c9f5af657369807ca76c17652d17d37f8b583076eb4dd33d306fe2a3a4b1b2b41879d8da929140fe1f9101f75d47c71cd859f8ff1f6b1119a8f30724805eb11f6c59212648cb277a9ff53fc53a2aee05820a55f1d524409c99764401a6244d880e3b889098cde3995b20e971f7d58a08d6fcaeb424edd181df2d53b9f165d625768671c55f1396846415143e12dc77803fc2d7fa4e94ee81c2a7f9a7ddb7227a09e5676624cff11552d68e92aa2c36d8f2a598630b40e82b3283a9df5ec19a17f1257f42d54d879a5da1e41355081d9abe6b5ca240415deac12733fbbce26070190874d960008087d2fa10de14e3caae1dc431d7d1f6497d144ed25a49c9f6b5765cd1e6074e855b5496285d134fac99b1f272ae07e921672fb5fc7b640785e9d7e8dc79f3b7bfc3b08c00cf6fcfd98cc00c33f2a6a0edb3c6f98f31f4f0a80ef6bc64614d54064678345002089fef5e31dceb73b65d32b54f4b65ead03a1f91c85ec946e444532a743438c94b68904de3f6caa8e9f0fae041c38f9fbbd1a8d43afb5d13bb22212ae1920487b09573bbae0102acf8c78320b36a5e51a1ea80ed7d16bfa2ee15eccb95fea218aead26b1bcb18e1e2caf0756ee53e0ec06897c7df243947fb16b44ab598bc31212d3b523cdc4625db1144a496f2e9a853907eb7a8eab6c8d3531dba796f0a8425419def0df9861a1fe42781b84061fdae7e3324d22e2f4adb01592295c7ab4bd37e65df2df37185b9e7385c8b0079719a0cc74454361404484579507660600136fec1937384edf670797ff89b76fffeb8d07be9e1e3a2dcc45c4146e62d8c34902bdd2b2e548e3dab4420e76cc55d0c5a501eafc7f02d0c984cae1dd6707c39cf44b51256a3476b63cf91425c422892f655ed2f4cbc34b279aecc9cbc4636d4d147d284eb3d6f6f2915eeb85c44aea0dd9675e05b20a495694bc83abe0d56ae853a4882a05888d8a4ae6ce85d91781b2872c2ebbf47a45285ea8460aa7a1e06590bc4ab55799e19b316ad31ef29379405b7efb5fb9de3b9757e3d28b0441e2af33e618d59caeef62e8d9c7718e3621f6df3ab4d3842e094aeef657160475fd12cb35a3c32bb28e52f7b850f0c4f764c9de3b799e1452aaa3748dad12cdf3e2f4131961d4b7a031528b561edd905ca2ecaed09ae65809b6978892383623d1850d3b906fd96b54d0a32888b4fb7e853d75adcbe8b5e740f8388ba0f958521b5947a30d162f42740abae6df66b68e0594ac385479126be912cfef96c9f2104d1089111d23d42275bd5534db9d178c24f627443b2a439e25f8ef70bd1c8b600474b98812ce5eb772542d92111b1ea10f9e4635d703f3a32965611ec21b473032ae5efb0cc71007ef1b83a339e7ee67dbb44c8a821770edcd6541d13a29961af3504b8d04d40d98a4cf899e719ca77354176ebc3f2c269e3c87044f4b0458cfc8c023076ddf15fb59e5ad2ffce3fd0758f8624272e39b257f0020534c884e76c1f50c716721c013d1aa0ac4ed651898b9ec1d393aecca0bf8a3ea49752ded59bf31374dae97ee5c7df9f33ef9500ef0e7814b1dccae276889f076e636cdb38ad68ca0179ec803811074aaba1b2c4f357e9338847efc1043a80b27a894980836dd3078ef67e962d922cc3bc44c58c0e184feab1e6ecbbc0a467bae8405183d617d8b4b197bfa4e3a66cd9fcbaa3f3996c31d69c8bba77dd8d8626d24f61f6a2a1fc0e4ff6fe5a781fe4f4e2c5c8302fde532796e11aaa97a7273c9ba13b19a1f71da4d97619cfd4a9eefca5495e4e995b7e492ccb76b9a0d186827bad85c741f108813f113c1ea4d0f35405ff0a8071c13f1754b8b0d1811d353d554e64eccbf2f96ba49d40c24c3de35c56d085999c436569e51ed3b6b6f389663f7aafdf582111723680e48993b2e744b130c3d2e13e76101926f2404bb51ffa9d44e112ce7ca8ccdf4d247f1b377a6d9f223dfa8dff6104bb1f05083077a5ba004384b66f9202715e91434fc3271e8c7370bdcce48959c82d9da5b0d30b3c617cd4dbc865fadf064a546addcfd5b3fe161dfc3423f7bd478b427dedf17e7f7afcae0d986837b5ce3b57280292cf6b338e3c192cd7735d1933ab9741e11258b0c0f01e28d116333d1d18c1ec3f0102b25bcdb5d64df661d02f63e9e16183947b6e803fc426744f4d4b58e5581aa9a0172a3b2b94e9363eacd594b9fede5794fbc7c0de115c9923a4412a975f5cf7379fb534be90bf26c543918225b203b17fa90f4b4e5d6cbd9116ce9fa63af109707d8ea67786a6572a5e69270910e867c81d373db9d568ea86f3bc6571b086cc6935473df2bdf91ae2e26553acaf234e8e86073d8983bde473d377cc4c5dc9c8b409de72d43d60daa11afc0f95ca3f2aed49e4979c7a31a80b63db6d6dbe53b3c5b3869ef56e7df4a22a7d9cc487794d6be6b46bdda3ea59bab18804c5d9cc6161ccc7d048917f8b098a40841217603865f2f356292f5681ef0b243789454d01488afe484d518e405b6a0a8f2909d2631faa08a594c49e43258b431c3644399b3a95ab3235eef12f93c368fc4537900e152daf3b3eecaddffc67cc140492163a2b234ae0c034b254d1dbcabb996681d5f069d2a5cec0bc5da6d34a3a748c0386d0d9273e0e25cffb02017e497603aa518b56b18ae00782150ac46c407b504cc5742db08bb57f0784bfc9cc2f926361d1264b10e858ace5af252863888e6f8e6e04058b130c14e2f158e04f977025724dbd753c9680a315421deb8dc3e94106fe3096f7c43d100f83cfe613e7947eff42764f68bc9f8c76d1fcb2c94fea7f97f01a851ea089fae81c0fdb1c078f3b26a0c636381acf7f740f546fc6c0a1365fe6c6e5550f71474672c3337144867c326298885721cc721cf13819054c0e7fcb634c7132f14c9a932052a38853bb71d6ed838bf8bdd02240d42886412f77bd7ada5a3b4e1ca41f6321a0eb53c5502ac465626784fc598fc2355434a872d0d79762f962f4cbfcbe4c3c8c829a2a75adfccf47e3b62557ae4fdd1e6a4fd94e918287183a34d23225a5fb748348eb1cebde1847a5cb40070008bcae6a238c4f7d558fc3742c99a469e249a6f567fa7cea65323694e4c0280cddaf2df8daa1492ec1e6037a3ecd305d908d504959a0e0079d1686f511179f6dc9d926a52cdeeb60635c4ffbd9ebb874089fdb65613a35fba2c1f80c5e6c9c83017412bab94c66d33df3089ad062b3864bf870885cd39b0fcfd355945988f22807640d177b2904ea089f3dc172e0e5802f5e4b41eed4e945392444d6e180735643211a64c3bfb00142c0c24453be70085f9528ee4458df72dc76225d4c9b12136100cbfb9a1f5cb6b492d6e6aed008f0ddd5fdafeb1fd9a0e77ce08c370483156996c4a04572c917beb8bd004ea974388886dcd850da8ef4cf58b5b21b9801e01cbaba4f97814e642d31f79333f1430aa13c0cff97f3d9f22125f5407f918d3034769e7e21e59b2c9e6b3276b86da59be020701cdaeb4a7ee5dc82213c782923512f0e054922bc727df923ef07ff0b1d760a64a88e49e5082a0c9e2111c5d61b3f0e95a3aa19f2045e2087468ddde3b8ec0de3ed823696748f31ca5e15b6459eff9cd09730670a1fb340f6df9b70b0275b83d085f695001150fea4e36532c23c2faf817458bf10da7f3520fd5640199e8640bd5a10bfc8a079e78ed2662052de2e13a09dc44ae320436e56577d44d82edbe6a62e90c0021703642282fe10531613bb80ded8a189d88b14bf82034d6a35d79f8d3eecc0917e501dddfbe9a9bf329cdb06a219707c9631ea631667a5cee141a80d5de7dd24d3c0aa28af08024ce4c692d04686d4d03e78944256ce08cad7c58f49cbed73a1a3284a96dcde2b0e29818320054878f0fc5ce266fbe4228c265d7b241f72b93ce6977169e3afffe5b11ae7eae0aee3c5797be003fa6855c3f07d107952f85c307dfdb89e39600cb63b4de8c1736ca982d1c6c84d6ffe5f99418106a02bb5f7f878d95249d36716f6c80aba17251755bb30aeee7d61976636933d1e35dca746172fff28d9fa61bbe55939967ac5d719a17892f0963284a1a41030000cd78cd7fcce8a1ab36dd6b6dfb4baaf0cdf5d9b9d247535c1d50d62e6a20e74c5e50337044ef94836b8f6b214895300ae03666db80eb01a12260fda808b32d4d259d547be71923e6a331a0fd34baf34d037e7549347d4efcd37a84e78d4844ca574a52900fca06f394e2270db2f8958bc1dedc99da836510c15dc6e1ecb6ede06c78aad1b4b27269c45726d992f59988698c2f96b002366f9fa34ec53cfa3620a7b06153b01245e3ebbb7963ccddc2b423988f997b470da14c8dc5decf4916fa5960e660d75b6fbc341217f188baf6bb6fdebada72770e6c16fb25b7285b5924a783ad361f1d401376f17d2ca88c552a522ea8f9f004187ac430eb6d5163565cf065f909ba5b2223fdf6a1913e5ce74794dda7aa98b5ada8b94c34b94f31a98f55250463b46af937d1549ff5741e81ce31d61dcb760cc9e45b06066443caae518d329d8a27c9ff6020b76fd2ef79ccdbf8159b81c0316273de6c0c5c5b7b029ae5fd8fb9b226313e50ea9a37e0a5262ca9cb60cc84f3c12f2a282db000297b1301eb6656cdcdbbece2eca7dd989be8ab6057f4c6e325bfc8210c60b497a2d114ac6d492453c4fd9e0c246fe854e6ae7128c0ffe8e3c941bf82ffce7054065cd0a37d9a0deda01e1ebf45097c1c9916636d62fee87f022cfac2a7bf66434264cc1ec08e02c06f360593a390b691aa04beaa4c9a335c611d22a366984bf703e48d553550e7598440868b14facbe285c62d426c7ea9073da7265c2355d4ae021db46d12acdbac932c0429898663adb52fc25360a3a4d292589765a781f6d9c124600d0fe4a9995eeebc86deb371bf38ba27b733a71d4ccdb27c3e083748e89c296d35af0f679c2575e957f9da005cab534c1ae672f287a18bda37103f871fce91dde6eb15a8ee2708c0875b102c63342bd7ed0198484a648689d5222a4150337716c2c39f1d6af87673abd5614e79ee389e3c3a367f9c97c2c6a96b740114263760f0b77b1a43aff905a8816c7a1eb05fac6e585e8abb1e3f47432f883c48f514652fe6f8d18d0b5e1ae07f90ce7b421e6e6a1f57aee02714d6a8ef187f4bc267a0e8383962430e01aa2f6c4b48e86e5789e304cd4b00868c39453b062a4fd711e43059d588f1475facc9708d182e91bb0c94b9c34fbf35931f4561154c7c5131c0d38c69bf270a6094e43c81202bc66129e3b778c7d6b62cbdd39d5e1628ad758f298c2d7f8bffd755ff0b44f5b5e2862e6a8169fa61bf94fcd5ce4983baa78b2b9a8b69f98f8c2e84fb6cc090f66cfb1f1a7ad78306ebfdb329fc0e89a36d921367f2ad139d1361c0ae31bce44c60a8ed31be8753589bd099fd7c33e59bf4b9f6fd8dfa9bdf86d7310be66236140e1f837b387ea7882155b347caf16db01b218c3eff556f5c5ee9dd7cbed2b1d5f72b87d787667a93340bb3f579e9e638ae3af7bd222ab8d268f296e461a70665d0676aeafd760c00ee86c1a1701aeed393b842f0022523239002ab8219b04730ce76814de473cf0ee3e98927d36e031ae141fb74bec187125eb287d953d55767ed20a61b96c1822253ecad5c77c9bf0ec7386b5422107c8d2539e9fbfd084e99b1c202b26c285ce92328db0a8daa49c0961d9de31edc1dae026de93dd94e1bc4d63cdd1a036bac958b4146f992cbf9a18196a74439c8fca3d98ee68e73290d60f9a0ec155c59f311a7a4955287f080879e5d0b0171e1d0e625d8c110f89f07272957351ca80120048b9790a89977d8cfd1f3d0440e414fbc4d722a587aa991f24e4d7402bde93e0124a3723868d99cd1334608b223c8f71f27fe71c4fd6ce9bbf922fa631d2a08a7cc8a846679dfc023ae3902d53a21fa36b47a6721fb070ce1516b690971875d25837e2eb88ae39e464cbf5268b4247d06a2f19be313b50573f8efca8be4872a3302133e80b01c894865905c7eb919dcc38f531f0bb0fce63bc689d993ec5fc0020ea0377f9ab5ba116c60ca3454491863c9da34125f7d987c99fa1b58e0e5a823e3ccacc8350efd99dc24fc28f92acf5327b91068c0d29edf3d59803ea5d8a4c84b9e45fda2b3498792a49503dc0c5616124a6b42067d6234ffc34a1c2c3c5e00ead1e615c5be5197b399390f16353afcb6c73cbea870c482b4ef883d9bdfdc30c169b25b15a447a9b8006930de658767b2fe7323e7374e4fc1d4ed71257114a12dfa24abcadd21b54801239b7d4874acf0ecd9604485f59b41e94faf8bcc9fe4829fc48835b962032d37ef5ad7c411a25d19cd2fbe9a4e833500e21179d6635fb541f59bbb658507511d5d7f19bcda8c30ff465938975bcbe5ac63fbae2fbe9f90e63f99ac93d3c67e939e965170e0990aff6a5078de19c3143830eadf83f3405ad6d1be053e174cf7216d7a1865a218036ec7486310434314450020603635317c3396f2f42a60b6acc25f9f0a27620bd75acc772f4966f98aa016c5721903be0ab44a417455e5385c13004a311d41c7baeb35fe389b45bb2398180e46e0b649935383ae59d04654be016e83f892319c089bff2fc7ccf7ae07db45a3d0ecb649acb4e30dd93e7e1a6eec9c0b132287d287b44664fcffe0fccfd91f88c78e22ecf6a3b8898394a2d8278f4ad5c3130bd8ccaef904a619982237f0a6ae5dfdb3b3eade8e57a81c80d32ebdfae54b2347b4e65627bea658ce135a9e5567e76e81d38ee30528a39a418c2f431a555f68c1f80d8081319fab522955c8862eef978ceeab24ea09c3031c74d116aa669e58c13ddbba1812f8e1895cc298c2e798afc1f39bae23d807c6e5418e2c48f6c53eecd61f777a7f7315771e3712bcd14c5a06656cb3df6630b8732c0d9d1c775837000892627271c482d6930e0fc291743b43d85ccdc5f496e5ea258ea5f3aee05a10b6f81effd6ec66c158d1574e21a3498eb327b42ff8170a9a39f0c7f54a8dda3c6a09eaa245c45242ddada0bdb7b780989db847bc22b3c591a753acc5bc9fafd4dc1bb5a94db69eb53ed7d2c3a59afc76fceae4963c496f63f1093bdec3d43f5f2990eab7046ca2dda6e2efb32943df8b0db2ef02cad12d262df0d2cc8066c00361933c6c4bc0159b417aaca1cb893060580a99a072231175be3b3103adf3d1c7fa6ff61d4600e57c436dbfbf26a6c90bd754389484fe61e3ffcdc8f2f79eeb1d39b37b367f7d7f02b6c960a14029c86f307b214f6364d195058fffc09715453f5493973df7ccde6ff67995601dfe42d669c3421391a824e0d3617c4ad00fa4373feebc7a683cf13d191e7ffd3fa002beef5e457713f965704342f914ec5d0aa6d7e1457968aad4e06ca58ce6b5dd3624e596ecbe00fa231a246a04c44e44696582e63308ee2512ef6fd53a48b8ffbd23332c8516329e0b50b798c6f27c83d431ba7271300fa353fa9cf27d8812c642a1637941d1613df2efec20a7565a798dd5c438c9d226fdcf00c497d2450f326ecb56803f8dc446d246b10f0a99a25d53db96032f8d2391969e90363f4226a937a4e3cdbe89d8d67964ffac82c424810a2234e1e37ecb62797e2697655eaaf63ecdf8bf8b403dfffb4e91deab876c507e01bc21f3a5d4e30a570550ad94c4f91c16a763e988b6f3ad4fc2466ad53e96b3f51a297037dd0037f730a2601d1c538334aac80a31f19af2de9aec7d95caf615e958782f8bfb1d60ff06ed6298e8c0af84977e6d86c08ab60564d813866e54d32d87ae7306b01d3bad7535b6bbbeaeb3827c29302608b45aedf5ad5da2db8abc54fc96b4ce58b846da0bd417060461201b616beb91ad582ace4d456ed30173ec8ecc7a2b00c6503c9a3809fca2e52be36ce404b2a35419863739b25c7700cbe57065b7a622d61fa1f1587ed18a384862a989b12f91d8b000f06cc9fce8e41635377b8178262c5118c9e3b83cd5dc294b718b687aec3bd1ff4f584d5eae450dfecbaa3eaf70793734e49fef5b0ffc1d96ee7f5c3710fee4928ec58785e43f9699977167889da456dd676bf3e20e67ab8bb9a0f51959dc9eee8716d9c798faac183290387fb6462654046074327a976d772dd8f1dcd87025e5f4cd4c12908a099d9570108b63c3c237ee7825e1b5b7b7fd7cf7f928c978899cef815bef4111aa39b310fb71efb19fe9973dd380e7bc0ae013c7c1d9313c128a34f240232080e7db341d5cb74296decb5f278c22309e532b0bfee88720d69164dbf059acbfeb69af7d05a7476d161e1dcfb70f1e1c82598fcb4bc004e13bdf07053b7df4315c261a6ae279e2a5eab5a9b10dc1bde74605d42a085be97dccbbcef19bff052fbdfd9ba2067bcf81d284b4f6df71d5c31f5827fa334f36366c2f7fe0f9e82e72bce1b18a7ad8a68c8a22a676fd49426ae9b5a55b80ff547757bf27730ac5ddc63b36e4b37c09bd135bbca9a287c4e51bd32935d63c98263d712103f99e60fafc9f73d8981ea29f962d978da87e1ff345d8c508414f96cd5d024ca5f3613a0853df64a5abf761fa0380a260c0c851e2e3d4bbc7464ce6819b2061549e383c584714409cfe4cba4bf0544effff1a3fdbdbf2cdaa50e8ed6343eee7e8bafa8a5dd2c1a202ba1a49d2644691798d70c22a6c3a1b7790aeb606d7aa388e3f74fc9b3e3b581269b728325e3534dabd3a8f3e9f69aaa03118f1b3547beef2651f4eea1f126162a7e9ac7fde15b1b8fd86faa91b3625e2c9f5b84e99e0f97c4c7c9f9a0918dac8a3f39f04b8babac7c2b23370cb1138c2f18c44e9a6c7965fc509239c70ef989f1b96a2f063adedcbe7a48a08b0d09aa2a07d8700af07f62d02b7deeca6130ca72be5d2f74fe61b85bb29998c3eb3f1eade936f77d4eb790b5ba4fa0e9088510ff384f19b0e40e9d0601e60fb33fbb656878a207732ed1a586775650c9271f3dfe20d49e973616e27c84063f3d10abd4cd43927f1bdb5fb19a7475ccbfa16b88b38e90d06f52fd92928d4ac1b59e942d128c1c0d5453415f80f620d0427863963b7cf2d7b7fcd869eb7c19a9ffd7025922dfd7edfe87cc45cf56b7c12f244832b1f9e22478064741be81cd8336f2e425cd1a75464d293e2ad5f3538c20d9de3f98a50b0d46c3e04e789bd99a659bdc5507c5783b5e29e4033ab3df2ffbc4cd3b802f224a2c67163b0df9e321a5276af08c8e6e351d0bff5aa92599417804de73853d1a89483fe62c077fb936302a3386050fa55667cd6840554b2c3e80840bceb32dc4f3452bc43ddb0b947bbe60432d23206d03af083b9d58a29c9e1f8a65f7a9d11c9551038bec17ad939478fa75f0e62ec058642d34ef1d8b78b1602b3dee5f2a01197c8cbb4c7d110fe47361fa1d7d9712db773f520d13215f9dc4625f6865e7a1bd3d4713fd32dae261fccc6234293c8f2a778222ad0968d309039900b404a8394654d6fb32d77a16f70ab17608ac0ed1ee91d16736e1d7cdfe15bf8bd58f69264c8d0d15f38473bb3969e2900fcaa5456eb6527972a573805753f5c1fa820066fdb0ffafc5dd1b3bed1992ead5df50fca33e9306a90e3e92ceba17d01d016fef4e3f24de60e949b4a2b22b4bb05a99114adb2b86133aa07e09357815f8403add06d552792be914328e3926060944b78295d7b552f35a5cf8cca4411f95d09c0480c051bf04e41e624a569173320fe65c474b06e661b17a010e6bc39800df00d91c77133478219dcd9ada3f7656579749501fa56f9729d9f2c98fb5c01b620aec4d5ffbd812960ccff3479a6b80487b368472fdc8e5ebee2b5d3299170858e7d6afc1abf48245f76d93fd07032ee1c731974ffb11d6409f8a53885c0f70d9b1700ec2120038376fd903a3f6ffa3652eb090ce906e89426563c0ee095f36eb9a3fe8520dd18af1791410088d3c801b5b49c1fc3e9bd57f06999d44b8da96f55e553311bc5d2f30c44174032576b7a351e13c0e5b4355f40052bc386800d4db329f13384df2a0ef3792df5136d684707846168bb3ee0a212cad05af66ca26b7f39b65066338d0de59d8c3014e4e0cac86538d7328f40fd02ff28fb129e3d72758413ab78d57f75f074030a7c04e1cdb921e3e8618afef1fec37540617a7a222d16c3e0ab575ed30c0df2b54faef5afc7c270ac54b9f1d559a58ec2804be0bf12fd939232d9f84145e17f30efe2fd2d0a279387f7a26b96a53446e3d856e71014702c2253c53b969cac217584cdc36455f6da3ec77ab17566a564a58851bb61609805564fada662d2ca12f5508f1aaa348044ebd6897c999527c37fc77d5e85f660655a547b0d32fed6ef69f3f3804d304b129dcdb253726a6286c5954761db17f9b0778c3d4248a8a1834cfe8f59b42e4467cba83a8b6ef36083047ed01589ecc08cc48b0c0f2ba49229561b42b47968d23cd4f40d6097c8fb1dc0ac606b67033d544ac22c1eb0df7b020b2f01225151f66b95a7e16560cf3a6aaaf9494decc2d034016fa71831667aa3a4dc1528f8da03892234b63f5385d511aa9ed235b9880f1ef7fb148c8ac4d504ee59410421c827d28b7d47b0aef2114016b5154cbb44c42ac5d765b82234f88c34fd69b77a790c3a76e0a653219756db637af69d31fb7071c00a2fe1167b7b15ebb656d014c74c853010ddedc2f5cf88eb06630ad32e7e454b76dc95c84c62b9a4df2a3a1bc412026f9438f052a3f82bb48f41254146f36729bb134adbb59673ea26ca8a2bb6f6154fd2c04217c5f87f9b5562db2115f185a2c55f0431dbbdd47ef993e1d2bf1114dda064769c81461982195c07739f75d91f08b399fe7712f092d2e0fa9859ec42f1b23cabca214cabb7d167a24893f343c3b7cfe647dc13f80db8d07e90ae0a56f6104d127ff73cede364b7036feb58cf0bccdc629c122d674fd549e4dbf69103620be857c9d0a9eaa6f15ad0f20cc8b9c736971f77f0688a46f57f893eb27fc4761d7771f14ec227bcdbcf8a77c050f1c41741653cb3b2e5f0415a1f45b0029dd3441dc50c00a2f11a7296cfb870a00e266ec7f05eea091d1509015d87044243e78f254cfc7e8e4c459f0821cab30bb30bfeebb586537f7633a3d7f2eee8c10b5cb2855f4b3dceabe2b72307e995aa60807a1a1347a2c267fba3958e1c2facff4c02e3ba6618224fc14462e04556067d2c0ab7a6a6c80718325d8cdb0286317d86f0aab950bba649ab0c981e8a5917a3629e6abecab6a54fb29e2ca7a7fa9821c2f500eb18b370877e3e86030cb8ba1b9c4364b6df0c5e05884ecbe72ed0033fefdc9fd4c8e8462c71be43d7bd3782c58bd4fde7146fc9b57ff781a7478727032b9ac4bfe7d4f03fb48fd9723f06a15ff075f434687ef921ec22c3ce14fdd2c334c429297d30a060b5a52e8b628346a10089022d000a5eb4821cd63efb0a5c7c8ae575b5f6ebb21ab096728ecfe476c722e2cc8048788cbef459288f62b122293563def5a48159d8c2c7407a430fd500459858a268d606aae9948a670b2fc69fa5384644b7130d7d29680d85c3abd844d3c6eda7a61b0e7e36ff77981a71ca448d8042dca07269f75c03bd07b553ddc125766658ab5787b812557e9e92ab29fe739a92ce895008550e2eb4d542408286a38953d9e9640d100a4b9fbc7183f8ac69a13b82747671c8cfaa247be7d5eb9a6a8846b00876a30799938a3bbcd001312679a57aade60878209c7facd4d1b7c451aeec681be23ecd6ecbe6d718aae6e7b5b1359cbfefb5fe6611e32b743ee02d4fc83c0b972683b31e4a0a008e840efcf971deb3f6f4d3ee416d043b234e810bc790f06aac0f666091810fa5bad3b5cc26a12f69504cd2b0495a396655e2a0524c0c2a31a0ddca087b475156d334d1571ab9d148096772f9a825a10d12b39c91c345ec4e0e7a1246da6a5b3d7812876629f67d82232d985279b6786a49f440cf9f248d68ed7a3aa450cdaaf5081fe1c85ff6b34182727cad524d0b308ab42b3c82947ca5cb7dd5102c3f3e7d3d564978107acd69464efa2eec06432f1d1696acc018ebe3a998bfcc4c309a3b60519fe021477ab832d1a1ebf60f7115cefc0d5186031d8af3cf842679a6ecda8513ba796940d02e0aff7f0cb467394077b27afbe5cce62f89ee00d17d830707a73b5994f886e8201e6c4833475202825257834cddce7eb8a039bfcb5e7f4de675bafbbc8fe706ad96e799ba926169ab954ee56ffcf0cae727395eb8fa503d3494e06528ac4455bba0677103f9ad9b276f0c5e673060b5b2f57181daafe70363425d05198b27dacc94c05482bb36389a3d655f09fa898838f28a75b21d181175207fce18ccd230ded73c4c22eeb53873530a1bb9b823f52842b176defc63f947e8a9b1509a1bf97f7b43be59b3794de54df328cb8e18ddabab0dac7ae4e356624a5a76462854f852dca533f05c75ee6f1db67278e5fad81d3cf440e09d44e3008a2b1bb8eb17735487a283a092a7f1cd49b2759fd9301fbc22b14e1b5cbec39d42f7c979837ad92d4929ab30ba00972080a971220b43c29b451925939fcb0f800398f09b0685367a26eb35d16f430fe6ff56c1c08a1341ba8bd2a262fb538b2c0f835ca56573b4749a1bfe9c27cfb03e81845ad53b0cdd6e0b03b194496d56d85fd38b235733452646816bd85dd6f0348313712f02e63aa26b551a9156d4699d5f504acdea864159d92fbebcb5f4d394d6510ff7736fe3a0ff8ce839297abf773ae2e97ba5c309a1c590d19ae226eb04840c794a2f526c902e0fb384e68d2b58d3f9807b1ae707d642e8610e44ad5fecdc0c23fc67656032bdf4322e2c7fa60c68b37c741cabfdd1c615fa32da665169b89e6dd1fde7eba3cc1a43ec0dd9b1409aa372607ade22d10622b805290022e1ae103b5620888963058cf5f468506ae082daa5fa2619a5f88e2d463a4a0dd8f72c6bca8418a8e25b294e3177d379ee7bce327fc611bd92223db96a0d16926812f167593eb76ecff9e147cb4c72be9f62bc0d11aa5d6db5328a47debfc394424d68dfca9466807017586aed50d249245b82117204a9101599046aae04f61c41bec15fbb559f5b0531150069c6e0e9087624679164d752e991cc49c7ccf375747e9b5d125aa6092d263f59bb5a172397b6f2bc75c88b031401b584c9b3e9b9588380d8ad863963fe83369e81eac3c51cc8bdaf8bb549afbeb530ff6e30f43937f6f0f88b50484bba0aa45f76333ec9a67c5da54f4b5685ddf70b5587bbc060cfb6dffb8209280ff492bfdd0e4364c7cc17d35d5b0b30918f57ff3eb6063ec67c30b8415f2fc0a4e0a12b59db2e744623fd70133e2036520ad740c46317af99422eb751a345c0a15841c0bd81130b6e9e4f0502c51af180dd92f3def9e4efd809f37149b9dd965386e738187b26b507b4c75d8f0c89f6bbe6b63e597be1c6b545d9f699ac8239b9fa66f4316d23996a0f90bdf40acf282ebb50a5cd64a3fb09dcfe33d67ae1abd55ff0acd44061c9faf154c6dfb9a08a12525d60bd5e99d9729e29275af398a1c380e4b37153cb035260a1588fd287183dd99e5dd694b62ce3b73d6cb5145c9c38af4a2062015df40d41c931d18984b5e179d5e05b0ab7b792c8ccc56fcf8975d47fdb016865ddadeb73f0a78368525c3b5436dca73e34a16a7cfb08f7e3f9a58682204e2fe12d609edf654790a6d0508d52c8261708a6b20981fbad5688352e1fb941ced7b7752ab989abb8cac3c01ff13d4d85ff08d69c2ab7a265fffe4f68a28584b52c121aba4e438dc9182e957b22b2a01b0d4a515cd7b18524e4d362a19041c080a3f724a89ed099c7fcb4233b1252419eb543f905578caa7ba7109f72fc79fc97746e6b131ffae2fb7edec4e6f2f032a9f13f0ff03ce0fa4c554628e44d324dee0b8133f115ed67b53c91bd9441ac089681d2f82bbd933e1ebf79b7db3467554f501997f55545fad759b6a2e8675b826add5a7f3ebe08c582a5a6c179ea63fc0f1c4e04239e1c9a63a1a8eb8e2d174508e1a646dee15e88bd6c0dd47f8505118ee609d1622453d71ad28a355e90ea0cf77af5522e9d2270ebd2a57b2a9b5ee5129dfb0ed3d35568cb4016ec04a72749e2180cbf8088691c507409521f4b807fb5f668dd05baba3d7d0d55382b6a06529baad0217a20842eb0d3a21b9dd5d553163be4897b537fe1eb5fcc06652152bb868c5882ea69679585126b84cb80a5083a67485e9cd00fef54a1243d37889cd170a2c98d0153134d0ea1ca28991236273d98138e342b11630f5d3e69ab9d273a46477033b56e43fc12d0df810a6aa4388d9ec5180c2efecccc6a7f30baecc879abe77a6aa11662a3601b8be050ebed0c51cb997abeff28ed71b79f326f12d30b3af9e3c567fb20e670c57070da6e23871e6dd433e421f7e48f7011721fa499e2b26746afcd86c2b96955d04cfb7ef1e5c7a808ef7e4441d5cfdea9b8fe4f4d1057cced7bd7677ce7afe55484f596bfb769b1b1b1e8954f4485984aa8f216d4a32180e09edd9b5747c6ee6a0a2b3645488d0628bae1e85992b8b212aeb10ebf57a816881abba12326e20c73442c60f4ad11a87a30ad877ab6c9d49398b33b3f1a1f701f9a072ab80a8e644ac8093db60955c14c8588bb7c9f5531069f45852b80e488ba26865d8b9e81ff273eabbe96550ee19307e029e9916e1eae18e83efdc372ec02e1b068aef2d2db958703b34574f315671fa6fa131cf398e0d86658f63fe473e90264d015bc39c02930020188c4b43f38d50b3153520adc9c3ccc3748e70d85f6266f498737f64c5f260aa4fc96bdb2698a3963806987e7ad9f85e5531aa6784b33bbb9f5690d1229d2b8bfcd2cb6fc05f7038685e7cbfb2f65a425864c5e4c9b185ff2953e121581bfc75337b29018ffcf674f2b70cdac56b6dc59a997987dc97bb5cba804dcabceb50c9f39e56bfece38b96bc93ad1f7cfbb821bbf5d94c107039e3af266745c6378b527d40248391c9d057c9357b9550f5e27ead3329f752256d8eb66c64b5f55b2932ffdb3916fe64976e0680560f9979e8d9ba1cffe5018fd9e1eac0e92973ae2c0f9e65792ebdaf22eb60b6db01902f750d3f3e6db9f2a5b75ec7a7a6c6f19a39f72c66c6f8a7aace25a95b987ff32af68a7b9109fc61b4149c493e22df1b9a23f70bbd4327033eb71d4335cda23daca9ba53249021728a99b0a24e14846d27a52dff5a22c4995d407a15d4c197fc48f1b30e66688c33a2d503893388ccf9f4dee3eaf2f876833adc6517b671065cbdfffe34ae6e6d415796e0141b771128937f9747e57270d8f5f96d334bc0770223e8b4f01723f7b736323a19b958821055c0e45262df0c4e16fcda1205b239a593c31ae30f90b4bb89194241d670b1113b33fd256f9e80f87d47cb1e33613a9a4306894c1da69ccc967a2f31d24322228a9fce73e72610665e4601f743e8cb4f81ef9f12206354d165e072fbd0cefcccb3d02d865568e31c6db298379b83fb714712fb55944faf718356d03f2d07efe18b3cbf67567be0457c8c087898b6f7c48fa0bf4605517833cd33382720daa74ab94b6c916166f5c099f16595d8337fead929439df2eb9ff8ee420f7e8168746cd61f8ab912eaf7ca9f93af1f0db255c790812dd1515cb82c6abd90d9aaca145452279eb32b9697e85a431defcacd75905362b465839f31d01302b346087265a7822105457ac37cd13c6157885a8d06f372642ab9cf3574b8c452b6ffb1d791bfd8c300cbd4182803dcc621790c053b60e7ff39fc4cdeca3bc9fbc0204ac6eb66eb4076bdf52f3998b2d3ee20f198523853cf0c3d11677538003ab1cea678f611c4059b4110b7220a7c12ce416c3b847d3c21a934069beedb0cff584b6c8637b0bbf365e79c578ffc8bfb23b5d279046693d1bd5023c03d561b7ac47e240c42181088463080eefe60ceba0f43fe7a25d95ef98908941a26b71c9ca47c8ba735edc2b81766e083c8cc1a6f3755b325d8cd4b0e887103a45e39f8036ee5029d360a2e8769153a58012f367b3389834009d0d85e19c05604efc3ea927a7e0959055230266de66580f3e4036561993174ab666e476192280667b61b073ef5a4d548ea66ece1f45d7efc9c447e8a664ce7ae8ca4c0c14ba14385d5ff47e8b7535be8086cd20e79650bf1e05d89081f58df3dd9174ce2fb60210a88958318fdd155e458f00c98864b0b4d1b50a6cba8ac50d93e8fc286a5ff34d148c3517adc551a1a54d1c0178f1e1ce569886410e2795b5cf2d918b40779271d899132e8a82ec741fbc4c1a3b60cbe790d7a139681f7b61305f02b52115fa6686e519a5b0f9d09670df21ebdbd30f2beda06e97974fd6375727ef70b2f69823cfd3064d8fb032368007d5994174fb1aa78e1b2ecdf5a5811ade6c8002ad28e2ef66f1be49fcbbc2b4e8e7a3cf64341a36dc6a99e0337f64260a371b1fa4f461931aba48825f5ef6a4369e45b5d058874ebb56710a948b3abcc30b304657d335c19bc8b013dbef494b13e50bdef147cdaf086c66f5f194ef98663e1071a9f2105f3d33a3fa9b5bd172e212edfcd68c27b7f6a49bf79b3ee5d63bfa7a686964d29da62d94a2c072cbe5b9b9d14f3d795f47f013f191d0cf6f64710ce6f4f76f564362c4105247b250d14175e36c0103d702d827902591dda583c5a09bd47b1e82f4767b037a81e4b6dac215f8752143369c22e827a13bc3506f04d5e329a4c4c029227bb0c969bee0d9d4bb27893ff4c276e37209f3005bffcfdfd0117b341149bb3fd852293ae9c029fc2f34ba8b23f65a4053f721da178c9fa4d9301db33c820b1581c9f21427218898c288775f56a9ae30921c62d06852afe14192dcb4d16725ac2f5c93f05277b9dd11f024b1bfc4f9b3f7b72b49981a679173020de8fc462e2c0a1ddcfbcaaf8337ba94238990beed9bc3af1fd2cfbe85e39da20f51f2f93aaf9b1061315781c2792c06abbcc1824c7384fe9dc114a2a7fa27d602bd9330642536cf6abed90eab8097022cfcf777009f759894ae30de848c883dd0c767330a51933423437d4d0d9bad333a11119470dc2b5f75f4092847182387bf677365091d51781c1686887bc5f10a56ebe31b0e8f6710a7304428b7b4b24bc1d74289a12e46524be62b455d021478f41608974dd3ffa98dc70a78afaf766902df03b8672a9a744b030cc5baf48dc3ee8f13c11ddba49142876327f0d1a73cd612b84c9a920a6563b6880e2718f63776afba20e7ec00ef66ed0673025ad705dc8a447ff3bd4314024541e04bc732eca488790124131ccd941550259aca34b86d203027b91d66464c3a11d0bb6fead76c23c17a779680370b96898d830f7ae3316408e969821b6c834c08ef6583f7748bd01a570903d6357bbd46019b02c2c2dad5f84a2005020e0eca237bd5308bad6c5663b35a18cfe173edf1e6e6cd0d2c342be68c7b8f238e0eede3ac21df514681fa98c558b4ddd7d5c7346832d9a18bc2854d9546fc675f2cf08fa3989805353f781748155af3e9c03d4731207294d92507fe6ad10428381293a7ae29767272a24e8f091ad66d21b7a24f3332ad418a43ccf567b8012f5179307fe2fc529072f407eb565f216e535adb18d44b1edf09907aa11463069617639ce300e90be9985c5c529c4b10c56b788a78f416b08028f8464b08c6deb79495a7c2ebf598a9785be9f0e0635eced2cdc5b5f00a67060124393c530bb3c9b6160c395c6751d089f50f2b104db6f366f05f7754fe5c18cc62614dc17fb7edb63c272abbc0f27ea022f1bfc0a156273af92a5be388bfc1b0ed93191b4980aaa4f103837c5723ef42d15d7a64de71621dc0b2880455a22bf2c7c2d4472f6ab200295e64ae15879bcbdb0375f10221c428e1b328dfedb224c7ee6c6ad02865615b8d36ae949064c730dbc82303f3610419c1c4f7e9e6c6bd8a1ce5b384a39e594b4d79e3f31dbb0c20c513a9a3458d0f99aa76456e93da3d00a47783188f8a744da41362dce573783a7a58010baa92594a32779db3ed1d5a3e83b8ef50461d8a3c425ee6d55e32789d0e52ad7f5c2225307d7772ca9067667ef13f0b74d3d62e26404c71dcd37b748a97c4a341b9c3c7ab751536dab0cfaf99f2dbb2ecebce4235e3504f525d075e1ad253e789a502781db6301c200bdb0db819193bbe4e43652abbaa31df29b622c87a4091761a6b00bee7308895bd8788953e38f02f3703db93259ae179b348f32dfd426a1a4274de955c23c451aa8bfad4ddef21e37a028b2dfbe4d562f14ee05b3dc70d2b1f15bab5cbd97bfeee7c3380140e806011bd74c53ab3791eea4488ec382dbaf35da744d70c1ee8d1938d0b38561e258c19b11ad2932de8fd36631a6a8ac1553bc9461eaa914fe5b0d9b2521423d9c41a15004f59148ca90dbb9e31ed3a7463d0b2f5c44b07ab3abbe8f53e8ed028b521703c4f42654b7b9d09b867ddfef374574ffbf9b68a61e189417b0722e937d609a17eb0d59e0c1267124cc86e622d1aeb8a6c1476adc3d5cc060083513c73c042df5ed72de8122cd045733015b7a5afa5538f3f9f149b059cca1f31dbd6bf34c91584521fb47f03d8ee4569989b9046917445d4435b638c1d9a34353cdbff7ec032b983d27cf2a67faeae531ad696a3b81d9b3aa1dc7da339c3e3646e6b257e0596c51c38d7ef40f12f5c7f8df09455df0f697ae56079575e332dbf71a63156a65460aa7ed156e8a13a45c381063b4daa50f699dcf768362534926bf1bb28b7d2714eacc1078946a3a072b41a5464089f9427f251a09448ad4612e36a4e60ca9186330ee5e96abc5f3b5a8418458e74a92e5b466008d37270749661103a4c479071d136f152c78262d5d1857501586c9f662e6d94e889723361cb399d8090871204279487706c813d9dfd1c1321bcc7e250e08006e11dcd9390e67490e1f5bc38031b49bc81a7ebcb0f202912b390666c526e27bed9bee87c62ffe995127587738630187dd39a272f744947e8ebecb2956033d3eca86d02599db87c97ceee04aba3851c6f55dd972e138fcf24e4683b707845786898998f5897c25762f639762ebad138037c4c8dd339ab93d998770ff89e4019795871792178c14e62a55350edee6ac45b89919ee34125eb57c0c350a05b85b00ec651c260e836ef7e9f3a00ba5794ceffc01eb00442956f0811679433cf5e937a6b87668155c5de388182ced0578ec22b1f4d0c57e32c82f898ac9451e5d52bd69271685f6574ebcae20dd0043f895f5a43e3a77a81ed0387430670f368dfd57ba4c38896498e8da20413cae9b50aabfc3a2d0f8a2892b9a752c200249ec6cad44d6226dc21e955a16824b928a7440a682fb7003bb4c28cb0baf3bc6df2df392a9e331f18086d7c87c96bde962a0d668ea1e24259e5d2815708149872f8b901442a06de0613860942a5af508a78877fd0174bb3e0e0603dfeb9bac382970f0ff33dd40265559cd141a70bc1fa382279ec7a16fe1ea56f2e32f0b8266247c4464566e015f41a523fa732341b0f6071425053894aeb136fbd5df217b00b69f4c785c15083733b86e88820dddee5efde37c44f44db39fa0788766d34e5c272c08dc918fad14f6394601e57d30171ceaccdd724781caf6250a94788c5d6d432ae3a01dfa5a67b2b7bc8437fcabc4c83cc496cf23bfddb109356e780fb73719bb27c1c5c3b4b8f10a27890f8c573dd1d764c47454296527a905c1e3856105aed509d54350cdcd71927118ae4687c11de3b2fde872ed950f5db815878713b1f4a0db674f50bfabb9a1325dba7f45e2a9a62c8b2e546d14f29d2cb1ddbfb8a364a143ebdc83509f07e7fc6c46c750b481c8ba21c5ea0310f12cb745532c52c1ccb733f0d4b0ff58c3014a476fc21b10b1fea15501a45611ccd5b05d308b37ef56ad07ab16305ae328d8df11ff8d184f5b4b310e9916716f32cac001e9f4d5413801c286f048b6528acb06e5de9fb8ab2e0d04779c5e9afd1ece08e095bb83ddf185423f575358d2460cc106ad4fb4e9bc47ddd97a9ec912ce02d4e1840d0d2a7b9f4ff3932001aaf09fc6de749fffa5779155bc60677a8d27a4ee2cf826141db2b358a631cff50e4bd84adc57ec57bcc5c027c0277e5e790faabfa5c38f18bc061ad17018a647884b61274c6c3bd7a0ef710f20fa25eadfc667070945880666197c7a948da12806ad8eea530c78807c11985c59b6b40a27b347d95c5569dfe7eee1e0f6399bef954f8b19cd8b5222df9805f828abdfb58e14bbfbae754affafbfaa19347635c9b1090f8e1dfbd3b05e43c5ef7f8de1a0f090a6e4a4ad864dab445cb7fdffd60366b8e6020ec654018459315e598d9b44e88af40e0977721ded26e6362ae875e77a97679973396544823db685766189c9f4052b98811bb55c2b17f8aa9b689b83449f8852a312bef57ba2783ec913f07529d590467b8e2450087c3c04730d2a1716258823ddcd024f01e0fcd2823178029184f91403b4ce4fb1d95b4245559c19cd4260c823c02e2cac6ec880a29c126ba5d07912e70b2d5686b636edc77b033d3e1dbb0d18b6b39cf8ef840d45fe02d6ba07fe8e86b54dedf9be37ecdb90a284b2883b5c35a23f5fc148043065dcbf09f8717b1c2317e6f0b5b2453113e52adfbb922bc02442578c53e8af24355b58dd7950a2f90cbd576e05090d5e4d3186f613d7a9dd03c83534852cae045dcbea5dc8cfe202c06f06f895fba51dfc3498172d0534d058526c64b92d66de87a5238e686359863ad56e6125dacbded0455338c4fb0c52921036a45342fda74d325fe2c0f182323e2f52e979004ae52a6a16595c2712f1ee9021649929acda206de4b25247f5ee95f6434a37074a4cb9ee2f9b7322c05a9445503ffe6f4fffa67e39dc9ff5473e97daf9af331b7b3fdb3aba686fe830679d547c10d178da1931520f34acb1b8ba6c2385a92966c7b389897a774e430e06935b52790500cb426d82bba29c69ac759638d6b104959507046d26d1d0f984a83f752e9f623808d8010088f5c1bf46ce78178c921d29c3e3c573f4690ccd814ba4b49c16a57d0418e8767524ce298a09457cf339915d10903871b080ad4e965330d6383b19b2ffb0e892d553fd99d7a5f87913b3b49641323ca89a8a30a2e80ea75261ad86af7b938de44e91f3e2783b6d2c3ae46f57797825b8be1c7fc36fc61585200adcc937900862ed0b9335faecc1f89258c02d6ea0e0c0aac7216a066e19de199320b8e2cc522a00e0f2b956740353fb391d5c64b8514d1c76caa32a14c0ddf1d9bf263991b0534023f0740eb71ee123a36ff3fe2f93000f225217dd0763d715a86bd3cfc17fc87c83f441b7e112da9d5f1a53f541a3a3d6033c101fb709901625be708b2cfd2e3a68cce9522adac7fa22f927cc856e3097791ce84642b2a6b916291af4664839336c41337e5e67196fed8ce09ea8e4bb8a566ae3247521119886bf02de0295d937802c79675c3120c7cbe2f426ede71645a75471c4772e71e591c5965e4296062fabb18a607a4af4df2a577e0d9972e7e20c2456af18319a31be937b8ed6fe85aa0ef2fea8b6fe83a5f416c4ea56c0776120f3547c3ae923599605d439e83ddedcbca63712e871c87633897bc55fd42ad189177f2d2ea6c4ef10a52e5e5755b3009c0f7059482a0a926c76ee33e79436d93b4cecab9960c77fe8f395373ab45b69854afcd6b68a6a64d17f485e6da23fe99511ebcc0903f4f7d4e9f079cff3bf253a40dc0a89bdd6d13983203584f60370e09edb32aa353b4e396bec5a042fea3529048302c67742e1a398a097e5f68dd620cccc3db9d8c9a42e1dbff6f37a2e9834901b52e4ef8efd4737104d1417af0e640f0a295aa08026a872f33ab77cf0888df48f7fc905ebb301d626a31477ec38c98e655b5fb405d3fc387b20c4ada91868761af8e48213aec4ff83ec989b020f12d56bbbc5202f3a73f6b8d99485bf31e8c781da198caf6761b87f5b27ddedebd9af1a38f230be83170cb149cced79da73f043309fb5dd790f1a25bfc4ac8dd38c0f02dafbcd2a11362a162861d1d4bc0f2171c62e48af7365d840cfcef7b8e1610717fd2ed599db31b3838055b169adf668c1ed3056c5e55e16048cbf9ded4f7b84831b477140345e0715cb02ed9b521b5e4f13e702118f0914a50069dc8c7813f22366df9cc23c28c6ccfb70b166a46d11209f0752c949596e37944363bfaabcc27bf243c09571d3259839471823057aa87c214a05490c9678543ccab3eeda87c7f9c5d486e4340af90d1c38e5972c46aa542dac831b795d47047eccbae44902a23cf686c828ca72b08e2186ec28af41f97da6c0a756c5a5de2991805d557803d00d5aa98a32de1451abad76aac1bd295e6c669f90616967cbe193460df12248434cafc3f67b4d5821b1f2ed444d7d8b666e7dd98c8b9b16a411c31fc4fd2008b815a3168d2a1d1aae4a20bc09dd459222813864e9aee93063e17651551136cf2a49d7b922df88ba72c297f69125e4d68b25a87bff6580515b127db3fd303ff63b9e9488b6776c1967a0182099bfefc30e6e1a9bd1e7620f67c4bf4c914992ccc352d29fc8ad356e33681252a51253ba555fa79c379f82a4a279f915b9541a39ce26323064bb55ca755ea783de8488f3f0125d489e3176174d41ba7687c2cee5ee37733faa7b31186ed0f017d0cf030360184c6f0f22bc30e5f988533e76f2567b76f2210749aa7380c93ca7a705cf0d663e6c22c7f8505612062d271b6cf11a57cab03f0d0617f688be057b1e0175ac7bb31816b4201ed12e8a41508df193561addaac8e321b1382725a3b81bb5cef7edcec423dde2f4fa49df9de6bd3a20c05c698fccb5d593ef959e87d12dea81b6d35ddc6f50b5c66a1edab0ca024255b79264ec6e6be774fc2c3d998d4b2b6d7f4abeeac4896cde5c1eb4c6b78dc13f304307ed90cd17ba676d4f07870221dc723d1aeb3b64944153fea78364283fa01b144221980558f5ac93f81b855a3ae8736de0891a1faffcc141a4935b51cafba8af062155ccb275b22136e0224f4d196e59ec661e6a908aa14b393c06f5edf3a058614ccc148a903e27b636e6b9cd7598df62d478b06abc0c57feb17b88fb7bb257a5580c8e00296eb27c110954240dae9a961cfe2d5591b25e81e1b059330e21183e04d195a529a8dd95d73555dac82275da3f6546e116ffdf4d49f946f46dd225d5a84bc70435697416248afde844617c00c926a10aa08f72e43e2021a9e779b946a910db23e0912f35d61e3b6a5e3e996331da20763fd23ea4e1cfd3b0c975061e0af8f69dc4e58c022e4e23cd400b840feed6110b1ffdb46d2a36a6057ee1a6f1b32bb0d659307a6cbb9fddb27c105f6619d40fbbf788af08bcd16d37671e5787c1f5b33fdf0582f382066ee02bc6e4114d4bc35fd102543e03987773591ea0a36904b213786a12210381b543fa633473fbea445174dc75b88736dd823e371e70d20682907e7d493fc12c80468c63f3dd78faeaa6d0536770cb8dfd0bd392b80e77a9110604c6ed9d364786d730fe6b1c9eeac681f83c0663009e7360e57fa33231fd800723ebc46e1658c8d1c5a4bdb56b536fac702e00dd096773e8305683e0e2b1507cf40d183c6f4bec4afeb6e4857583030c3bd39db068d2d00f7649fc332e22428ba9e5e3af48b794b0d6b53ad824c4a6f511549c683b9062de18eb4998058d8732bc48bf6fd0ffb902daa290c190f89117f27f22f8bac3342fa6b5ae8b0f842c2fb69b8982466d570891e98fa3b297fbfbb168a8f24889b7f7a2f97bb4fe8046afa783b830a4bc9a783b6c9fb93c3bf09591cff5c8c0788d79f50a51f398007f5de9a5ad4e38ae06ee4bcf95e8c138e824d74b4949a05a3180738dd034f4245656828a2d5b17757ae1ed47439eacecb877f7275effe84b47a3eb393b934629e787d3dd03e86aceebf63055a69d58a0823bb2a9f061a127eb467169ea2353764f2026125cdcfafcdd35f47231bb4f434dc3dc9ed47867a5566342dc145c5d79064924c77ead3391336d373c673019c6fdc87dad185cb5b584c0163136e57e6060f9687a13f1e346102e4a2ca8db1c2e61c43db8f57a126f4366ba7e35037d3da17f19d21461b86dc46531c6086db798d86521b88346776408a2e63ed9e0d5ce89dbd050d5423677e3e025a2d39f441d305bcf4898ff56928dc625a1aeb7e1ee156f1d437859f9b78d59672718848343416e9f5c978a79b5a539bc9d73764eec96b7660989ecf8c7f07678d8bdee1031f4fb7a90174f8311a3e92d56ffb738d8f0ed5ba95e5110c114f1bb4c7e0e20fb9f2cbe0e52e4ccbaf864f9d818aa94f146ec018661f93ed1b0fd297abcc8990abc360eea2550d86e4642c972931f1c0a0b71698369c32f74a0803d409a9ed5c2fa262ab52a10854f26865391963a4ac4364177fca6bedbf0897f64b0406705114131a0c3ded606c829ec6b43b94b7a25e027150ea70ef10259a3377a4747e5942688550550cf202a83a3511e54cd8ab4c5061a623db5a229a26b1a2fc1cec2ce8e5be1a163df44afd78ddd672f185902fd4f2a145f93765df1d087ebf86129aed838219ca9d46dc5618bc0f829e40af67526f4be86278be9ca419fdf1de283de1eb141dade7f5c21bf8d1d830014bb5896dcf2833dcb258ef3768ac063329d860176865a28425c0aa2b8a8edc8bf0ce67a0ad80798747fd5f7c3607f767d14bfcebcbd50284fd5ed91e095cf5c67cdc96c3c8ba7fba875c1cc886769f7f71cdf6d7fd621f4c3c39b27d1133b9267a309f76b9fb89a160ba2ab0f1cd9831cacb16952b19501fe80351ded33b147693a26d5c6e27d3f25e651deae52120fec5dfe4841e6870853b8ae3496c8e41eb5f85a3fd2bc275ea6a8894fc14dd48e94f7cfc033f9ca4298ed37546ea0233a704e4402c739bbaa120a8b23cc30b6c00e50fa81e96f624ec6f19f5c76ffe530dec4c675ad346b69c10f907cc8f7db9055df87f6ff7c0868f8b61fa2d9709195fae77b109ced389054ce4dbe85aeebc4f6761a9d8a0a517cebd31bdeb1bdeb6a8bac761f99935ca3b425969750d45508a9ed1102778f151305575fe6499f9216102b41ff295d48febbb4b9304045685cc1bc0b97b895fd7b0461e0d839ff895c0865534e8eeecc178529d9dae3cb2ef087b8d4ae4e5ddfc03df0c428a5c9289448e416f2b5327711f39fd5b1b0551f9374f0b5cdf87fd639cf5c520397bede94fe8e30dba6b9f517e28850fdadad9c52794a60e9800ba265ea728a9c8f1b1ced6f3f25d162c896edd79ebd5f692e551dfaafb8c6f4124d9083791c5baa20b436489dc16000b9fd4b861f53ce4c923f9308dc672f0da6ed0ff419456800c290febd3cc1e2c7c529b9b7134372161bf859514bcc8afe28688a44c359076c1352077fe8377bfb46027ea67059148f09cf2b0388d5393284954cc96bd2f32bbb9bf58d6d81c221d49e722dd7e50fcf1681edf3824ab2ce41e8319a1f589fabe0a71bec4659a5257b2c2dd646af63fc55d3439847eeecc219ae0fb5313b433c7d30d4ea1fd99636cea1d1c23b0ee9651e1e988c4837655aaa6e37064bd229f36d2e2507c247b070acb51a2b6f5fd9586f7f3e103ac0247b9a77798b8bcebf9534a9869bcb3211a51c55440cc4e28538ad1835be73a18d0af898f567a049308f00ef103db4752d7bb8800fc491d6b1655e555151dfdc188dd4c3a344e8588eeaf2983a8a088811a1943a8e1db11d80bb634c4abba6523c7df35c75f83afe01cf8f928f544b4958329b092415b0b1e3b7fad137163cf0a48caf32e82a0862bd0167b2f323da671464d61ebd6f6d0ca639819c46d107331317f1ff0cebbc3e67f34f2927ffd8598fe297ab168011f367d62187e6fca4ada2758a6a7876f66bc4be81bcd8f11e92898c21fea1c7275ece679c1b72b2c8440d9556f79da2756db5f9378f77839dd1119539cf35c8950f3ceec7d6ee043013a82bb1ca91c7a441ff6de1d7e0acbcdf11375bb5ff64c096893337b5e59fb5995595f794e51b7007d1066ecb613de8c7d9244c644c7d48f9e221516f9f22f1e70b4b8656021d9ccf587a89ce83a8f2f973bc46260d10f793546b9439915dd70b854ca2116d8e618fba44c7e34776ab8cdb7e030b1dcefe30325cf2da7f4314b44877f19d902c8e6cadb70ee67e6c349f8d343468c4ab9968a96eee4eef352a4891ff27765efee215d49c5d29e48a0cb237e694b2d1f54f3ad5bfd973e2b0fb3e1f54de67e50348cfa278cfa0fb54fc82190af9d101ae226bd3abb3eaf4475e648c9ffcc612568c3722fb3fb85f0665f822b78ca1baf693cdfe4245ceaa56405785c0f8599233069a1acbf26c2b64c8c285a40de88b151117c05876112b1725335daf06b50b17d1bd312c46fb7c59c5d640760b2d1fa63d66084d42d280c568fb90e42bbd1c74239b9c82f8ee64dd1e00e01592beb63e948f91a10c02e6d20e71caaac91810233e2bdee611781475e70c57669d2763a7f56083f657e4ece0070e2110efbffdf8393d0ae03dc0137d6abe9641f6bfcfe9faaa7e364253fc545646c560a263e172b12a19b709a57e9c01edc09d1a62647bd8cec221a5ddefaa4782cb4a3f9a8940f3ee3e64e6d9c4786634d38af2b5e55d52001a9ee7d4ed63af7afcf6a9ca84fec5dc321e015e1ad62723c5c5fb9f55d4d039f46c36f1d0d37725be147f74b172c3b516999300b0d739c741463f4ede15a5864cf9dadde0cf5f1668fd5e415daf0ad32f4f1513286f1ebda8c461b0cffb1d55ec6c7dc772ccbb2f9e86a1e14bcfd16cbfdc5df2a58f17b33971a19dc1bcc8a3f921953c30a6e651ddcd02e997ad664f264c2a7b37f6d1a1b9a0b9cdc6c7bf07d0416f27a10f9ebc6499710181ff7df72229097155f7378a9ebe76b23b8dfea917f00702b14b2c02f491f6ae5660a4bf46b15051904675cf4bd6b156a40320d9ad3b07c82ab2269c510bb8542a9ce4e3ad2a973f02822fa081699e967b68cab88ca2c8f077e8d5f0efa29ab2184cc921a6113441808dfab4b1725b1f6956c80504dd9a291f945565f3c1393f518f2c260c57fc7eb2c87c6b63d126a60a177c5830a22d3911c18b955f253ac34e8b2ba4e995fd54a0209c261ee48b6f899d17c97fd0535c7abbb596ebccb99e5193690d09cd7e642264fc34190bf529201a12dd2e196dbacb1cbc1fc9e66bd56de55d905bae69a10fa46b075372508d9aa3d808f94497b8c2c94d40e04267a1444e8bd25cbadf502ed21186e676791c3cdc4c4599901851932820b4ec4af101ab51d0da12bfc3e7ab9dbc1ccae679447bc173fac657a78a5a397a2586af34ccb8fc116b744f964cf4530c1f02577d54a5dd9a71a8024cd058c256dff15589cec79e461cba30bd11a4d90ce1c11c5024918d01466d95c378ed0a2d2d16150fd0a8113d49d0776e3b1661cf19b51aa1539bf5e1b0dc943a75638b0a4f06840acb979b5704f4dce69ed59fe06d30d5f8f008901749b3752d4772959f0c4d068ae86ac71f1dc8bd800e500f499a353d57e190df10e091ecf8e45c2546def61284ffc2c8c58c329df43c0f31ea9cab50eb39168edba447289887ca7f8cc47e4a9a85281f2db877c0c7be1127728c366115ccdc5ca61ee69b355944f2714730c3c6bf40a8ad9de6fae5a2bb11a17092d16e9fbc6fb81ba7c6afd8a046aa3e8e0be58897e1b2abccc244d684255b1b5e4e48a745a38bc2937f4133f51cfbe0becd5cf30f4de8617393ab8c36f65900957f30f198396110332a56f9ad8f1ef55fd8c4fa4f69b0a21ffa39dd1e96d9acccaa6bc7147ba8d03ea029316df066d1d794e8b7c33a2e9d733d18b31506bab2cb4c66c7a43fd679305eb100ebae81e8072ef639103b5c6fd287c3c2894d406d0f1fb3351380d5f486ef64e634b7bc45704183e136e2788834eb26560cbd5c63f2d80475410e2662700f2c222ebb87bb6e2fff4923d6982461c57020fbfa365170566d8e6d396eb64eb04ba6cfeae124216811d55f108271ac4a559735b64f4ff30e78b5144798111104b7f1283950d51e4d85852f378632059ba7f5774d4fa9405917bd55ab5a48474dd8bbbc7999f53671b7292547a530694520c40dc4350fe3a40a3034c96e39b5934cd8b780ac836e6dc4160b4f38637c120a3c1275b143ded1859ac5e663256ed102f06c683c2511f5101b95a695a9ebba9ec59e3d8f215171fc0cebeceecac41953c7fbd4e25792a6b0781f80f990846019a74a130d3ec0b9a4790b74a8250295841ec45ec9beb8aa9a7b81fc6104bd2a5fd9cb63f42afd600851e85e08b48d1d1bf9d4aec9084ce371210592245645214d39bb4f9451620494db2ef0230935cc530c0ab06244cda46da1e4df30f4c155b306aa06ace5785d5f4d81169423b348788cf4bec5778cd669cc2f399e1877c93886df34716a8145621c883ab8b751b5d4916f1658e7b39cac128e148476f4af78497dd57be76ecf65e0015463e1e73c47c0045373225248fa571afa8cfb12c3819602f8aa5d63cb5e7ff8641419d1ff60f84de1b1a64cd87ef39863e8de5718a0a80281f227dd28cb1842841c165f1dd7ef25dfac307dfa178ae04c1f369c9354a652efee3d5c112f4e1a20b1c302f88a5bc75ea0a9052c3e29e041ed16d7f9801b22f85ce81ccb235e62bbcf55757ec9a9d32d62a7a39beff38fce1a247814146d8b77e0ac29195d84d14b33b3252e2b5dd514b9f0a096db12b4b16ab4011d68b41b2daeff6cd8fc56d38d858d38aba0f2fbb1b2eb0ea3a8854cd498a8c46b1a35e516dbb01b99443ab2e5d79839491e2c961228f701ebd5d7fb9e3ac8df52163a74f63586b06c3cba7ae58395f37e4a091543e7b3b1f42eee9fe21ca599fdc1c57c3a41c99554b7a52f74fdf2081d409aa368b5838a2bc24ce884af447abfc02665e2c48f28f60e2ce41e2098b341dfb78763012fcf0bbd6671d83a8d8577b48917d0b5dc98d5d09a22358a103ad0cd671f2e897e007abd98ea69dc9ba24b059ee23ad67f07d7abeab3ee2a5471dd486d60fd2110db8a46b49ca0662e70a054fde64749cf87cdca56b105395bdb2849662e801304b912e9c2a1172f4b614c20189616c59111e32cd377609f343f50b363959967ea89e28cf67c84fa73cccb817a0b4908ee02e516003b98c83f88e22e973fc95820d9ec8c5628ae7bb8ae3deb795f1f9075347c9f22394d104c6e2c7e9341bf7d0e977d3ca75c044817479c1c75e1f26a1f99e22e0225888af1e3ad7622b55a5180395d3b09da942e4df800907d72714a051d923400b6d7b449cf1e8361139e7484ce32e54e2258bc2715187157d4651cf4a63ff0fc2238d82ef3746dfc2b7d569c8a7de959b9e2762d5418de52b111746890800876a173ca2836af4bebe2dbd07a5ed888af485906d5f6a7490b3a7e5544efc90dcc20ca09433cee5f42f7742b2ec6eb4c6a572fc0eeddb7a669cbaaf2a7410758dcad6c65332cadc95d28a4325eb07b931f130533ba7033cbadb43f1e7d748f4afb7c8f1024daa39501cec519b1af712c3e20560757eaeec9f34956901a55679601a01d8aa25b1dbff1a2f335aa6e546432cb20af00c03bf0b72ca1045f24b88fa13c6ad18a38ef936d7e8df2cbf30c2a2adc26896be4a3548104b4685513a362c3e81b9824e076e80e047a1f5b80bbccd2f37a1462dbe9ea00612a30fd507029c35b1bc1205b1a22018e328219c0ca7577665dcc6ae80b92b45bcceef42aaf4c20b5e360c031c9c21f581d98cf768ae7252d35f8f4a5796982285e2876ef2e5a313eb5057934893529d4fee9a098852eddaa5b278a734163ad83d579eb579c9948884797f6417462df8511fa1ba74d1471ddbbcbab9dd0cdceaf488317036764543926084f5f5e9770e629fca70b1a899a52da8359abeaeeeb2d208f04b9c733934ebcc1138033eb685db8ff1d96ed06afb5291331544bbf760f92a7d913ae291311f361f53a819cea3ae2edec8f8323282cc271ff7c8a65fb88d9bec8e600436733d6667388866a6ec7717ec07501be69e576cc9df7f30b39f169d1167b03a139a80bc6c1ab480b31361b935b618ff0c7eee8856cf675c88b0ecfe6339e07b3b25f4284179933c67c6999a1a96400bb7f6c8c59a5b30e2e72eadccf6fc4217b7ae4535abfc35068d3222dca5f5e003f6d347801ae7e94eef307719994143b2925ab0e6e1c95311b31f3daccf483eb1628aae6844cb6177308b6a1abe3c547aeaf0ca5911e495cd82e0c7df79277040b2237e98e3e38b689722117a30f27c718b6a171a6a01d99a6fe436980c1aae104b0a6af4653340c7833a223054be833ac2e541fce5a390acb2b4eeab830bcddc57547b58bb227166a095f5001228c0768e6b53f343de6e4cb0f7966426d0fc7fe9be82ce3c4f96dfd2093ab68d8508fe22641a0bc001cc001b24dafc7ba30496742a2c137cc33499430628291fc4b539f58e4b3fd6a10538061198aca920aa1eaf9e2d2848c9da9282f05853e6b4251bd6db9e69260abeb892bc88db41c075458cc574084fafc381a2e258cc5141879c1e09af545f4c26e8de628d3951f2deeadb34c8eae42533275cbac76c31547da0972c98582e15a2dcc9b8ac95715b125e9adb761a798be5bd9e6970a1dcd0a4faab89bc48adf34c306e2be28b9b22ae6612bdeec52f1e9ba02a91d2ac3292397d818df08ebfb8d58c666fd3d37eaecad7ccaba8b84870c484a5b8de7a62028b76caaffe1dbd303c3b6b04d0479569565d1078e79f452f419b2f7cb359730b43fb8f1d928094196b79945d96b3d84768d874dc7db0025d5cac2ecd848ed36ea98be83b2425a52fbe64cb42daca694ce91c909640c2d4281a8217a4805a47ac0b9ad594079497661a584eba90fd2ca2082414c9708ac3a6c688b01d3e1f16953c5e720779634b00052c08f45d23a739912b154358ae15c4ed8ad950b543830eef422da7d134aee8e5d120ff04afbf29c1f4f2c2a1ed9ba9383e973936cb61dd8e3b18dd8c151967739043ba0f3b06409254d1bf0a90f732e0428802c40e93ad9a87e07ba0b9d0705d27742024d184d7bc3de74cab69c8fd831fb4c5134525e32bd7036d07403acc29a85f35767e6585808fc3b03ae6b85d00e3010c89b4207ffffffffffff0fab1841a3a91b791394524a321a97c52858a2efe6769249a6241341eecc08adad7f9346026710a20ed10d6869d151d34b3122af303aedd462b34ce7776130f8e4d15d69f9d5ff61609cad456bad2175de24fec2d17974361976e329f385c2a546d1f4d6d6928943472f9ecf99c3633595509d213a78b1c71cfb3689066d5ded42f17937b54a4b5d36d24562e7558f143a967c542edcf056264747e5a51045072ed0f77d2fe7ea3d4af516263db3cd32238476922d8c1e852cdd51e928d6ae053245eab55ba54a7c6e4565bdc075d0229b6f21377656faebbf8e59ec617a93ce6ab6d359599ce44de9cbcd2b1d45ac2316bc768e9767a65ad70f8b3b99f2fc79a59222ce1ce878c54146cf23b654ec6b76c5a3da2756e966967cad58c383d6319cea86e83491216d04d1a10a1dac5046c55f472553a995a12ccb8a1021409625c84a1b40e0002203206f045931e245569024e9419fe19e46fbb23042c72adaa442a652b65adb56216e048923a5053a98a04315eac9a4b764f6b47e93a42c42472a8cfae942591623434e14a10315492db62e93f24fa1d4ba4c3c29ed2e539ae2a85788cd1f648eafa65268a7e975cc5ac30a0f93222964ac1323737fceef083a46e19cfb98b89451cf951e3031006260d00122478a0c3971224792ac9c10418728d659264fcaf54c47f428cbb2868e502c3d5de924479c4a655249515151c111748042995feeef262d34f64f84c842c3083a3ec16c909bb66b4a46ea5196a58dac651122870e4fbc52867fd4a899ca64946581838e4e9c65566e69114244447a8810214ea4d730e282654122474a0aca1b4196850c3a387169873791bead62f44d34a75e87af10b2e4b99a48e679872ca1796384c4404726ce79b4defb50bebd2f4ca8a4ce27368f36ef4c5ec2b45a334c67c8798c96e0cbc64fcaf44187fc54424d4f8db9fc74a9112516e1c2f588d079cd9a8442e8e624fda4688c8622061d92386ae797c173f2332d83b22c2ec75a01838e481cb3caed13d912fb1b4898841cdf28f6f7326a8f38a97e5321f24dadce39c2e0191fa356ef4eba6f84f13676dc95a3293b464939599c8db8c0481acbb22c488ea4a80e46dc1a4abad0a2eab3948c0c592abd2c42900461b501c4880b80b491216c00016276e67036801c152041d0a06311674975ad42cab796af08b5d5f46b54425ea57a724447220ccf9f7d3e4735a26a94655916212b100207106303c86123c891d5c61e5945545256968e2ee840c47a55dd771269eea35941c72194a22683ba4c5b391f946511d2021d8638da86eaef4669ee1c448438915d4b6525082a004284c842034810349645c714741442a5a773b2b7fdacc746599647e82004ef652333abcf2dce44591621405680a8b40124c81973f0b204d101091d83388a7a5dea33c9dc394259966581438720b0172173bdaa0ddda9402c64c7c9d133b2aa3b28cb222408117400c290fe1ded6487ec6da12c4b1174fce17cdf2c777f347d372b485294a0c30f99f9c657b325aa1b4559165739b22c42548064991144471274f44139711eeaf4649b28757041071fccf5a7422b39da315419007923880e35e8d8839f753ff68b33cf4ed2839b5fe23eae884fe6c943ff4aa510d1d0c95b090fea509fd545bbde34c23ba442ae09d92e45477e3aec60c728f1f9df9a9336b58e3af42f4a79f699acf61e655952564e74e086192478030741d8d041074f93d4a23cf7df681dcab2a0a1630e4b659bc3bd6edae45972403547b573f1f03d651c2ed36154e698b46b14c1e1ba91194a55a45eed11a20233902c336a1084053ade808ecdb1e6366e96591615a0a1c30dd7471b29f54fd7b6b60d6d978ca9e64cf4a98c0d4bf9a073f7cb8a2cc5c8c91ab8d916ffc2479becd5708e615ffd5ba44e370d66774ca765fb99456858fba627f9cca5a36740ae8dfc50266e47f366b06cf5db5b067db39701e95a4773714f152a198c953f5ae87852bdf131d82a2e93072d5d3e6e31fc5b1a5add9c0cef86213b391f7d4cf5930886e3ece93fbd51b76ef80b86f8a8395ee8eb9c5e507d6cb614e5eaecd4054d4729f5780a0f912d17f4ced8b1d598defeac2da0a1b4fd3c8e87c95ab0be94f04f23bcefcdc22fd4bdee840caa552c986ccee4c43d3e2be915d6789d531957a7b556f8e42bfd3243dc87aa0adbca0deda29ae93145057537987a96cdec7b0aa7911d34cdedbd50a5e048a5f9c546f728cb28e8fe2e330bcd11a5935030c455ac0999ebf63fe1d4d37b725d654eb513d032eb2c536abbee6a021e62262f66d4f52a31c112e749a8a7a6f27809fde908e129a3ee9712fad23943535c89d126a1d73a8b8f1a29b57ca10e2438badfb4cc7acdb2e21174d73fcd26b5e3bbce089c79d0e8a9c657fb2d022a85d49c85ea7d9c08abcc4a071de3271fe1102e1ddac1f5f9bace4e08b7c99d8f26a4d4eee6b883cb0f557acb440b823f62349c76add9741010327d5b793a4d76a97f90329d3a9a4de44b2ac99faedea49672469667e520ca33a97efa5c2d12748852b98d715a982ae9d252f99690dfa1e4036352ae739fb2f9160dcab218712227ec3f3859165e4e4ece3872741d5dbf9072a5f638055bed16bb1bb19121cab22c8b902245d630c18311b316795af9cb106ad445524a735699ed83d03909e5c48fccf18620882cbb4ce555e7c79489b22c3f90031072b06a737337aaee2f13cab2fc408e935f4ee444881038d83091430f50615f1ecc6dfbf5432e9d9e9d268390d351d2da5d3e69a98c7c711ca6d8575b95f9742e144264195171810e527c525f74870b33539a85b22c1e477b3beeae4f5cf56bd01f455996388c0c9183e350f1223ce84dce7c860c36deb9a22c0b0e38d3a144b44be14afc44591622cb57909cc84124c92e8b103880b011e406bb88cf213cbfd6f6742f537607ad95d6234fcd03e4be1cd73267c40bf584ef8d5be9a222d59f76a06e569fb41c29e4cb5007af9bcea9bc5b88689603e63ebc8fd67c327c83b22c71181982442509bbca89c7d12a10f2cb5b00040e206c005949d2c6b2a8b81c2aabc8bba1c6b278a7185996653161020e10d3a27c3c4bc70721376ed07cc8d8184fae8e78e4b0811ef38c2eb1ca33b32a480b6a70876e7f11d265468fa5d020adf5ba65ffb59d141cb90c2d59feadb96234834c8e88c838527a9ed514deb8cc663b973cf35499e56a3441062b174a745eccd6064fc71262c08731d5545fafabfa0e1203187c723d87ce701fcfdb17ec3f762e364821e35e2e403e988796c8925fddb04d0ab1b2c25e46686d3c72c52959426470955b608c11e7324adde79eb2f19bd0a75fbbdafd158a8305af0b21262b6583d2a6654192b286996450525708f5c247946599c3e53859454e8da44a5fbdd9ebf639bf82de4ce677e6fb6c4a2b0da51052fa968837211a15ac8412fbb91a64997f29589c1ab35561fa5de468742f3e0a5fd5e432b3272928386730fd39a70bfd231b671cc2e3ae90cd2bb3125184c491643d05cb1244062740cee8d0f2944ca934ca032800724607e030c11a647c9b91e7b59f425916214284a4acac0c5959a3488a4a1aec4764b02c4174304188397a094d9fa9699b8372648e9565112224e58d9841e4489121419605c909978091731753424a1bf50ac2ab4cdd992a8410579d80047dce5d7a7bde45bf44b01cc694d29fe35d1fb5ff943314fdb72b36d7734c2d550955a1b931099dbc3328cb72b22c4384644a07fb20d5293de2d428cb72928aa4a49cc4a17272f203f7f8a993d6e1bfeccd1595121c5fdaf39acbd2d4ce5096e5e4d0752f5273da7de5ee41591664979fab961944a8bf0dcab2002145250e111c846d90cd778dbb5a83b22c2a2b480cfb19275637a60e13a12c23e084c99c15ba3a469983b22c276d649df80f547c59dc10414af6d9a9543af5730b65595090243972a292f2cb22e40c206f04618703082b06cbb22c270801e7b1d3fe777f8ef6a32c8bca42e1936511b22c2b459e0666ac2429b2812024006218f5ab5b2671215a465916212ac90682382f22474e8c0c591695150382b5fe96e193bd9629745096a5ddc807b48d79cfb93d471d2dcab220697724272b1ec7b2083103c992410c96a5484a1b592a282b08120fa49c24b40f744065ad7080030fd80004342007af254224e5031980000628708107584002660c295224a50315b800052230010948e00211f89524138040101ce544020fb8340107042105082b14684042338301cace89b91c8bc89ec46164080a928562c6029051000712602b292908e8e52a1d38001003c4c181021c241f208019800704c0810018060070a28132ac480a9fb8cac9c99123a11d6147923f507865ed49077c9c9c60a0471056910af0382729444ee23032248e3852883c808c2214d8a192b2b2246062868e73a2b29c089f9cac04c12f5002812920601653d0e14596113210300b2e94e8484101d24032e4c812808e32082000258f98051bce28198212c77bf3ae1de8200738b8810d6a40033866f0860c62008317b8c08d36d860c11a6aac200d15a4000d149c617202131308492507b802154a0e70056294ec8042004a1e010b329c51b28b39d882162525253aa6a00b3ac4616465213921b290a88800492df0505262420b5d9891865c5cc1641626b280c5492c605152820b1e64e000b7a0450d68918159a09cc8a20ab210428919cb196834925ec34ea5b3294450807e60b28a92121342a8e200b08884892a6001e400af404249091f50d18e640e2367b417596ed80f9645c5912451f123496450846be08853101942032244ce683f94949898a2dd0544889c515262520a13521849f2252526a3e8218ff01557e1334a4a4c44e14406f1870b458902453a732c953e7ca2a4c4d01c4b45e50c242525269cd8444949899968821099c0444989491f2c01884a9494983ca2123240627014e120205ff1152742e48dcc800891246938915d296e349114355c084382b0da40a2821494949850e2488a8a91191419b288a01184d50612f7345652dc50414aca1b4796ca1b5e64b961648d20a880c8f214a4b82107a7b4c0c80c3cc50d3e4cc20f922829318944494989491f2011045f31e183c923dec81a44882471a3a4c4c4112525268d28293161c4224a4a4c14d1fe46ca27c2c40f88c804251e410a429002100720c51e4a4a2e19620e97a3a4c4a410c6e670364c085152623288a5e26c10594113198282c61c448cb8a0a4c444102b2aeb48498949207a884a12222f282931010491b5294456e03c24050e6fc3484a1a2b4896b760e58c9423292a2f30b250de709594367a8d217f50713986184969a3a4c4c40f253fe0214650d8dff01f0c89c35b40640de1219d84053d9803052525267d2829292931e1c31ee4504949630e14ac220b491236da88af20711aa4700aa7d080871449a20257494141b2d0303224855786a00089abb81aa70656035483ab01d620d5206ba06ab02c480cfd6059ec07de293bc76a831d0e21296f648d20ec70a8a478916544888a6f911423de45528c204182a4890ca1819020ec700c89c34892348c24f94e47ada8ac17ccd12928585159477a8d393a05054284180a6268062b488ca4a0610610952488b293442599c3e5b894a487184a22e41345d8000a8c008501941cc11378d0510b1db6d0210a1d84d051084ffc2821231348e804194a8cccf1892d54c0044fe004277ea4e0009cd061128c2bc626f860084d40420b2504388319c840062a94943ca1e4095160c2194d10b232809292216d98c0c8103348606668c00c234ec40c6346092c608611278207c004144a4ace28d171868e03f0a204beb8431d2e60061484010b252f2881c30412b804194e6477889137809082c611242a093b1c48861c3903c990232b0e23448e141962091e25252b1052092225252584a0043128218592921223016a0441410ada083207197238b7ea79d8682eb5188540461c927a438be835cfd93145061c10dbe183d7fcb7dc8f11c878c34a8dec6f2b5d9eaa65840c64b861b94ae5a9ab690a4db7e1ec583ae6f8f277ad6203af414b8f31c91622af0129856c955a67274e359c7e534af5d1edeb496950a4ec2e5dbbff184734983e1ef75d2bc4d7e80c29ad4bebd7a8a5cbb03a1966483cf99af23432e75719da8d55a7545366869e0c8f72d14a8638cde3f518d2a6e3e6ec3f4d994528cbd22745c81043af4eae7e33d3be5983424618967db67a3a29d741072d18f20fb9af7d94cc7b5a5f685d2af9a9f255950a215932bca0cc2e956b14195d3076ceaeba366878f6c8e002bfe2dec5c488bfcd72838c2d28b52e29b585147a55dceb0c32b4c0ac10bbf51393754259b855caf4ce9c3273080bc609df559ed9b4faeb0a6815ba4984cca020195658ddcad0e91bf4f6836e04082927292bcb520232aad0ad8cda4d27132be76432a870d4d2c153848fc78da7609bb613aaa2d5e5caa4acb4800c299c42efc710ffaa9e2b9435c88882a16143ca92571135a22ccbb20841430e5e2b325081011950d0dcd52afb904208b5a22ccb0a2f329ea0d25b99a5f23bcfb55096c5c82272448d20ec9d828693e184a5e6b8d57962a4881c8508917582b272c49bc968c2f274b82bd3a763903b136ca9758bf29c5e02a62b362bc735e65742a24eb9b415217487340928b97beb1a756420e12cb959bf7c52dfa61d639dbdc4b674f73d37ad1859bce0786387313831a35e3de6a4b4a851961395143880b011645962b0a318b894b15fa8d4a13fcbc4d83fe824d3b58c367eda310c445f6bd44107394a845096658e4564598618c91dc2686347306cd99f9bf4a34ccd71b00318bf386fa6174ac6d8c9cfc10e5fa0850621a310ea42244407e99841022142129a1acb12040746d8d18b854b3d4ac4082553b678a1d08e6a4ae6b5d6f4b84a1224294478084358961347e1c547929c088101901604b1c18e5d1c74d8169d46c6a43683b22c4528429121284844f083210c418d1dba58a8fd8c350db649b323179f2813a7bd455cf5b870b78498af760e4aedb730a9ff2874b02da974942dd6624e5368fdf9934eaa054a7e27a94dc34b3c8616ead51e2ee342ad3c11cab29ca0204972645982ec988541ba6a258496ad31e7b2e093d2e93b53af562d130b3747cf22af3be9fb1c1629599fa56c2d56f9bf5eb1b6cf51d91ae5eebe488eb0c315aed0a06633e7bcd19c1276b482dfd66944b472a93750d8c18a463d69df5062f2f3b88ae4c38f474f1ba55cb12afcceba793e4d6a2e2d22ec484526d37dd4f1f4c6a465743461072a8e37aa4586d82095e918493985dea79bb38ee88e78501064adac146861872914ad4da933e798552dc52fcf6490fde175f29214c84e5a9592194f956ba358cea7eef8794d564563d8218ac59adef89da5cfa60ccab2c8c18e12c489ec3ac38e50e09f4748ed59ab2ba14459c10e50a46f85fa183ffe3f293fd1af92d246bfe73799f3861d9e68440915195d78ce9f74946147278c32d733a576914f9f13a6fa1291e94264f4bb094dfc9b3189e6f6f79a0d19766402adbf931e42d58bac4196450b3b30d14abd59c2a3e899c9f0b0e31246b5cab5c728741e5f0d3b2c71ea959f7b3de6cf32b522871d95d04b656cec987da5a78104b1c20e4ad0c08c1d93381972e48c3956ccd82189941fc29ef246d640030d10ec8804077640e264c81133763c62c891339c8da44c6087232ab0a31148e670392cb08311438e9c4103175860c7223c8e38d61b20d8a10824894062c60e444460c7214e24b0c31007d8518805ec200402760c42003b0491801d8138197264013b00b1b28f2d2946bbb631ffd06d6f6a3de5316af154d6913382a8b811236ba0b1c30f7d14af45acbdce9863ae72821d7d50c8e8b1c6e66f4fe64476bd61821d7cf83bea745a839cff970d5179c3063bf6809acc27daf7fbc24e946591c31dc9891147593979c10e3dfc7177b58ed1ca5ff3407023cb92c68e3cd8ad5135db5ee81815cab29ca8a49c00619d60071e9eedecf8301966738db22ccb22a40541961d77b053938fdefddcf13c0decb0c3dabc430a2d351b5a0be52465e5052ad85107a5bc8c193da7ce984b14212d08e24354de6863071ddcfc16ef59eb17abec1d73d074c5282594d0a45ab8430e7e690cbb39682126631c3be270b2030e2b97a1ec743b691b2d0b1122ce2bd8f1865bb4984e2beb1b5608e5c0d182203a90b0c30d6854e66183d67b2ed33bda90cca92b951a9dde5f5e62071b18a1756ef5bb2a850bcdb0630db8e7acf7c7c343ff97851d6a5066652e1ba4beeb2c2359c1c18e34bc62368a9faf139ed318ec40c38e33b031677c64d4bf668ab22c40582a2b3bccf02aad2b2a73ea3442435996651192b2a30c7b1c9d3b4d7d29bb92610719963df5735a688d61c71894fd3ab370ff1393ab18d63676765aca4fe72318768461bd19b6a3e9e87cd7828193f2ee2993fc6bd861b0e30ba693a37328f396bf9f2becf082f6a283697da9b9e4b9a30b2ad318c36ad5fe42be5096650717de91fec233c6ee2d31cab29cb411233bb690674fa14e9ce6dca884b22c76d8a105848abffbff8db22d85b22c2742e0b0230b7e089d29b36c4cf2732c9c2743dce998d57fbc57586af7509934bb4f675921ed7fbef19e7e3ad22ae8b16cc4261da7734c5261d3526aa30a5fa11e4e41d542b57754740c2624854784f818ad64507eb22820cfcc5d854ca36c282c478f485ba95f64fe09bcd0674ae8873b25e3881d4e3896e78eba49e79e461c7634c195c165d0ac7d395ac8844d7ef86b964c5b0f05d9b1045db878d99b75d6a62b61dd29e719349667cf9390ae6a4eea44b4eca97620019d5d7c7ceed5cb17819817e85082c931dcb4f11142763cba4c8c818f1ca94c6cb7ca6c520cd3ab0f3753b7b91b2786f23bbf889ed2aa3bfd81154c8681e6ddd89607e9d9998e24980823572e5c06791dcd6f46591621494c82618cdf7696e6249f64830930d458b3c274adcb5eb10c26bf3864eaa4b39b0ef62584e22b46561c2f30f1c5aa35fb760b173228ad93364c7ae18650abd6f39498495e0d135eac296550b3b72bae3d946559319224650526bb58c7838e79a3eca9d151960505cbb22c7e86892e52bfddf6f3eeda6d8e83492eeed87767af93ab4f73103c98e0a2d31df5b8bb6e7cf97be0901d2046054ee44892373a1044871a4c6e81d0db14b97174aef8820009b22c6e30b1c5e9b36ecb8c5a9dcb38cab2b8bf1117ccc00540de60528b5cd4358ad4de1bd366c184168b89ac0e795bd3a26382c92c4eb1265f76b73aebb4144c64d1b9a7f8d6cab0b162128b741a214be585897c102ccc246ff3ee9378f4d82bde0dabe149f7cae43aae38aac6bca6dfe7e55428adf8333c9bc7906a52b5acf04447f537cdd82945ab40deeea9baa6ff54be2ad21ca4fed00eb522c5a9d0e37fd20fd9b13a4a0d13547c7f427e7947e52e2a23989cc2d649bcdfd36b33c94da1b7d898846c1859ba548a5d7bbd6a11194b4a9114caf8ca3b73469945c646a18c715fc8cfebb7392f0a46953a213f9ef951188a3e7a8a8d1f4547994ca0d8e6652e9597f2efb53ec1af149b1984ac123b3d91b86b55a6d6f7d3c974c25badd5966a39cac5c809d546ad9f35871f1dda4d1c748e7a8eea2b22a326ce584ab6d6d53e6b8f63082699c0a38d9037733ff1594c1843938638f35c625294658982c925ee204dbb4c53f30f9125cc3a5cfd8f7cb8dd5809e53d85ceba730c4aed94b0937b9042dd54b4924d02d951329950befb1ee7c04412dbe72c35c6d8ac533d994365650e670388124c22a1101da16594e747fda32c8b0f7c5984f8400544d69095234518c9b2b8610209476b3e612a85c6382294655916152c8b0b4c1ef1cb98ef57283bb94287224448105f03c81b419660e288b5fd4e54ead89cfb41599639f6a4ddc80cb460d208a530cf3432eaf8245b946509a2e309268ce834958b19a9a350528d06901a982ce28e1df389f8283506bd8922d653bbd2354a3317ae4922d4519a767b33e2c4ca0411ad9891b27369e88f8e0edec0c11b268748c7f6afcfd2cfe525cab22c4b0b82cce16c2ccbb26c9ff8109595203d303104371fb37c6c6de3d92dc4b16bc3ebfb0fda95eec4841096fa684ed26c352a9d16980c22e922624a3e0a45881b492182c6b2b89114229c0213412c37aa53298452424bfd042681c0538febd2afd9e33940bcb64a8f78dba8caec8d33f9831f307ddd59d4e4691de63e989e3a23cde5ca49293e583f4a79fa8f61636bd430d9c3bb9a73148d423ddc26b4d4b26c5429efe421a1fe3d69d677f2dce0219975d6bd3ae8cef4fd1d4cb9326bcf2dfb45e47650c5ebbc42eb8e2af6ac436ad33ff39b189d2d462674f853f77ba77a99852ccde1d3b74d31e7cae154aa458ec98889d359161407fc4fe7f02342a4d4c1e174ae3f6485e7a035fe063c8bd32433a93f196a3778ab6742aa5f5de3526d50842afdf37a44e6c33761033ae56869dde69aefae612dca63120f5274dce23051c327735a5522f5c4a43ee57b6092865ca4d2b154fc85971c0d7b4715d9f6ac47cb243739c34a89de347ac7c58b68864da9e91c55cba032b71c9894c1f01cffe685d83a59f2810919102d7a455e4f6e1ea13198b1b473faa84db9be08c5440cb6cbf556dded9239c7240cbc4cb55a2999b93f9d60f88217d2bef94ad62aa93e6d284b9022292a69044931e9429eda5bcab0f5162ae5825acbafbf862fb1f9426db2056eb3a7ea98f67097167e9d84dc355362277e16ba951a5a7293269d4c5838a4f8d52f4dc84fba2b983c699e7cce25bf9d15ceec5ae45c363d7ea80afc68f43c67a3fc938c0a691db5c8dfa8a3993e294840603205d5cdc6dc9d7e17cfa22c4bca8a91212773a8ac2c8b1021330892b262648d3954de085202132924d4a7acd041b73a19435916216d984401fb986bbf7c453e1e8414142c4b7b1052f6c4040aaadbff6bd7a1a5aada031d08624c9ed09da99692b29a4ebc9cc0e9bf4fdb493beaf1bcb18120c6a409bf769261751a1df57226acab934c36ab5e421eea36a3de53baf93251c2aae4778c199bbda51c6559849824a12d55f227bfafef5128cb22c40409ac7493239fa3083d298a101dc750b896215ae49936df18d1610c6c4fb5ed5cc91cf62ec6b3aeab378b19b55efa40073190cdaf3b94df6c0a7b1801ca6e083d54a10e25e7a674b66729958a74d0ca76a369c6fc94083d509192bdd33e2d5e4945a1c729d6d0d5e0ae45bbfed7083d4c91cbf9f8d2d4573ceb283130418f521c6bd3c8693a33312d9465d91ea4b05588df1c3eee31ac1ec78aaf40a5c728f024cb4668514a99fda2d05be49bcae9a1c8cfd5b89cfaa4759c4191a8170d5a78f613cf663bcdd89a22a5cb13e65822a418fd5dd9ad4e2c345b2b3f5139b18e328dabb6764ebada84f3bd3a794919b919d4842edef67b72837a9d89b51662a399bd6bae8789d4c99e129fd3513b45969165117209b56bef955026858b14083d2cd1e6ea091d328892d2a3127da990f9e2e6e9a354c58910596844a107257e4fdd41b75e5922a62040583180448f49203fda7aab1cbf934d093d24a14e32fbb7127ae6834488100f2c403a000488100f7400c81c2a428204016228491039d851cae8458f489c8566fadfb8a3d37d4830a6c5b48ed51c35fa47e469374729fae7b5d48e70ed473cc617bbb9d38df0ef5bbdbdc67cba6546a46637c6b42a4f3dc68b50e67715616ff8ba97143ac9962722fd52b8d49b3486dc11a1e9b42afb44089dbf75887c43aa598d263768ca10ca8d10d721a63a9b853046ff7fd6774d7d3a42a42dc3f5dbcc7b98d120da302e4a85c912114d108c929abc47a699394f208cd1ac7db3fb8bdacd1adc0310e61242efeb870d115a637bfcc1b0e2e7745c116a6ad7f1841e7e384ba13ac6f49cc5adace244882c1df4e883235ec613979937789c0f9c89e657b5f67135dd83595a72bc5345337684bcd1430f8bcb94e6311ae4789c0737dbbf4c8a075cbd0619ebcf4b99d21d92f19c958c6e97fba11d34351a3c84d6b317b23a985234fee57489c774408891ad5e483137a69c8322fdb6cd3d9fed9ee490943968d4a345ea1f075dda8c6739ab35c8201c7c19af539da3528e6fe0e6a48c793b87af776ed84c0a2585fecc7f4ad4064e0b53b24d6dcca199347ab041a9ae36ec7fc7ad969ad0630ddebbd629a5f0a43d4d35f8eee2ea6dbae95428080a7aa4611d29758d27e52b234603da356a50275aa6cced19b8911fb3fea7339cca0c6c4e5ab9cecadac6b00c6786cb1b4fffbc5224648d27f4200332ca8e213ace766a1565c555c6f00a33ed782d1e410f31b49a73678676ad543e9465e91106d55eca6d2dae5fe32765e5c4d34879a3053dc080da34f3b1c4daddc6bf80460dcdf6beabdd84bce0072537a8d0da38ea93468f2eac27536fe84791a2792e70a35befc3c379bd7a0b9dad8f7e29d7357a0b8d1e5a386aa1a56a8c7855f96721cf32e74bed792ce032373f6e4b3dfbfb15521a466f5c0669aad33dacb0720f4a8dc778f32aeb5105d45b698e89d1b627438518f498425acee8ff72a5753bf6400754d0430ad766253708995d86d10c7a4421d5f8ebabfe33429b3da060c8f13462b326a5538ee2468f27f470420b7a34a15b9161bd5daaaaab081e7a30612d7bf1a3dad5d8ab28cb22647b2ce1f8a8b40c9b51f567ab460f253032749cdd6ce6151ecab20829c23550e991044eacfaf8861acf0cabd103099a7bfcf3ef5c993f1ec378f118d39df598d8a09cac2469c318674d7eaea4aabb52138f62289bde5187d09f4aa5c4c04367d6aa74ece76a18693b97df5dcf9626e4210c377b87359d5d46cb778e35018f60bcdefafa5dc9187d2e30320fabe495162d5e1b11220b0d1d50e0f18ba58bd856da564b66191ebe58a65fa9dbdd496dc947e0d18be33ee6ce576c54951777e617a9e247eb56e25d187e424913abccbe853c74f1cd78cb9331e4918b3c57546b36e7d84906a57b093c70a198d0726f948a9959d3003203206b0439b2da585944d2581623beb2b288bc0a8f5ba037bfa4fae04abaf7b640fbe7381bd234099dafc5253b5a4f4ca9c6ac69618bfa144fdf7965ceb358cc775032fdbf8dc8b248dee5e62465a811321b8bb63ba34ea34a675e2d582cd5658697acaf50e651a9e3c7c78ebe7285edae45881ba9725baa15e8a8b76f665d7f5cc98ae4f6c74eb6ef5ad769156b617342859a561a4caa685f4cca95a64f952ea5e2d127b44e1a75d42746549c74d23ea6fda447213c859eb3faf0c15345fc33c5a6638c4c3278ea9cae1467f9ef54a655a9f7881467bb8d2fdf633eb9d128786d7a5d859028f2f3f86995f810e35a28eee4e799d4eb8f9582a23f21b2d4eba447289d4ff0e55a346bccbcf9399ef84df737bb94f24e7b279659b5c820e2e489c97870e27361625e87ee58331c3c36719728f1503dbea57a14003923c8b22c0b12223c3471b453aa1b45c39ff45096057964825d93c27596cb18d7f51e034c9c5dae16eb2a4d47d7a32ccb1b3c2e71ceb83fdfb63ac36c097bec55ea0cee396e0865598ab80c7854827f19b3f8a8f2d4a6e7021e94588979f5fe733a5ac2491c2ee565d72753623b49ac4f56e7e8ac4f9aec2261a8fa8e969d4de6777c88ca1b90408896fa556a56d335a20849c10c783c02a149eb660f7af4aadd11ee8c4c23f4b484961945c8911495142ccb9114957e834723d06ff1155b19cc66445916462ccd55bcbeb3f75fb888731ecda8ca7536af064511abd3ca4ce84ea1237f367824c2979fd1d663524b9541c4ef2a94a6b78839ad0f715c35f18fe79b460b5196658e149515242df04e71c310abbb6a49d1de31a9390843be10269313ae4d95eaac6942744a785c3dea576f569465e13108d6958de7b67acd2c515c06ca43108adc97c95648913373200c25b596d39d37335b40a051c6f3dfb589a49cf0f84342dd8b2ed9dc4f7f51966565f9e12c0fa33f28d70f3551964525c5c8901315f0e803bb5a846ac78f2621e683e79ba921abadc9845096e50c1e7b689576d7d8e7c8d2526df0d08322d4a7d4f28dcf521e98f9d91c3acc73ce4127e081874e9d92496851fa5466efc0a7b666d7faf828842de06187b5b7db764c9b7d4a58875d774c294ca6350f1219f0a04366ae4226fde6ee4a3a87f74be79cda36b9af7258ebff6567162574fb386c32ff3467aacfa25438204eb7269da6f40637fc5ccaeb28434d25e0e106eec73608a1bd934c9d36e0fadc94b0fba0358ad9e04b9d4a3b9f30053cd680aba711fa6dd33b54946531010f35f8b232c34857676ab434982b6f1af245f7880d1aaef53237379bf9f5cff09b8e4ac74fee515d6a86433df6c8a84d75f553065c5737e964a69a5a5b830719f6d53a9854a23265ef2a292742788c6121b73ccb8cee0b530b788801bd9ebb9336a961a4fc8d18f008c399ca3b7690b2c15549d4083287b3818264a9a1c30f3cc07012ad56bef49b3c3d797c61f939d98f7a4ded9dce00f2460a787821ddf79ea6514961c2d585cbf4be69d763b2b4ca053d96dc4d9e6a1fbbc3630b3cb4c0230b27adf5d1f5adbecfb1c0030b8b87d8f84a0ad92e3342e07185834ef31a3e29994e71630e1e5648fd8e52aa3989789757411d6b4b67ab6e53d2e441057474b3cfabe62eb4018f29bca23366cd331efe83407c88ca1b5ee02185a5d0ef22a4a7068da628a883946d37f23d9e5c41c1d220525b09f559f59e70e8f8f9b23e7548f9dce0e18456669df4eb1c638a545792b4a1068f26fc753a7e90272e525c28cb22c47ec08309689e2b93ad1bad95e6093c9660eae9f30c1f44360b29c18c9fdc5de97e94f9e5910454be6de818fc93d626cab2e860020f241cbb74bc368546c631d8bcee6533ca75db468631d241444afd516e7c2a15c3a45cb9671f513a9d470cb4521fb2a5de86717a95f2b49fa60f6408e3dcaa4e8452a7fb534541b2d4506404c398d71c7342095121a32c8b1a417440810c6058a384f648297b95d87fd12ba13f3ebf5c53a67db1149b65cbac7ff2a9edc52bb43633faeacf152f6c7d6ae2446a176c6a948f51dac65546179e8eeafb7dfdb4695cc9c8052e56ed3beb371d3c5ca42bcd74d68d9b95e76f91c9e6bfacff742f755bbcb659783e93cd60732d3c95223f49a54de669b43856a7ff8696d538cd2cccb8f39aafa587d5ed040d3264a1ddca673ffdf8ea3c42462c54a231f75599e9fc7258e0b15c0be97183c894bf6225ebffa49a53a3e676052e1f52781c255b07652b5425fafcdfb592b156561c1ea49a6f2b57918f2791eb59eb185115e6f25add5b2d4bbb948a5dda681fe152b4f81015e97a71f2daa753182bcd53c3dbaaebdc14bb6b5db5a659a256a5e074bbf2333b57e921523c5a0811fef3d2dbf5a3503b4d8a13db5a49bd28d855da9afda3cd978a52848c5024a308a167dcb5fc674181c92c2db4e88df6aa1c647ce2ce8cef9d4bbbbc663d818e9e743f74dc634c3bf1bd8c523c7dc5895cb55e46a9b642283781aa5b9561f7b31a4c4d60e25c766a5dcd6eba4c9ce295dcecd661ea61622576948e59376488d025d2ffb9b5abfd7789cc120bdb8f2ef408338d9a24472a9129d552aa167953fd28a18c39b696881bf1aa9ec44193979e790dfb164b623966a76536f8a78e228186fb57ba64447387049ecca529df525ac71f619a6911b372443a09cdd8646ea2538dd063f252291ec2d7b418910c73afe58512f9e022be8d2552b392a9ed55459cf643274d0ffea226021d76576cd45d299b44c4eeffaf9f49b712ed0ff10a516d9daede3d848650f6538c7c8d29f5090b81dc12139949ebf797109fe9e6d5f18ef1f71bc49ee1ea3d6a2d3f464d10a692d392f1731bd30e84e223465be694678800a19a557a432829d563fbc3dff955d476b6d751e887e7831042a86d9029577dd8a4f9bec8985fd9cc876314ebe12d6c76847e0fb7ccad33eb8f49668fe901197a508969e7d4310afb90f3907c77ded8a63b956af0a0d29c51faae492b257e23c80fc8b8032f4ee753ee43d3e7ecb07299d133e7cad3fb7c40461d8c2fe7ab9b65d6f01b17a8112406cb220432e8700ce6b9636f3a0f1a4691011973489f794ca694da7cd3a32c4b0a12232968a4c106903428e30cee495b51264d6a90d932ccb0baeaf8d2c184fcec659461df8c59de8e122d4df9155f411dca2083a55ebbf4ea75b931668632c680fccef89e97e6bdbe18bed34a37f887864675187c939b3c869b3acf170ce64e71ea1e9356957f4135a633b9faee85b4863221df95b47b55195d68e65f8953e3a5738dcae002ae9f9f6dc2de02767a5dd829dfb44da12c8b1fcad0c2275adb830c694ae7a432b280fd97b0574f257352a12c8b1ecac0421957c895e89cd446ed6abbb7a10c2b983eb3c9b437215e7450f8a48c2a9c338f58f92d576f1f1424cb02471954d0b3796d2c979db5b6654c81b35d4d42bda5461952704ced93b2152242195130ac27f9ea32429a4ad55006149acdeb4147d6932acf0b653ce17e95da59b57ee11d6b42194eb04ee9f8517cba0996365b59a9fd75eb6530c1cd317ce69c3eaa69501a652c211ddd85ad9ecff3513447194a30ffa7789954eb9ce207a18c2424a3c2b784fa8ae9189465b9411948d0d54a99e49c07113151e448e91afc38463a4bb13a5cdd28cb728264c8b208e93582a84ce28731dee04983902d2e464264eb2f1d2ee22e458c84a9f928fd562baa0d63af55f273f546104e69c1b22c8b901fc2e883abb8b89812731e30fc08c6b5499d52512746cba9063f80f18b1fbef8d10b213f78d19c6e19f65986b88efcd845deda43ea5279c0b903408420012244d94902a4d738b2dad881881fba389b79ada7a9043f72716759dfa84f9da66bfcc0c5a2b3fad64ae4660fb6174982649320b9852aa436adf4d328cb92821fb6489bf618c4f347b17147599693395450f0a3168bba90eddfd272d4465916151f92b2420b557a90bdb2aee4e6ac4a11aec1097ecc22f78fa7b34611525facc20f59e0254ae7eba374bd1f347ec40299a5505ac67511233f6d64c82f8b9021692ccb0f58acd263308df2f1b4360d413971210c4122841faf486b566eafda6831dfb224f9e18afd3b66f3ec711db67fc28f5698b537a3988f6285f1672742650e655952b02cabe8948a08592157f34e3f5491d4a385c84ee12aa509440c3f52719690de39d4e7ef4d28498428e1072a56db6c3278dea05c57466600870a7e9c62f1aee5a14b480540de08b24224490f7e98e2de9849e64e7e29fc2da1a13e06b526c5a450e817f9ad49bf57a34691f6d2396a6a253cc84e14c8fab6ccd1fc76de09c5b239eee122a44cb641f9010aa55269da6ec59e6ddb6b04d141851f9ff8e18963542ae6f4c65df9d28f4e7082003f36f148e11abc34a98ce61bf9a10934881cb17272e369d37e6462959ed55b6d68d252633f30e1ce6bf169fe5fafd25078f0e31289add6ff399d6c8414cab22c8b9095941415191459aeb282e424073f2cf1a3128b97af716d54a7fe7c1a3f2871ec2acfe699a93da77e4c22f1fca78378b90edc3083046fe0608ea5a2b224a15426df7cdb3e0897a12c0b1224ce2a2bf723123f20818ee9ef42ea9ff620343f1ec10b13dba73b5f2ea38e60f4366fd2b9371dcf46e0c9cd4d7ad6d8b17442d230632549910d9c116425491b283f18f16311d76869a733aef643117ce7e72435f4e8c93c11898b96674c1e55bc8d883fbbdb289995ccfef910aa8e784d26ef3d9bd610b6877db80a51cf51ab1066ffe0feda836799518470664dddc67b6d3acd411c4e4c485933ba3b4441b03a2b54530be1d95e208cd3b06f274a40584acd2a19e7537cf60fc7f0ede9fa731c0d911ff4d1e25ff369fce6a70fbdad8c880d1bf21ff3830f48bde9d9466528cb727eecc1dc5fa6ef75ffbf96eaa1d9f991f7e6a74a7b08e541cff4d0e135ae47cd54f0030fc8d61dc46ca88b0df90ec750a9859061322b53dba18f2d6398cbec172d9ae3471df64dbe6e5acd33874f073d66bc4fab639427d98f39789b4988758d73ef23b31f7238ab9b191d657614ba99fd8843bb2a333e8756dd91d90f3874aa72d58f8cf2b56ef6e30df6fc87ef2095cc4fb5d90f37dc396eee7768d1e95e6dc0e3a5094d55eab15128cbd269fc60039adf59b22fa492316a083fd66066d549bdb554f714ab4111bdea943cf9ab639886b5d61d4b967b380d0d0dea96a9f5767e9c01a945ee447dd4c2836c8634e9d16a1fdcec3d68adac1420f951065cd98debb8dca7f7c8f0830caeccac59fd3e88d2ac1f633066e16abb75d6420881e18718349d39889eba4ddf7218fedffbfa0e73d1fc9fc0e00718fc9843cbea1c9aa3f857f8f105d46a909e94e7162eb7c20f2f1c757f95ddbf0cb571173299a5d70a2917d026fef4eabd3f1d5a5bd0b3f8fddd2074f4137f68c1add3625f935e6d329d85f563a8cef2ad5b8e180be88bd12a1aa245370fc38f2be45a35af6ed0b16ea21572ede1598d9fde9f470c3faaa0e70ea369e7a1517454e0d7cf8486b7d1274453589b90792e64d8342143e38714ba777975aeb47c6c5b093fa2700979a35bd45f5d2a8ff0030ae8158d51f37aada5fc04ee73affe4d93083f9cc06c07f72cecb3522f05c4b80088710388690388690110c346167e34c1fc41ae8ee9c363fc4d906539e30713dee4a1d34fbf6d6f6de8c712f8522e954e2a6d33caa0a0e18712548df9bba95fe76a4cc38f24fc31497942bdbc5d5d177e20c1d6e2a2193aa47fc98eb166ce957ac58a39ad35064a493f8fb99e4e5dc5604d84949a9a4aef470c4eddcba8566e2ae530d29cb378cdd90b23ede0ea958bfbf4b10f06f2656d14737260bcfffb52de9caed5f92f789df977da41460db92fd2d2f7ac498bb796772f92ee9d537bb5ccb779d1ad3829e5b8cca2e45dac23f5a372ad8fafc4ba38e8a0a13dbc5536e45c28b63c3dca965bdb312e8e79cfbdde5ecaa36ea18ffc743acb2cbeccb50522bf2393a9d5bea7ad8559ab9631f673d26e5a5aac4e6b54fe9984d24a6b166edae7fadb1c31de92856955566db7349dbdc6e2cc194c33287bd1718545e3a549a6e918e3597dc52b442bd75cd13a94942bd2ab8356336db90d552b9249cafc7611e6694f56381ff58dd8dda4643a57c19a674fcfad438c26559168bb1f65764a85b245efeb50afc7eb840a7c8548958f6ea3733ac5eab5d0a9f5b88f7a93293c1954cda48ed63ea5526cea1adff3b485d04152b4abbee74a8677cb6f1408f9a14565cefefc48149becd5a526468ff4d447287c173ae6d97da94b0b14e8a43f4915d33ae7863e8167a89731c794cf0ef28452e3c724f4bb4c25efc4625768add5ba4f549c38c3464db3873791aa0d526d73cc1f93a60974b974a9ba6582d71a653251b26b34860984bbbccf61a538d32fc17cda12295f3a1a3e96e875542f9559f687b912cf9c4ea2d657e48a28e1c7c86e5f9d691be3732c2244560b7c4c42f531fcd7be923ac6283880b021832349da53d07e24497b47c187247279e7aa44b9269da42281c8349e4b8b1d8f4a3df880446bb7e63283ea7c0dc1c72314fa46cef5862aa55994657957393947f0e10857e87d1975db394afda524498cacc047238e266aee497f7c9d71467cb71ec75bf6d5e61145881349928222836579828f45ecc265ccb6a3a4eb16d2820f45b4ae5d72c7fe4fd5690a3e12e108f1f3763a6ee83c197101903782e00311b86a708df15c63f07188b4efe6ecb75a86d8b47efbd369de46e7c0e0a310ca125d3122448470347bc53bff696c07071f83503b07d3984106b3570be258fadf6ef6695bcd81c064eb2cb167ae5486296f648d37f800c4ea6a5fc7e6943ae940f0f187d3a7e9df7f9dbb47f5e18774897ab833d124f202126489828f3ef4264b4dbbe65ba38458c1071f3aed71dc3bcb966d96c4c71e9657eabd5a67e5a25940f0a107577ff7caf3a454c5ef230f9847e17542c935dd7a117ce04111a7443c43478c7aefa0e2c30e7548f443c7fca5654ea312820f3a5c9ebfa4eabbe7b0e8fde98dc2cd56cc4892acac15194cc1871c72a59979cd4bb54bf9c0471c6efba0db856e90bee1233ee0705aad5736868d4a442aeb093ede903821aaa35e0a997fca0a121527f870c3223b6e681935e9cf2f4a91218b0833c1471bf4d56426728428cb22040d1f6cb8d6edc7a56999287900c8a524405412202a490758e0630d3ed470b77614193555d4d868f84803269fd947eb8e2263148a11172ccb899114341846c6ec28ab6e5c3314244894e0e30c78a6ce3a9bb8153fad357c98c14719ce7b9965aa06cf2af46a0451711cf82083e2c5e3976ccb9e7ea12ccb1c3f6425c5c8898f31e4b769b22967dd6587b22c487c88c15c9f731e22edc664509645080d7c8421063ec0808c597352262353f6cac7177c78a1dd686e3a5f936fd897e0a30bae9c2a19bad6c4eb14cab2f84a0a100f0cf1c105f53f9efa8fbf961ef5b105b765cec14c46394a33f3a185b7e37bb52b55b57e06c14716f6fce9c4aca63ba993d6f01f0c595971b81b45862c4b1c3eb0f06f887a0553e8146a5d46bcb3ca0abf898c10a767150e1b536bf9f9a38252d6fe67f528f44f340584aad7c7fca1453b24054d7b2699c4b7b6ed270a26a9f3d53b0d8565b46ae59e57dbbb9e6069f9af336af7ead87182d1572b3f539a65934d60bdcfb566d6395a4c483b8cd2a073169e21e3868f25b0425de6687a49b5bb0f25e81e764ce774be6a6d1f49d0c6e47bf7a54c2b621f48e057264d13afa2b4698f61cb77254be7181a45cb18a9d4acad57783625b48aa1301f539eafd48a4a8981ecde6437f2567d940e43dd286bf2a3224b4985a1d6632f475b4b1d4d05c3acdfb12d4546757b02e3d06c42e6287b1ad4f90bf4c379eb91f72fa5d2176f8c169e616f7b5af6e2f09db559ae68d39ce485e67273c7f9dcd118dcc5fa65f8749b75819442e74ea9ad8b9b0b844cf137fe59fee1e1c28eeaa5643035fd29740b6e4766c61875f23f992d0c62fe644ab59ff7d7621527bafab4f872d122b11a7b5beb562dec59e841cb9647172d559485d971426dd0d14bbf130b64cc955d4aa6072c1c216394baee197d1b5f5924e8f18a844c53a5746e447594087ab8e2ed7832c3da78d269a5010d80d08a446ec776cc3a3bdb8eb22ccba2b22c438eac131500e8a1072bd41dcec566ffd88a27c14184bd8dd16315b98eb1f38b95421a904ca86410a533611c0c8601613018000003229b460013130020302428128763d170286bb30f14800243483e664e28282e1e0bc6e1703014088542c15038140a83018170308cc14094c971aa9a0043a892e1bc45e81fb394811bf6c3188769cfe759085631e5c1f7b17b36a84abcbf245831da34d42c562b2986625b4d504001e6d2f1acbd9a401747d765fef17cc8b8209f04ee8b20f20a669b1fbe78044b3f02e7e737f7d8f2c94c08431f18af7b21b2381cac9de0fb21679881063c1529f43c857c43e270f1f5bf5c266f42ec874cf8cddcf30262701e44cdb3a09057f5d7852df4d73e162ca151599edc38ce338312f58c2a7255e4a0d14eb69a823ec4492a82c77f01c1fc95b240909c3050488c31584ee8239b04148eaf39220d89e8305815bc2e98e8e24089b3cf5a310c3ca63bd5c054d94412e0eaa1cb27d4dc0b6b423adf9b8358d82e0d26b6aae84e1a1f866bac77e9ac44b457c20a0699b00ce9f388125025d5879996c8c81a3bbd69349ea8e8f24d1d68f91d62516df47dc7e8022615b4d36f6f3aa92e038d1ba4dd0410ed038d59b51a49246fcb17aeda232e7c40a05c41dacb176f7069bba8a14f8bd38615e7eb0585b8b78ecd801dcf5fea819b4f18adcd7fc81fef539be2e8c02387f262514f8be472198bf5ed81484c8a673b1171fdae4b6adedb156e437115124e785381b87e5d821cb09f500464c799d7fe84f03997b9f7723e1e72ff85880a64036cafab6cd3f14d52f559ed30c5126eab6b625b58af2d31e6377acd18f786bfe096ad881d0c00480c16fa3889dfd9571229f6a792bc1968531bbc458720ecd4e8da642a6988ab8be3337466dbfd67308fd58bb4d9ea13c5c01b6b6a4cb3201103ded90363cedc9ac2104fef5d94313d31fdca9d92356a7e5220fffc9d9a4a14363490b44730da215e3d1f2e2060ae91d7a9c6b2f921c8510684cce406a2662f09f706437eb4f44e500647a64aec932f3d4c0416d02f13f745f0ef8aa417ccf723364e612aed9a73504b57e46152c74ddc7ac8759127cd418f5c48decd390c96da8f3cc10ea1c7f0ffbbd7cde283c6f83873bc56dc78d51b75e2a32a560653b7d8729a669b75223a0fe9c22781320f2a171ad2cda54613f684e3226391b9c85de80664307ff66203c7def409efd840cb6c64ca253b1ef0f0889e52210324b624a890a7c33c1b2542a40002983e3f1bfdf025d26bae000eee216e3f562ea911209f6d0099fd7116fa9d7e17e1ad673dd4e91019809bdd37fd4a7bf2c4c2c032246c5608aa8197b1bf69fa09e7b9f1dc7ac479aeee8e36b15df5d6a982ea3179dc3e47f31414e5fbb5fe6d148c3a2e1956b099ada257465d50d923b5191c54cc412886d55370c5a04b3f010e0c9e8b0668f29584a8911795a7036b41d76189c271698d07d90434a6862f7004f5da005f9b449669f9b258b9419bc354dc6bb8f01142ef8f7c485ca70c1e743c8158a913c63f521e4f58b26fba3dee637408630c1245c51f11ad8b7cd9e97676fe6c0ce2f5d3029ce42ab92745b2f3f88ee0b9f30a82c631ea36e0106913e8d4404c0cbedc89517bf04ef526bfacb85964b8226593cb87cd8992fee173bea47fa83c839fcad678124aa172ce26548be7e51cc01a6098ce74db54be86a51356c5734cf0e247befb9bfeb39fc71c3bd0b33f9f3e5492eeb07a579d0a3c6acf33a67271df5486bc2fd10fea8c966b5fde156db6c0d1dc8bf1a2d315204bb04b4b5279e2b5642c8e053278ad4f040245ed5080680fe3d20109b3796c4026a133b0e1de43663ddc9cbbe0776b311bced42fb30fd9f3d708d424e51c6ba05986964ba3ebd8a94c4d78e88a98b5b8a9caa87bd9e498bda0d918432ae8f84fb9bb6c7a5c48fa02eca9714fc1ff7e44da0fa33b814c2371b236d2df3d8cc9976d6704f2e7a465f323905b1cc5ec455ef3f2f165f5915e06f179fa7cc6aaf812a3ebdf00d21c705c0fb3d4cff81ee758cbb0a5acd236a9bf8e6712415d199c814b4edcb6a2d56c3115942c45a5098432854dcb52a84e518a55efa4d7b3b6cf01c475cfbb9cefb900e9384096e76231ba5a338f7b6111f6a2e3ccbf6a2103db18eb6810da05d8b72a34c2f77ee9ba172a3dd0fb366c40f96225446b41269ee32381ab15316c17e8273e2059ebaea0b404a816392600d9846493763f2c26997d88a31a0c2c39fa17c816434c6e711bd80debda1e35fbb24b45c105e39617b2919c8a4095a9f4b1576e5919eac2078dfb668950085e69491cd5be002811fb26bd8de0ec5f2d3b2b2a3d5d04509dee1d055f95f73dfb4e750e75f97921bf418b7c5ee85640babb7ca16c671457b98a1d5ee124924890ead9901eed4e6579a9bec6f1190095f878c8dfe11ff526395e3d7974b6dc378ca0d4cb9ded903ae1dceb0ea253f1feec683df7ee81c3db29db0e6e3a8c8b016e58272bcf51b253e9f4f0683adfbbed0d04b2a7b70cdb296d868dac960c29eddbf87c4a82d71d501446a8a77516714ed97c8cd68cd2cc54e7731ec1aba4ada8448b55bfd1f83d464e775302a0066a988e9778542313c703487d40a496e5c6d23e254721ca6d2c3d315d124772c8b8b8e87ca0ef0e89b314506cba84eabbd07dde5f40416b1f3d38e228b6b76e8628e233b2a8339ffa20c944332d08754659ed1189b6fab14b56409a80693ff7d1da0b9b2dc41adfa1c33d888513f3eb84fbc5bd4503822baafd7fb5bf4ff47aab1cde245c1b85bd1b3a8b54bfa9e0109826c2a14e45eadeec9d6365f79148c30d0b2362bea4d7ff490d90ddab8bf8f885004e88223f877812dee2ef881833995994951afdca628537ad3b868cfebe67757d94a1ced2ae4629c6d2367f51fb40258659277c0b11f483d4665611b3bf958b05155518da3556a2b5cfa1996c4f60b2e0b60281360bafa116d19a9b872cb2b46c5443098c65130846f341e0af9b58ea7462625557225bb90ac9d22333a8f4d22d128ca9341056639b886e1fbab1867ce32a9f68f5499c8a39b53f4ced9584abd996dfd064a8300140fd0aa7c55e41e8bc1a258683917022b5383a899fd69a4a069195c2da584f9dbb005f3d45f9474a04663be80b10128dc224dfd0b36563ab3c2e3390e0415f9e2f485677ff6fe6363ff72b8a1ed5a0364e1d97cb8928d831388bdd9483c3893c8e95aedf53dd4775a750cd1e5d07ec3d09d8c0d1609ab7886b40e39d2aa00f3fedb232ec358691e9a8474204edfa8ac531d4f4bbdf52360bd757c7e2e298e82e559ddcd81d442c8656cff4504aaadd9073f4087cfe75d62ec30130a80c9ca50e8ae54a7d33ff68eea68c458d38e17b4d621cd41f708f4f075e3312fff9b56505b0dd6d2f26ce2061b1e1c318d67717ccf668953dbaf15ee42a067a2cba14e33f3566fecb5ec5491ce9a1438d80463d58616d865e6a0830454bec450e4792d54c69aa02d95831258d661609efd50b3347f0d1744df69027189425d234d643ada139c7a940722e865d9a2de2a16a741927cac131ba94fc1f1a49182b1e9e2b5b4efa3566ab3aacf14274391a45704c70b778b3c0bb584587b8e3b5cc883e58aca42dc30652b2123b005f7596260de9e5915fe2b93a2596c3ce25dade2a55e506e7c2e08592abc59db5d3873311485bb0c9d1870e7a7c9b7edb3deca5b0ab6963b1f5c858a30e7f58a49670d6ea14d349c358e1912bdfc1d7ea8b8ff0a9514ff6d21a970f474a7ca003be098fe8f77382dd493fc9f17703fd130fd9f5fb132c0eb59df3bc20302334fe8e7f81ec0c86f98211781387e2c628893e90a526399590063690065b9852f90ea9334aa9a6d9b04a0c644bea6fad9396ec7322652f952d344327faf058d9ea46ec7812acc03432dae71e2054514946bc7aa55e160462be20b4bb6f6003b93688ec0fc09f1f60ab15ce5a8414ee07e96c069bd471eb9821304f3d462e138dcfc0bdc26d3176ab6bc15ead748dc407ad200aac88b0b955a4d229086f2b205d9412b7e10cb1c78f629fb009caa92c35c0a1602586042de4463e8cbbcc8eca224e785c92587ac5b78dea4a6a2053e67790bc451ca2a25a49f1e84b1c0e54b950cfb19fad057c7e447f207b568da81a32ef6d75c579de23d67441069d761f3667fb7b9bd70cdb35859cece11f4714fc09c259bfdd05b5f64cf44dc3bef5170e68b14e177eedea6bff200a8aabe2ea0cb2c86db941fd456775efa399860e1edbc5b3d97b8068917eabf8e1a2e74a139c12b39121373b61bfdba81bcb3cb32a0fbe8c07913486055b9c5667dec8ca4bb1faa094d72fdfd3a7240855e24db316a1b5c17262b1df5ca4df8926dfb9878a706a456c60032b45fb59afd5e2e69c33579fd217e6762577f6141c36a253b1fa83c11073b281ec26846838b347667cee7364b26bbfbd1a47aad865a6519838ac435b8ada6e856cda39991450ed62c32767a9979518409afd923044ad006dd5995280627fad561f3fac86329c5e26422565c7e1312a1e1ec64a0652b753a497ea1060e43c41935afdaa5e0ac2080af2d7127ef97dce66519d65d0300242649c6442c72794e58e9e426be95a2e5b68287a2fff4d56fbc64028fbabe820859f0f153a448cf3ad42a788576eaba4d3858bc5833e16519a870ce6830515b2a6e0fdab849c3ae7d51ffd2ed4f25184c2720cc9db2927d047371fa9c7a3302750c60bec19b03610ab3abf272247d36b7aa11c7b5de8e4293fcae4fed7e9f9f98e4a725a91d7089663be2ebee94a7f6bbf07becc124f7bc752a3c47970f6262d65fb5b3bdff920faddebda4e0b3f1f21082bbd8a7ee3fcaeebd7315f4b8423172ed2866422c26a2377ebf3bd5f8436e1fedcbe9a04289ea4856a95361d92bf61541af9259c190bbec3266f67f780e908a72665182d929c161c11eb43a57b71098de6f3f8a8d421cc938501137b116d9907c80b39ccbb527f2568946bfb730e4f675649659c3a1d3010bd8303c031f46b91b05d77742807eb870a827b6a695d6941195b5fc718c2363ce2036961992a88b4d8568c3755278f8a86af6c877fb3b06b22bfe30282f273d073ae3f3e865e24565d5b569cac8a890072716420a02c2dbc15a1414d19a7f82792dd2b0a1bbf699495555539b29824262676f844a5b647fe382d99be863bfee04292908ff4cfb470be38b0611c0fc4bc8fdb490ce83b90127cc5d98959789c20208ac46cb495ed9f44441bc24faf777138d10c5ae1092155d5343b2555a7c9d41b8343a96c80b03e792978a8316d14316937b1b1a49ad1ca25d0bfa61ea34b4fb9b9af33c681b9a129719e40451713077a42d6188286d95cda8c81fd12183d21d624c3555a049cbf42f8e254a952f650d42aa64262ca93d82318ca53a595d25b49abbc58782bea96d70a8fa25e79add0334a0dea8118e2945dc4c24e3a8382c25e417c4b88f228170850c7075c7823d6f2cc8d4263036c854e3f2d149c42dd903c1003391bb3cfc9f8ed6aa845146bbfd38617a4d12359ac28c0e3080c0e91f74cfeae3c18f2e678d99bf6565d83fb8a2dbc5aec5f00f368ee09aaf9c60825390131b238b2e58aa380bf288c9d75115d95a868b466f6c02a234b29d4c3665a0040ddb02b5e39eaed22a8253c87638024dda32afd29d8dd2325d4b4f147f550103a1120e475052b1a7cb01f51d3c549d3cef03c48242a74802899d96d5240b50d542222fce70817ac15a0843ace9ab16012c42b73675cf8295601102fc56baddfa1798f2b89d058996eeff80d5b447f7736a9f4be7016f67abe4e573f60703461088d2da8ce850d8584017b18d8db3a0b75449da4c2f844dceb9149b67e22d56d18d2e9b4ff4927de3558084af4bab42284e3d3f533b24330fd0c8852a8b2b518e0487f6a8006794739dbeb7ab9d264983b3fc6ad70c18671005d72c75a8c9e97f4b40bebc3a44644be8109f11826fae783250b9e83489cfaf11ddffb9a6969eb490870072a6cca8c9ff56e2f8fc43c837612d4e3f2e46263ab537bebebe1bdf22b32fd69e8d9df3b907b5faebc82a5611f640eba447ba75419225c78759dae3840b02f6ec59c05150b7997fcab5ad0225f19f1b05e0c664d785737a61f6541d41a7ff2b4d7bab5dfaac24ac68274becd5d64cdab88f77cafadf43fdf02b784a1cdf3ab30133d229f0918d8760ae16fb1d36c885f89521816b6b3408783ebe00af389bcfc9e595f7c07069359711a3544ae2bb4dcaa8bfad23f0ead6bb7b95fba08d8e602593c66956196f2543756ec9ef4e2ead5b0e8ebfdff5c39659b2c67a34334498d3c9a967b0dffa06bb5a14bb432577a52dcb53f4d8efd71f0882fdbe0477ece29da85fd10eae654a2d57b212929f2a2e4c1f4e0657ea4ea8eedef4b27d63fa2010516a0c8c6b9faf5167ecd93f86f232916cb7cf398aca90c03d085c4e6434fa5f73656eb27f266e512f4d64a3c7f8fab8aa8255e6b7d14dd7c1c383cc29cae18f71b28be3cbf3d7291b29ee34ed1e476ccb989f208bbf17da1f790ffb06b71820a24a81da40bd72c9453d49233fc8f838c2417bec52c23185e9a03371521f51dff2f9f68df57e7a7f5e0641403d10a335c946b262d8bcc6b0494dbc96f64c2b5b08215e4a92a7d834662be6f6ae55955c027b91233e14c178db41832fe7a9edd33c5ba9de4e5f4842953cf5af6c9850630d0d8be1182d790715af667cf3d806889131abcfca9a6335f933c5535e755f8fd1988493d3ed9b2a99a8139ad6e9f82cd5e8a70d12e8e2f684fcbad0a138e24613b327d4d29fec85361f3cd76043441a6765348fe94254e355be804ba6314d884bb99668bdb9ec0b3c08f6bba522f988ce190612618eb715fb0f25c7dd5c670879e01ce863b24fb4de939efba4bebdca13f5d8a5e39201c937281f6a497b4250e0e36eb5c6f70ee685f7226f129f3b0c0b8564ecd4c91982addc2de69c857ff10bfedc277185c0dc7b332bdbe03972f81ed9f955e8ff72b8bed1b77a32fa1a826bc001044d1a01c0a55e95e74aaa5849312cbbf01d8694a12548e8f01782f0c4618a093ab34063bb2761e00c986c1ab108efe9890381e46f4dae6164863b83e10d98ff8a87420d181638bb6890b9a9ca269f998924bd99ad63b3a386dfa8ecf53b6756f046cb479c9a9c13cbf78128ecb3a87f4ec32dd85c187d30a03c48c1af673986d2ca31aa80e520fa2e4e493d8a3d92d443cb96deaa8e558052f65750a785f082c1f50933e527e8508c0e42009ea88bad161d09738d733f8ff1dca5fa7d2ca1cc42bbca265ef2ab10448e0e43abd8321d98abf27c3a5cf77936c18e2ec55074f6b29815bc4d77098f7c2645d34c3f0384ad2e438aec8c365db77c2a4ab0cea25cbfa386d7b30276fdcd0718e3973b1f986887cb440479c48502cfee8fa897dcb22a1a0108ef436256117bf2d3000de76f4d642ea1621e3bbd61f63a2f2dc19a7171daee535190e420ee759b0368d92e490a321e996e86792824793ab333c7d78269cc234d94a3ea396718b0a853c80d05a2fb4a93e1e2f6e79507cf00d3f3893d6fec173f2a1f9eda93575ad693f27645a500c6649468910e7ac35a951c0b4a46c46e199aad25aaa006def624c246009512857a2d43fd2a99cd40148fd84c9d4135299a3c64683f4674136004dc12e36f9bfea7bfc3043dd1f8d4dce3869638af0547934de919d56bab3a1ec5aedf3d7ad8b4226a3ebc7de82ec592cff4a5213b245e7ce129d9f5fa24888886da85e4aec0a22ff00c8478a5e0e9e0b08e4be65e705fdb308629ed863bb2c4cf3cf29468a13012e6a13e14e9631e49350f7e3f5fcf2714640f58fb760fd2f794911eb82386a204bded77a2c72b6c78e6a8cea68eb8c64a574008574aad0cced7209589196e1b83c6e4125cb9e0b9bb83b5ae741e96e73bc312c0ccc27ba4ceac1c30c96b158698a47e70e5f38db1f2b5374877b9ec2008838a4e728520e14d7680457d1465d33fec564bb3cf57d21c0902627f5034482aa58e384977e71991ea5f4186b30c74fb593606ad28fd342f302160d959d5c17f370eb24b0d4e802234a2378b043f317413d5a2b19f2197709de157f834489876967f628ee2c8cd6e1630f83487eb944e0d491bbbae9ff68488c8e936374005f4cb35a31bf6f6bb69c0e3d5305c20527a4913cd3ed14f24174829ca586ab0a779dd162e8bbd92499f9e99c138ae2b56b1f015a736b95039566c64398108e3484d900e223bbd87fd33ad96bf8a4d87c9070f31ad2898a31d51935728c60589e723ae82e2e880e6610711f7094a0e9b9f133f1422089c183cd9cc7504e475fb3ee4106c37b0bed5c2bb1104d85fb28bcfd88bf70fd92e3847c28b41430dbb616bce35c4e56c9c1a9efaec00bc9f11cddaedb864c1559eba0c20e32d60364b7866139e8c0f9f8c8df42bf16a4c135d05e45ce0d5dd4fb113941643397daade79a1021a159a663c5fc9d3cd1c3d1edeaa3c8275cc2dc237ba921023b8c9d20619473f5f559507cc6747c30d6f5b4a5e535cca06fe83a9c3aad257d4501e3c8d25cd262f612d1fe3412d34a066d25fab48e83d227f62efb7edf50d7135ace7de9e03b92838143298941d627a3dc76386a066de53b0752e0ce120008445b4ce95d5b472eab42d71d00a1dce1d92e2c64cbfd8936314c2d01ddceeba7ea837b22e34b8a2a6b24377c480cb03d4d605c94c870e66c4a8455890a26cc8e518090d3464971d548d15cd2d84d74b14392df8a2773c11d4bf9a59e81e48a56b528d779bfc6ddfe54f7bf2f51fed428d46c67c0f9d7f05642f984eba2a75cce5421d5340dd4b0c1248e9126620c05618892a3e9e2c294c482927b242d7a1f15e318908225de20068c123ff080deb8aa27e403e103f30216816b447b0f46a634198a0cf342d271ef5105cb062569e47f48a87be6a2af1ba4560b6d32b5885a679c1246cd82015108649598f7ea8b3b3aaae74b1795b4291f16e04b4cbb6d49d604531b5bb844c4a7be53decc014df5f08f7959175b2fbb1651bb08a57d527f9d62e84e8f824764be13f6bf0b36a41223a34471181f23250184c02d22c0b57458a12fbd7dcb0b7e56ef6e99f40df879380a51d5571cf29173b0ac761c880027f697dfa9af2883db2f180fc80cf8776d2e31000e7b3a8403a2cf835e2d79106ce543cd1d5fd3c4ac9fc68ae3265ea62ae8c20b1fbfbf5e236dc4d08f6b93abcd5b36dacbf3d5f6edb6f46e5b2f6dde68b3fe6df4d4e61d6cbbfe70ecc9ada977af80b742f79ad5dd7daa1de0970260e5bfd0b34da4facd84fd19b8f330d64e595d716fc9209609137ddb0162de6974e5e1b0bd7941ffd5c6e930ff42ac443b0baa625b824bf21b20fc194cef9e1bee3ec8f187e0e7ab41daec709847e575e56fea6ecb46acd36a00acc1d1f7fece12a8a86fec37e9497815bc8cb3ade4d24432074e0e2941b12c9cd4ba88883673a305eced2a13af242fcf88208c23b6c08ea63f31cc8aae3603440baf402446361b3d655d6706bf130b37d08e1c8e2ad9ed9a1e6ee01f161484872f783c7a0d6ab0601bc89510d85d3ece1a8993a2349fc6fc2b430a9a3552a368fc05d3f16d58ed52d7a5286b447871fc4f194ccaa35c46cfc522c88e5a5a001a7f13c39d81244ffcde65097f05f18dc0692b7ef0e23d3685195d30efe1662cef2e975abc9b72e4155f741b147dbbfb114d0fd7237dd5546398b435c21604e067a84d7bb2bf738bb9f8ba9ae9f7c21fc3c95affa18456092faec6cf40167f0710c1e450f15b50504423a732e32c60df57c0e25bd7213c0d1672cd5d63530cfbf8999f792425c65c5961684b6450437dc77741ac85864ed8d816098ea0072405ba9ff599842d6d04fad19e1d173cfa11e6b6f4a524b685875710576e375d259ba2afbafb732db8b674c54172b5b9e0b27443526ded6aeb1a9ed54ffeae085416bc615fc53dedba4cbb79779444141583f65df3ef169136d377bbe209dd23aedce151a2e723b2fe85e865e0d7b971fbca3a609197ddb2a8b96869866c3532b846abbb9e15ba2d6c3a91eee27a261ffd40251e131bd1a2cf93446e70229c05f594d28c0da57b27afc92a8f8ee19a4c8899afdba2ff791aa8e971c6c09c6703df940cfede37d2b09218bbeb6094414e9522d688d16c99b710804494a53630cf6a3784e1828282c683036eb09a206ace655abaf17a15e9163e1c99ef2db63d5f5e9be5a2e6da2add30a4566ff8b8322011425b8c9cf72e48d7cf152e2d8e207a1d310e5fc1385d0134fe2437b374e909d1052800b1270ac01475f3961ded65126e961448e26831d0819b73936d9848662b1a3706048bc2a62b7ec850e411dfa01b35f6944964dadebc9cc271dd15aee0e9e7ab8ab52c6e176752d228a02c7fbbe8185d3b9220b39a6c066db5a226980db7c3b46927ee9a8845d3dd1c7fa2817c04047a486deecf4dff72587b0e6155494e1d9c0e0775f8ad9596302e76cd5af49b653682588b1b214930600324c19d6cff1abe8037d9cab1ce735aa747e475730d66d1781a5be23abaa29ac2b725dc849437124649ab172d8a22e87929da0450d2d3ef7ffb98309c41a3b527b34be1b73503284ce7bcfe810a1425366009cc1d3bdd65aed26ad4bac8e4705664e385c71c4ca309c64ee7cfea98a3aa7ecf18ef351b101b0f81d24a109749e30f3bfd5c9f0c57fd1ebd4724b7085640069791ab1430ee6d23c80ca7493c8b538f7566dfcba23825378a85fbbc884237dc86911a81900ab0bd36186ee3ddad696f27e52236edcf82b35ec7ce8165a397bb6527fe6d41ed4697fdf5908c755c648b5e6e6e91c0445d703396f85d44db6c700bc7750af18c44501da19ab4c4bcd833bbc3bee22068a89e9b173b7921bb1e410aa0f9b51610422ad2fbeab4af41c5ceb35aa4abc4c52aa31a74b3f28d0ec31888b8c2c87dbe4f70af3800345a800c26731724bf4c21d9e9020a6955e0d749053838ec8f94ca900d4b428906d0ea0c4baac850b2f36be91a7468cb85c4c415d1ae1578b7d71b74975a2e4e31185dfc72d480c15a8a584ad39ace0d8149d541d0e71be83094f799163583e986389809b1112b885f1d3e00b3c0a40aa170f2932880366189c04d3b5cbfba32d1c42e1ad947b0df36f9222eb5966ace7d82793b0f87c4d1e7cd8ecc976890a946f2581c0db057c9cef2d857fe246af141a80d2599b0cde506b168a8925929c644baaeb0107e33f4fe86c14140967d1ae100a1ff90bde88182d83d77da2e73058ab4ded3fd108b263a815bc5ac4a6a414f1741a485dea8137c43900ebc8430cdfb17acf70690330d5d7a9bc9c4089d6c3fb296a2b5208ab79c1abde7c7c07174fb16ce4580219140a836373eb2453b1c67c742437c96e57e0853a5ec5bf15e26663162577192b6b6df41fae8ac32afc20e610c1129de19eaf8628223ea8e4b09652bda90f2d7fa8ee76dde019c983b16a10eccbd3f10ff07feff64cbf9b68f7e1b750de301644cf93568a01f7ee2f579903f98aad578ee3a4f5ba963ca92336d06dd68c1b40115d5316ce20a72a5f90dbfa3a9869faf60cebffdc7ff5cb260ef6631d5feb8c1fbc80414e047f113ab35b9ee4252b8cbf2e8b25c9b9b19a6ca11ce40a9d4d37efe1c2f4214e054f8c2e0dd1cc3517ab95231bc9fb2a1f45a2405171ca17f4485bf6c12d699c46d4c1170e5f7002837fb378f735cc26a822ea2d2b3c8ed50dc106ee0ccd42881ee0ba6f92231c3b8a872a1af4b946ecefffd5e8278c0c18c7997787962c9b610b080734bc21d7b751932a9a0588511459e0b27f16ea05a8247eeac755a942c01cc240f12143229eeca52b7d907f0fb1b82472254878048bb3ab3e95c33bfeb51e058a02c8b3401767a80b20cc7c9e389c6093ade031e2a0c94934fa74d0e0820638e2a15b0193c3e115c5c58581c225a5e39c30311f1b417b225219958d51ce514c721380d88a1d31b2b2ae89142c7f92e53c36c7f2c74d09f46ce577b276302254f57d43973462c1b67748c26e8295e1f6047e8c4788b7428a5538c136b95305e2ec12942c768150701d2c4acb92030262ecd8941176827070869631e5c58181e97e164a15bb49e0303c9628e5c60180797744e2776cda1ac74fc215fa5d75e9876f15584c629c097021adb06bf0a505a35f8129038c0d0f672ff347559d58a5519a576dc0c719e6ef761c32f3a8ed8fa03b83600c19676de99fce5c61f9e7d684a0175d0c9f711e0e00ed77eecb65306675c628d4a91339503e9d8eec8fa290d9438668559f671116cd3e2f48d6843a7eecc98d237137abb14c3ad9f306ec06c37dfb6c645d83975505b0c79e3b4b20bd051bc4d05bbaab22865adca54b6c4c26660501d26502f78027ca168c2d7e35b0568977563376ac81ff19789453d365f8f74ee9dabdff97b3ea7dbb57305d4bea8d8db957168f0a8d0b7cd2402c96c9f56c1233f29b9267fd04d3e7694e50b1fe3ad782a3ff76bed9ac0fbd0f77fb6702a8254155add4beb52b286c59cdffbec943ee0faf8ef4dc490fb30e5067c1259f4721237044bb6b0e4400256f0b02f6eb192808fdb709da9f242bfb661099be605a1318040d1384d1abf409bbf1447f0bcbdb2f95989c13e9e13a0b2284df510278a0f01f40f3092150451a53d07681df097392a2286885af9e7c14597829e2d91dc9a728e25b1502c880beb42553db0253d14c2a51cd1ee69bbbd9842fbcdd22c05bafa30064e7396bba4d468237eda6f6cb5469e87a45b70af1a73435b278a585e6098678774c3de6940a12155238fc5d42fb1355d74e2d587b9c550a93c305908b8c38b66546c8a5ac65fa7e6a9ff564bc7127e0a83bdd473750b5494985be61a6c30ec43c56288416ceeca872ea1c0e16800e059975c86b3f471160a857d7b378e82d81dc362fe7e1b8f3aebb0aa23f8a00a4f414635e3665b27174b90503e41196cf5d2cf12bb6f4922dfdbfd16cafd80ac7c56a6397bf8acbecfddff5a63a7586109cd5c7d09760b1edaad8c7f253f3a51d01af30257b9533304214a4a8f44232b1086792e32575d767cd2a8e107c267a716ad19ebc6ec0646eb9e640429f98ba9839e784b5537105103ee2421cd3f548e7f3578520d9771f043129ebf9d75db68a8fcf06a0190d2e766a44c82492bfc438a1c34fc003c9c812bfe5a8281126c71bcb0a701430e8ce14d0ce2776305fc990635a4fbafd790e184f076c8864fa527870d7b0ca7c539711a33cb227cc3548302b9a753204535eb00cf85a63d79adb502486a5fab379bcab4714829ea6158c685035934cf1cfe503316c05cd7d825d5799d36877376a7198f3d11c1c6f2397daddf20765b8680ced5291547566f1aa7d3864acc76d4490768d3394234604d45ede64acc691ab8514ddcb82a1f2890ec588597dbe2986c8dc20630bf08be712521ffa8c7453d3a19c3499885983c0c5be314e0d37909b62243c9673beed4e9ee3343b4b41c213d83891065cc3a9f175b0c31aaf6106f12e0518956f81cc0ac6cdd717f763493f17b2e8742a24f9953e559d204bd0fae76bc14aa741f61a8f633c9157a3f9cd87d19a4d27cca84aacf9227c89db856ea1cbad626968fb57155a5a120fd1cb84bd1f4a223252d92eab1ff0767d894917d819338eaa67ab8e8a9d5946d2fd44ac1324ea5e8a65b7f107f43d88ea54e2ac39c57b9fc40bb8cbd81e5c272a9ad7f268f39facabe6da07db8f3edd638f4f2808f29067e76ec7bc082b203f15aca2dfe2341b4fe7e875eaf4da36494dc3fa689dda3634c3ee4a533cd7a651bfc80e70d80b5eb4e32c76f4249dce9c006cca3afe6fe7f8172c29fb9e37096b799e61e7b21b964744318e28032518b6d05374273ac6dc74ecd7dded9f1dff8ca1407ccee3520d3b6caefb00320bdcffbd7c8da83e05201c8279e0b81699a9c65acb5f5c12667b76ef387b55306ba4d37819ff985d21bafe703ffacd0ec90f22f10d164a424175a4d077f977e140ca45d0ee63068bad3c2530a05dd853e2e736d6a72dc719b20d1f2f896220c7009c6051d734833203b565f660f7cd9863fd8c73ca3dec09bb94f21461a578478695c70d39560f8e0e54b9a16a205df4dd04f9ac1db9234ac326c758f825a1ce694a8982650769cc27338562f620c4e5950d9103e27baf8de709b62715872721defbbb99de79d6775e0ac9c87e7bd358893597baa96854c0f5dfddc785c2a3794eb3565fa31d35a75f4a0073da64917153b93c941fc34f38c80c0e0f1bb7cd48c74e90dfd178cbf0c4a9f6dfe01dd3dd30909e5527866297d69c9cdb9d67caf4bc9ea2e5ccfd91947320391c85ce28e5b8d542fcfc1c2e2bf8fea8cb4fe9754627fbb531b6a2f3e2c99464ea2c74501b40d9c45f3bfd45fbc55421b15c0d26e7c15ad921a8b21c65054e31d5ceeddd67c818fedf2caa21b39eb7130a48517f9a33973a78eb8a00044a9c4fe393a004ab56bbcfe6aed530ae646c33e9f2c52c34f20fa9e03d330c4a529cdc418125f2fcec2899fcd9fdb907b75007e386f1a25740799698468b414a27b26ac1558693b1bb1955f13b248fdb8b196fcf9c57e13cb5210a5811ba3a919d43d42506665dab1bee27cd512b1b8cd2a75a8d993104bc2201fc71086b08dd00de828685152b87981ef36fa42d1220f218b993121a3c1e91eb3de3ff65a1ae85d93f6848b6c713dbdd599f45541cf4ff7d89a41239dcc3d901986eb985cbb7805a7d4c4e45056cd99c16a24aa07672e472edbacdc80f5c056eacc9f07ce4d5754c7d83bfe5849d47beec9eb67eb85f211bac43646862bba3f7187788fbdf8356bc08b2c93b560dee77c4dde79933a0aca62406a92f4fb6cff5b14ed8434848dc0d7db1d2d3626889b36f5349b92a4e913d3e5bbdbeb7b444cfedfdd79be17ee04c80751ec86be66995d4a7ed9bb7b11a4c208759c8791252a6d8b1315b9d45a3edea63378461f7b2035a67b0d89788240d8720523440f736379749a262a26cdcaf01fdeb8de60a179289b49b557375cb2ab58973c8f663ba40e4a3b29e655138414b12e7cc3789f968fedfefb8c72d055b909f99a49ba369d9aa10a2e473ef5fb33c8044d2dcdfffeade5d4f44ac96ec927ffee6cffaffc93477215561c6242ca3e4308551714373568e249a071d525b93b6e9ba34bace0b87aa6c62b81987d5a4fdd32006191505368c6c77a7ea2ac155792fb97053116b8ca043af15d847c1ed755c0c7225cea427bae0ff498220a6d467510a705c4fc2cb0e9da47a648e40994096023932eb9699564b1d221685be57068cf072cde180d97905cdfa3c7105ae000697fb01f4330cb5261c1f4c24f684e5bc5afc4a8185a57ced407ffb9d9e2f983026c2f97e0a5c0d33748476661d0a0ddf93bcf470659bfe9cd0f018062fd349492e40f72558653f3c5619923ec3a20973bb2cb91dbad8643fbce787338407fd2873dc76ffef777f8f3b5ff3e1ad71bd131cdc48528acfcd6fec4633a65fcfd751a30f68ccb4bf43d6500f3384ee3ff57a8ec1efc3ef7ccfab877dbbe4cb659a89a772ad264f8768f9c7c506e71b2c247c3210925c038e59431e1d927bf3a19b2cf2d08d152901830a66efe120094b707c9718061010304fa5915441c0c85a231f358c44775dc4c3f879739dd477430a0e11a058ada8f815612afbb5a4d2c6392fbcfdd01c70ec3f152aa29e0a0f79fde3ebb2053e1783823c7a1b61d4c7860b2001a494be64e42fe2b5b9405cd3b0f0b7cb16d53c5f5d40934eecc6ce49183c5a92c3920165843e19bcd4f172cbc59dea154595442674b090ef6af5ce8c5082ba0cc4f9aa1f888d182938fc139c3b0c702d5f9c36b529a85cd050c3fd7ff4c0f2f79533677c6d0272409f2443bca09af6b87ff7b4606b6e5fd8f8283a36aef3d180b1a52777ebea9ff7afa021bc8853fdf1c4b86ce0f2638fc3666834865f79ac37cd73ca21d208b5e755567a61947612668bcaa027449036e3186ef0f3430aea690033cc3333cc3333cc333460f9a7f105975db33e58af80c17542d2949ee160ada12ee1f001e00c0a3d9ee66df0f7e060139108a0f29108e4828f709eb392606c711489f52d390315449922449529224d1c43301240187114e614bd39958427fe94558dfb3a4bd1f779db6141fe020825e529c99705230c1e341b8318c24932ae976e36b57f686307699edac9af960609f849d0d16b24ff6346e0003a991515b977f71ca6177b257459327f30525ea65cb302925a9fc1bbd4063e4e68e96b969738317e7ad0e6a49f60d2bea8d5d245ac4c91fe4c293c4baa0428ca7fa93a74fdc0ce1462ed624c47ce846c94b626ee02229e64e2d39ea7857b760e37f3a74cc9b78e21e6ed86213aadc634f658d6e32d2e3462d343977aab56a4fe71b2dee8a5b2299713eab4e881bb328cb42f758ca01c40d599876ee72d55ad00ea703f0cc943fdc88c57f213e5cc8da1eb8018b840e27963eee8e466d448403375e614d49e2d99628ba258e811baec0a2e753adea249f888854e06fb4a28e5f9ff3ee74cd75462c708315ff758d74eafc9fa91babf892183be7d30ffda926e1862ad81ce23bba7c9e553c15dd8646857f36e9a20a0023dc4045d7416b762c56ca6d086e9ce2b08a29a67fa5926375c314aea65589e994193a2fc28d52681e2d2651b7430aad62364bcf5c745f083746e1a4996b0ae1294f6c841ba2404a4f0eed1c73cecc93e04628ec2411e5525ebdeedd00457f5be13de529c137bc811b9f58a2e45825b91ead9323222228b8e189523033eb9027cc79f646276e70e2b89a163edbe3c626f29cd5f309e29f53ca8edcd00439eb1d73d8bba076263c9728b1ee928c8888b0e006267c59136773f4bc65e98d4bb895e6d929f611111115dcb044662545c99a95ae5a57e29aae89ca37dbfe299185b1f029bd64eae44ee28abb2dcf6919f2a3248cd226454dfd76f58944e964b345abce7c95410249b5b324279c1c6d7984d972f9dea5709224e56f38829bd81c5de2df625305c6386349d0de0316371a819f9ff4efd5d6da19c48c1e7783115b88cc714dfa6bfd750c0e90716311e5efe6bb73ef8c653212811b8a78d384499bd14454e75159b39b5aa8ce08901e63dc4084413c4aacdc540c6e1c829d70cfea2bb6e086215aa9d12cf91e7dd3322222d26348056e14a2d94f82c646661dab08519266fddca434973324c28d4124dcf67fd4cd175bfd1b82d0e478963b2a1b881b8038e39ba49536d6d24740b8f1073489e1c4f45efe34bfe1073b6ead494942c86b0910df81d7e0461fdcacf7b0b1b4e4509622dce00396febbf69a4fc8de24e1c61e8c16fe64b72aa8d48c841b7a603a9d99acd9dfc3876ab891877b47c2d2f26f97fcc1c399994dfeeb686a4278861b77f04a2e7832493f32537638c6577fc79a201f791db0122c6a7e4939e15f3a7cdebb16df3d73f053ffc69966eeacbb1c4e3fdb142526676ffe033db8118782c719b98d8d993c6498f1406ec0c1704276d42c9bde9f82c68d37bc52e1c4b83287dc7043b1e4ca6f77eb9c4e921e7803e9c28d3664b1f43cad49e1693f416eb021d73ad176356899b76ba83b3ec921e5bc33c41b6ae0b4424cc3c6d7cc0c166ea4c19cc25ece9ca43cd53e861b68b032e88c556a6db42d840f37cea078bf05b728f7aff307c6f0c0181d188303636c600c0d8c91813130d000be61865bbfc4436b88fbf194a151db8a1dd9f4e94a8f24dc204396e9cddeede5bcf3d1230d3663880a44448408711f41ce10f2013286f4800337c6c025d578cfb31d3a751cd0831b6270a5d7df7d2cc9a6370c7d32cfd4247e4c13c615fce00618b2b8944fbc91bbcba88c1b5f5853aecd1a1bf782353a95826df63aadbbe0955c399ef4779f9fc38546a62e624d8cbd57b700831b5ab891857f2b5364c3c735ed9146902137b090f8d1a4b1f41fa69decc18d2ba0a61fe4f4da52b270056e5881bae4e1844d97bed35a85ac849dce5161cbcc1f66fcc80d2a14d5f369bf42891dff142e4f36193feb73982d053cf694e58e594c52370acf87374d95b3cd6b42e1ae93dce377b854be3da1246339ceb7a245ea04443daffdab6ebae44dd8b253cac9e49a8d1b138e653397e48f9aa4e82ca1f05a27cd8eea9a8c4ac8736b5b84a7664b2b09acce46c92b62f9638d84e3cbb846c577dff411d4945a4cea3313344ce786114af28b6acb46fabd5b84d74efac2c7b366a63d6e10a196ede958c99031031bc320cb72dacf665be14a1bc250e3e48fd92f2f41d69302119113c446308c16fb19c38bad9e070d1df8068eb7404484033680515d12bf2e79fe8ba3df66e5563fb9bc7d516578c996d5631a9f5e98eabfdea325ef8b6e086cf0a29c35f9bd61a565f52edafcf1714fce3bd2161bba4043b2c4fa72750d968d5ce49fc492442d4bb9a564a447902167888878d9c0c55a1f93b019d3c67b68e316366c916df6126aba808d5a9ca4fe136e3426031b683f7b860ad270113cc1062df67c5d5294e5740c968f9cd580fb08b2820064c2c62c9a5b3f713c27d53049597827aab9a749e98249d988455972f4248d286d7961c1fefee44e25498a9a0407365e817a4a9ba9b99e4cde157cce88daf4dbd15c6e0561255a65b5ce58a8ac486d572d5ac96312d30d6cac02d910f7b9fb01b1a10aa36bbd9e5cd239b0910a2ac7fc639237a76ff8870d54506fa2f657099771ed53f869193bc524db07197dd8308596fb12dfe3319e7c96a2dfca54db5775f72734b0410aaa2c66f79ca82c66b283513427b6e75a852bb184e36043146e4539f9c247cf0a7a28f6d978cf9acd49cc64a48719437a78086c8022b17f2c5b5aab3c7b95c1c627f0ba3432997f4f245c6d85da59f868ab9032d8e8c423b55a315da5395a7a0c3638514e9ac232a577a26f13d8d8046162922c96586942ad34e9845b131b99e024494ba5b953f86b60b08189728cd9a24992fc5da176c1c625d0984cfe1cd74d78cb29c1181f20630a362c81058f5739c9b8cb955cc146258adf33e1c4ce8c62192ad8a044d12f46f734b9d0da4da2932449b22bb7b6e01b326c48e2cd9494dc706ff1a423e19e389b4bce23de9d03897356fdad8e0f0ff13d22c1feb2c5b29d7ad838e23e79322669fbc36ec846234e628ac97e6fbfada60d46d858449643c6187d34ebf26543117a686775eef28ddfda4844922833717652ea8d1a1b88283366df89323193148ed0c0c621d4f4569df7e484360dcfc10e6c18c23c61b242b3b35ee6f818e1410f3386f8b05188cbe3a89a747ff37f13224b67398465ffef71109ed877d96689fef1326243109609b2aae9ee9a9ea5e1390072868d4070e92edd9757494b1d1111393600b1854e49dbd72c6ab67fa8938999781db9d633dbf0838d3ed0b96c73c5b9d8c0061f3ca9523753ab3d98d69ef96eb16b373ac1861eca41db2df5e48fe1636ce421ff737f8fb9c5437b257d7c98ecda71d9b84355399966e3feb6535260c30eab979c24cbc76cd42199935b0df3c9532cb14187bfdf37ffe85b56866ccce113d3f426372dbf0f4744446cc8c1aa78f72f21bb639f8c888868c2461cd48f8cdfd227b767fe83d77ff062c2061cd01ab7becccd9a376f287665e6c535c13583b0e18692c5e7e82948d86883e795935052eb760c2608c6b0c106da4a90ad575fb3544744441c61630d86b04fd2264db6fbff881ab6b8dc27ed9dac21ac1111911fece3a0d11e04042222bf081b69b08106ff64b15abdbf24cd35222262051b67287c99c6954cf2a43c304391f1fd67be632187828d3228f7e55aa93d8d063bb14186e2afac6ca42ae39fd818836a258c6a12dfa42737222212051b62387996309bbffa2d52b0118643afe4cd8937ef686064880f1708f9f10303638c3186efc0c7136c80c1986372e5dc894b398f828d2fe4d539e798f23ed5520d8cf18131544006079860c30bac5559b6ac327143b7d10532789455d73c3fa15d820d2ee8e667b7b669b3c676a48790158201001cc1c616beb811b26145fa63d30263b93e7592bb4dd262230b7585d9c93572263b041b58a8ade3c9336e491cb98460e30a665ab05c17aafde4e088888815fa147563273da983c58260a30aa637938edebc266bec0363a8808c1f1bc4cbf091333840860f6c50e1acfb5049fd5e828d29f45ab33b966d728736a4a0099a27e5c9bdfe9675828d28984d379c54e9e1b96128141f1ae4444fd3627b425993ac3d6bb1caa4d609787a50d5cb5a13b09c1a93b8a16402399263ff63929666969094d56ee44dc5ecbf12de93263b49768a4ca924a8f13cfb6bdf75976604488f1b00f19146db404227dbb664ae9613a64c63880f37d838c2295afcbed27dfcd816f4b061844bd690922b9933c436c44611c8137f3f942c0ab14184cbf35a90088bea2967185eb68f9bc9dc16d909c39827770c93dbdc2925189db09fba97efff96a407358081fabd47ad9749cbf02f92a4df144bc54c48597d6198eb71ab98a46b4617d4e885f69ee4fc31695dd69316d4e04559ab2ba565dbadee152c4005357651d6937e97bc310f09a1862ece7c6671c3f3d5d572841ab9a0c2cf6fd0e93d4155bf01904d8119357091a0b395272a69fa7d6ff1af879cd85e36cd9790345c04b668bb4c2b496214a9f8d622b313b51dc9a0a1252ddaa0d53d7d31b7296516e54d325249de81d4908549bf9355e5be825824ac9694a0f1b623d6203560911ca524df30f29ec2fb0aadb4b35c9de6cda173c553d1c4d598b7d2d3d80a3c8de5f6e77e95a7d460459a9d2627e950275b3ae280168c2182069011c48c215b841aab48c39ee0f94acaf77d06a9a18a3533f75d2e8b75932415fd9db831a58def29d0410d54bc77b2af65926e13c453241a67bf7ed23fde262bf951c31449a2cb68f6923e6fdcd1a8510ab47b93187347cb5237298a3d1f3f7f4e4aea8eb951e821a22c4f6a99b05014c725a95c8284e6b7170a6c456534774ffba71cd40045169d9345edbe5a5972707cc48c1a9f48ee36ddd447744a45442a30a4071c38a386279c0a5df92f47b9b3be13c426f94a39c5d0b4d9327c08087cd4e0046f72cbc7bfe6c9576b6ca2ee5496b6c36a0a9ee26d4609b88626d49273dcaca8ddedf94c9c83fe5c981113997dc8141e192d257e8984eb90fdf2b378b52d51a4a76bf495f218b512c89594503964b66e901274d09793d7f4f5fc26c1986872148b9be9fb22094349b1628ef1d62ed50c6a4462d7178d93ff21915c266b4597ff083d6a6a8dcd991d4f3b22c9bc20fd390999e44650c13f68b2ca32c23462517d37b708d3d398050b8fcaf904063514a14965c8dc92f2dd1aa89188a692fe46ffbc632993a006222ebd1daf58db64a29e037721350ea107bff9e417a5eaafa96188f2e56a3a57f8cb9c1a8560df6d93850ba3a9f28888485f0d42a43b15bb3445cb9f1b841d611e36532a88929b5b927e2b66f6ae462036b90ce97a4ddf39019149bbb3259eb38423438054a0c61fcabbbfff99fde43a0de6400d3f90a19e2bc96ae6e87ad7e8430d3ed8b6732549c2f55b34a880031a20833d90a0861e4a50230f35f0405eec0d72a9a72eee2363b4a1c61db2fa12243f856698de0edcc534329d7fb12b5b8732ec9cc7e5c7844a35e890e598e28495c5b4d821c30335e650430ea6d9c9d8f9fa9b0992c1811a71405349e99ee127755f2322222ff01a7030f786342b6c2c4c64b43e23222235dce09a6419b55b2cc5eb8c8888f4a0428d36f029968fa6ca9bb21e4ea8c1864cfc2f294954fd74126aacc1880f6b52b699c4687a20a1861a9eb8b431675f6cebbb461af0dcbc143551fd8479053220e30335d060670baba9796deaa635d43803266bc5ecb27133142707dd6856828f07d150a30c98f0177396ce92ab3619148fcb0ddbe62788c906c95d29eaad98f44bf846e3e5325f0926dbdacd9b4546ed2498bd26b3d82749101b096faa57922461badf3dc2276fd8ea4ff77fb246c0dd3de6dce696f298a308fcfaa5579e8eda5e0e2218f9dd5e1e1bc699a2e3bfc685b1f46fa668b12d638c07a3d7a94ab557e195e2c0e04f63c5ee5c9775f25fdc25fac59532413ee4be48fe24c9f748de8b2ea778d5713fd79de605fba3b9ef76472fa777c1765c49f177be7a6b5db8275ced0559d918ea5cf03137b3751f2a51e3e211f9e0a9d75205ffdca2fd922d5ef4d8c2989dd36749fc74712d18379363d3c4a6244c0b2ffd42e77455d5ef2c2ccbb9e642d437aab2e0ccb25fc23f53aab160ed55c792b4adf184c5dfd1de4e8e1383f70a7a2e4e7e9fa0ad39573016c7dc3a6dd2eb5ad1a55b8a12b3cad058c145f17869939ce493b28a835e9c08dfecbfaa0215993a7f398f662a7ecf2d6ad2b57e2954d8295bd3bee5cc29758a3aee691e8f2945c714c88565dfd8fdb79252f039dcc7b9246d55428a448f16f36f2ae9349d51a479aa3d3746510b1d5120b7d1217350cffd87a27bf70a4d295e927350246969aa56ccddea4f54c2da9adc9ea22bec09c3b3c93946dd132adc892ec5a6877ed4bc97139b702163ac28317cbb09bd2fc67bb51c75a39ab0c4132c5ea88cf699f854633a39598eb363c29caa71b2c56c51cb4bb41544c24269894a969243ef64c961ac84b99e757e624a64fd7525e5d424cc3eea229f628e4a62af4bf3e99836dc84239149c17f74d234830f892393787134d474ff883de6ebd4704dd7b423ee6c1dbd6a6d84a9911abf938ce052cdcac78de40617916872d68a676bf93f452059e2a6662511dcdaf69524574c2e2250f3e89672c91ee2bd2ac13fd334c4f9426bc69ce525475b884bf21ca2928f999c27845b3badb9ddfe161f84ad6d714eaa4e39d782a8e36298f63ebff604a290dfe125c7aaf21810a574ad9a3475638eff704c9b9ca419b3f7eb87b5648f498a39c4bedb07eaf256b0900f6c6c0f573aeff1eee18a694623a57d93aa072b55ae52a8574ae6a18a368bd69496c1c44371d6c498a249d6ce1d92b7de242fef500fed909849be92437d79c9d5e1ccbc8feca8ad351d566993c3ced23968d9ab2cd689ca814c9389941f0b71671c74cf71aaab4463d909077a5d2d59ac94d3a6f30d56a8a02fd3d249e2744396aa4ebab0e17358b30d27f14471cb247b9292c9063d4cd4f789199b31d760ce141ae5996983986a307732a9fe2c37a6976950e24bd4749b348a976848fc12b4bf63444a976738cb9e94945f123d936628ef6a7e2837293626cb90d96554cd5a9b579264a0c2ba2a8b74a793720c499982c4dd6f8ad9a318e8e9e85025c9ad1dc7309c5ed324e17430186f54e4c593706a7fe1dca0d7bdf9e3f4ec053645468deffb0f7217883d31c5ca612d9972c188faec9723363bb905b4c6827b7f968d530b7812b2eff64dd20f66a1309fa4a496c44e2662a1726dbf351b0d19c42bf0f2d1e216c2ce3eb44217f3a6c2a6da7d0b562121e2d553553229584885e4b624315dfe6ffca750454b76f164e9cf7129d0594f36233ae7b0a390681f2b5fa566933a50e833d786ff589b2e3fe170d533d9d6ebd93ac1d9a8249b5d3c2dd9262487dbe7a5f18ae9640221aa1f359e9c38e7120a8b535e52a984e4cf95297f44f48f4948ba768fb20d26640e09c96a6e55314cd5538e70b84bd1e5265700232475e5cae7f1d9355d018a80a57eb87b92cbb2740520c229c5cbd9b9cb848d0f430b7da286ed73f55b186cccce92ad35754c07c398729b49ddafd732304c72b2f230da2f2aa13d85f76495a1335f74bd621bf3376d89592fcc1d473ef73ac6b1182f2a4d316f5f925d38de17f34ea5e40e992e4ad927976c2ec8f8f54fd6c9d723c3059f25173c836f647f8b4d1246237cb785293a3d93eab5a863dd06cfef1abd695185b018dbb36e793d0b5dfc663dbf7eda5716cc58e4ac9c7c9ad158609260b14db27c929eb0488e7935a65ad6bae42b32492e99ac27d574d015edc6457d27f92ec5562c277d274bf395b26758e1a476989b4b7f5131abf0b42d49f2ab42bd2c67327d3f6a9954bc9ea2eec947dac651f1891daa4eae8c95e49ca2fa3c9d25b3299cdeee514bb19ae690265a454d2b2912c36f36d14cd2d170147fd58b99789a9736515c99a1f555aaac6342c15d495a73679d390514cdee650d2f66b29f305c9e0bde6da2d49ea04a1039f1b07662aa139f6461e2d349e144f2b4c67d4d523661cd966afd49f95349d1c441d324a1bc5caf844a264cb39c35d1c2357f4c34bf5b2f7f5d96ff25b66462528f4e1defb74431bb363c6ab81dbd12dffb5f94a4a769e394403cfcc768e890b24e6229ab2ec9c48f9f5549e01773d094cf6aaa8cc4916a929999d59824244e59492df74dd9948f30ea98a6be4e294ed01146eb18aae782957a8d70bd73ee38f929d7c608a38d4f95a82d82f51377a53693909a22fcaa8dfececc1a2d11ea66d9dd4eb144328508aed27def9b8d76a50ea1bb45a67d1ddd6c528628e7282d53a90ab17c486fb3b698a43c21cc27d5265e7e4ab30f0237cb3d65d9b9ab0bc2f49a84cbb80391f6781a4f3f19bd0544b2f53969c92597dcfe01f528172b19ea34f4031db52bf5e3632cec03d1a9abf25973458f0f0965de55d924af90ed0199eb4e9115153a7a287a66bcf0f2e09d9498fd3dbc62785853d6bd6839354be60e84d6891f7395d801afe9e8ca27491dd031d9af3f8fc70b2574f833cffe8450ef8b9239185232457eac6fd0e54057ccd11cbdfff089839e44c67308cd493b70b8d3479ee68cb631e70d8931ca0497edd7d4710397d9a4ca5d1b32c1f4a3a5389b67c369329a98b2f484c56b487384ebcca549a706b3e96f46cd8c4948d390a49a5cd365aecc8806d32b36472d4de7cc19dc8aa3a525764ac13283e14b533b83a879ca6089529351f6152fd18888c80e4444840c01b23e826c0b900064587f73ee8bce374d71c4c78f1ffb407c308000637835d3cfe64e6f51420288a151ed0a96d6790101c270c64ca21e5a05c3b73f1171a9570101be6069b49c2d4b31b3f65e58d389495fdde450d5a680005d38cf598af49df5549a216890800baf97a8e1a41cb302026cc177bf8d8c9e4b72d819d8800908a005b235dfc99149f3a6360b9e60498a16328a85c37512f35aeaef92730565e6e344f9982b7c9410c00a558a9b3c88647aae9c2a9cef21af927f05ddcb488f1df2c3fb0104a002adb136d8d9f88d7b231b20c0144a424707b724490029b8399bb871e77b62bf512889bd39b387160a577d8a6f3b8fe5957c42490aa3275e5cae246f4e306cebe7e41f6297524d6893d0adb12439cde9c484d2977ce3937bea1d0e11b28084004b208012d4fc5131d47f67354c92e0f7c63efb2c1102019040c97e994c850970844b58910bdd647a9a03060218c1e4ed55f51b5dbbc42f10a00807999c71932f4979735c200011d674639e842953f10d17700c23693e6e87bad3140d0b43c90e7631c4fc7fadc138693c736fcfdb1a1f0e605451edc48b39ff8b93f0c933b352b6a4f51117e0f0059b2b55dfc5a4f9a4bb17444929680a7626258be1e0852972a231435d2e2967174d9bf6eef77ac888a6804317c52f8b3967d9bcb96a2e6ef9285fd9252d26491cb848b8ff1493d5ae7229b7c04b2a58a7f1d2a4390e5b5831c59834bc098e5a5cfeb12749dbf9a24a5ab452f9e46b4d62236a1470cc222989e35b31ce50c0218b3b3dbca8563c167de7f49e3a7b0320437aa41164c80870c0c2bfd8977c5f7b459b6392bc4924bc56c2e10aa5e3e5d6f464af1eca028e569cd7b3e7f01763a695acd833caf685a8e5d867051cab404af6389b83d788ea3854611cb58ecaad49451fea69a44232010e549419b57b9e92a720bf2e85369fd6fe9a80c31489e5690e9d73a4e7588a63a5181b227b41a30a0026e0204549325464c89c3d7f8ec2dd9cf9d67af1c31ac0210a4b574ad6bc235be587c24b957cb5e52f26491db1000e501835ab3fa5a83997a99fe842c718acc63de4898db890207b868848057078c2f34a4d5268184727b20b934934b7e0e0442571b27678a7ee938263139a6d4ab28a8a9a38f7be46a7a4ce80231367fe9c96efee1722830007261233cd379749bb9cc48c212d20238d2043807000c725684db93a0593c5ddfd36a38c4403005ac06189463bdbc71cdd93c7201a382a91f8c9096bd29d7b724fc04109b2339f7aa89cf84e018011704c22ddb3d891cc52a26a4270480247241af150a739c9887f6aa4471a1b648c317a0c32d880031266f02ef994a32e4c362222d26308103f41fc0422221bc0f188b24fe79b3cb59c4a1c610110138888f8b8010e47285a6652f6bc25d47f86a31164c66476c25d81322a800132c868328eb7e0c78f2d4305222264f00007236acf39864f65bbaeee8888480534408690068206193fc0b108c64c67e387e68d6951441aedbd528a932be04804d6b93e447426de9434a959e35854c07188522fe9444f0917c51cf1c103128ca181315640860a7018228d988bd4ac9ebbe2380a91c7ebac5f413322a2021c84b02fcd93d6fcc5586de4043806f155b2b89292183d7fec0d380187204a699218a3f44c6c76c750c1182510028e402459189fc8ccb97f7d4068a2c92197ae7c56ed3f24e47dc4d77eccfbab1f3ccf1a67b3957c3d011c7d30eba4df64923464c87cb857bb2db7fee6cf0be24078d0011c7b702e43db47355be5bc1ecabef963738b7a3e3b0f6f8a3997f7ccd5e885073d84a58a7c7ad99d8c88886c00c71d9eeb14377192b7cdc9480370d8c1a89d525d2a51b684f40c1c75e8a4ad9ca35bf420f706a183ddd12123cf63b435cdc03187379b6ef6f06fa4070e39d4be963579edf5999ce1830734c01107c62ef4c95c98a4e18103f11ee08083a14379cc2906f72c1a38dec0a698d4f11782c30d7884a590d1ed4bdeb481bbe8b497356db52419111161831ed463123baf4170aca1945e8fb5931a52f9e015bc3c23222269780e7a9881230d496a9dd74dfedace19178206c5e39dbcf1cbbca62e03c719924da3a7fa246c77a87ee030c3db264b7a46d65f864db6fd922ecbe7fa840cd56c67934e54b3f31e11113103480f83630c96f8d92e9f621963d40638c4709ce899299d7fdf85418953efb524679a3f7380030ce5bef52aa974e529fa85f37ff6bcf681c30b678c1d2a5669e795942e3c1f4e38e96c673a261172031c5c48ce0c1d972effbb21215bb03fbf2b839f9ce3de8888c810213e7ef8101f233ce831c46fa0053a7889d27144937806471628fbccf339e4fad288055309219b1b4aae5069b8d451327d9638acb0d9c9260972327f1a1d1c55f8d583eeaa6c6b26958c15e0a082f9651e272babc4471c533065d6a6e3ea598e130e29983edd76c5dcdbd74c1938a2606a84c49dea49eb09e9b102324e100c88881c20692438a080e3093d70380147138ca9d4941e3cc7a41ef103c40438c0c104e7f2adad764b4a31e258025ba631fcb667060d560187128ecff155226767f3260a389250ae782bfa31353601aad8918a53f9e5cfb9bb538eb98f1da8a0a4647b26ff7f455f46769cc23c156c9345cfef30459bc72bd6867e4729d00a9ffd184ef4144a9eb083145612f91fcf6bc7280afa9a426f8912e6b228ce4fa2496a284e5d7d9a334b4686bc0314477494b47eae6e0dedf8049ee55acbbfd5d2e6a5788292a431293a48e8aab5a313c9ab8713afa56e64e4849dae36f64fda44fe5e255b86edb730191111d9a1893acf65b2d09f4c70626a3ef12dc6264a30717650d51227652b9c2397307c59d4870f11e7f30e4b1cf7d9175ff5febe2f20eca80427c99e7179e311119121427cc88e20c80e4a183b95182d16b7c08e49bc394b9669451245b4796b8efdad5424707df3fbd4e1b352dca3083b2051cc2599a83fcd8eda23ba0f336f6229471ccdf6627df00cf9f70e7634223123e39c6498116a85e9d33c1a744c5e04274495f861afaebd55841ad543ed2ea967dc135146498693f4436c127221eb3f7620224b9e524a854ae2fe07073b0ea1486b9cf073bae6b1218893e63d26f1356f922e44b2bf7d5f8eaaf93335d2c3c7eec04790337af470c00e4224c5e5a8ba714db070f9f1b7631007b91265ac52f2527dc4ec10c451552addf5c86709fde0bd8119234880904d40ee084461337c73bc93911bdcc08c119c61460944446e60c68f09ec004495848b1abf5fc36a6a64c71ffc1017e23fa7b689b7c30fe6b3ead40f51c88e9c8077f4c137cf25cf6ece3c9bb3830fe63fbbef68d3712bbe87a3786659ab97f89edda107328ded949e6416cc1b1111f98146193fd0208188888888101fbc230f6ced868a721f173b8e07d3b8f7c587bcb994bcc3eaa7653a15a37d7d3bec600e33e7299e78fb19dd5187fa747e234e1c13434f63071d1276d63dd98b77923d40d83107eca2af06c96c053be490e5e452d36b9763523dd811074eb04ee12479c383ff1980c3d173df9628e38888889037ec9a59da2cac3c7655868f1f2ad8e186342ed76b4d8a21d26a83796256eca56dbf9fb0a1e0e91f4b084d155e5a43ebe63994665e8c49123598b1266474b298a42447831d6928b7c3c512acc025682c60071a6ecb4ea25645b75dce19ac9a0fcde31fa24dd20c5525317c6a0dba9ac61d6520b5435f55f8d693090044d841865c34f796c7c534730661c718d6ce97dfc3b5f8598e90fda10121ec10c32a57d525ad468fd4d3d813ec080327e7baac26d77a98ef08f151c60e30d47999a4bcb34b6dd9f185452fd3899ac79e8a777881df2c7a174f8e793a1464d1f0c08ffdb1a30b26dfbd249ddd4444dfc105a724ab986c136232dbb18553c24fb652ca1ed3dea1852b496e8ee5370b8d64a7f9c80b6e42c90e2c582e63314487765c818e5a1ed766bc65c21d56703a5ff95712bbc5bf1d5530e8a6778ecba1b4a467b0830ade7b95773c8f16e514540d255f925bc35fc5a5f0c97325b29f49ba7247149c9efb8a1dccdeda6407144c25dd754a7b69d37a4e06901d4fb03f37c7d64fbea8317e461a2b40638713f8bff5143aa927f033d258c18e26983c7fb353bafd159d0a3b98b05ebc5678e6e61c63762ca1b039fd6a73b6a7cf0862c609d208b24309f78efc8fc5a49b2d33490b2cb023095e92ab3589b213bae90e246ca26a93494f3cbebce308c91b3b255b97ec30421276f92d5a9d101a32c90ea6b0a3085efb64fae99b68f60b7610011f4b8266c89aa99c0f83578b9339797408a3badeb8df74ff126fff701f1d50828e607c9727fc4287accddd82348600c0093a80f14d6fb81fcdfca2dbecd6c12d6cc6a9c3179918767e37c59839e6ac40472f12b263ca5af92a4af93a7871924e969593261dbbf8a4532a0bd1396bc2051dbae0b6a45cab984c1ae96041472eba3c9fef3f59b15127830e5c24f961cdaab279d6948e5b346d266f0e51d19f3d5be0f99f242ba3f8c9a18e5a9851d4ba4c6c57970a2deaf8be92624575cc22414c0c55f335ebfcea9045e2e9dba7644933a555472c484bc12c5ea96f8a1916b76fca26a76d928dfc0a6fac2da746c615c7f8a87ae9ad3d96542bdeb8bc7ab63a2bd60d9aa3fb6f4cc9f62ad6e95896dc3c445f5a15e52561c2f5924ad0910aaf92a45a62ecf98e411da8309e5b7776e8a1a0e314ecb9271353123ee3673a4c717c1fc9bb93925c9623e828857134d8da787c056f49b1e88bf6656747a1b9c7641b962576a78842ff12567efa436c7d3a4291dc79a7c2540e8a244fd7a92bc89a57a6e3137c7adbab24dfde5baec313c750f9c35a4ed56fb23a3a516f36413bffa6480f278a7937c97cae1ccffb4d3493a2e9428575edec8888480f1191116020032618810752a043134a9514c49318f37fa6474c4726ae123b66b7d9b4c73a30a1e312a624a8a574b3c15f333a2c816f49d2e7b5c89b70a2a31294f06fbb53b72b633c131d9330c9c9bef3e5a54b13d32109a74bf2528dad8e48782725d9f237e76db971a003126be670d164b99b8c29113a1e81e5556f86fd205bee107438c213344c920d3d6791af8f6c0f336ed003083a1a614ebae895c7f41f041d8cb84a432539e96b4962f6e858843973260ba2194a9a5a11cb74a49b48bb021d8928af99584936afa89532b28013e84004fa3f166161a36b8cea38842796498c27252925d1dd800e43903737996f357cdd2f44f256e2a5ac91bb9527c41e4d4c4ad3ddebbe07f1a9e6cad01b41fc627db28e895b9d74206a8d12b44f364ffc181088db27b92dd426f6fd8395c4774f3935ffa6d10fc7cc2c553d6124e5eb431ad115aa64fdfca9e103a5964d9227eadf3c7be87334fdfecbc192ab07ac3c6eb214aff4340f5e7fee079337ce8778b0b35b422efc2e6de50ed68692aaf2adfa63ca0ed6c698f12a9a7013d6a1f0df697ab3e7f44ae840abc7d84afa75f1994339192b9aa19f7f241d7230e63c512565c5eceb75c4819cc91c4af389a9579e40071cf27bd9122a5e9a556f444444c71be88ee94a7be6110ce87003e39599fc2e8ec7bd75b4414d82acd49767a402630042071b2eeb923cfa3b7649a98e351883764e5973f0e85c51839ac433f396cea8b774a421dfaca29e4c9474a0414dde96abbffec27b1d677864e64edaa0ae1fcca3c30c97aedfc5f792ec245b47194eb77a72ee6e62c2a7830cdac929af630c6619b10b49af942dd1218636c9f5f3b69a3f7cad230c697091ab8cd19a8beb0043429f601b4b4c270693747cc10d376533e5eaf04262ae98637fcf021d5dc035dd86cdc77ba9ac830bc4a7d8e8152fa7ceead84226fce76d4f256dffae05cb9218772126875d5247160c173475c7977460a16cc25485fdee0a73e9b84239dd1d2a8966f244a4c30a6592da53b6ce967fb33aaaf064091d6a995e6c261d5438a653cbde72720acbcbd03185ae37bb5cba24558e94031d5228d2efc4dcf1672ffe3cd01185c4ba0c61af172894a4dff14f926d9a489fb0955882772ad9528ada0945995729f1ef928e3c2222f2e3878f1cfc40471316eb8da2a75a67313f4407134849fbe05942eb5552c81270d1d5e029cd3f99b8124a4f4b7eb2e55f37b12494a65d6d6fde49a32544071292a4d87789f88f1ea3e30888862eb12f53bef82a880e23ecf7a1ce82ee49f5c98888880f1d4530860f6e2932bd695b3a88b04bdfc9a55299de9b32720c83aecfef9b4ad21d9784d126ff107e2974889f2318596de557787eca2ede2007308cb8147b71454a437e835fb8d12f49c1d71de91cbec883af45e93157f97b44444488192322229ea317677bd5e5947d54531d1111d11cbc48bc94734f2c29e94a9d63175ac7f0b88c6d6d63e9e209b14e27a78fe9d7930b652aed55b0984c4e212ede8d66a767a99f53ddc275b59192fd2fe5b485dad91a35739fa3169ec5247f96202dcead4839c1636a27db083966714a9f5b952f4a3cf932841cb2482cc624f335fbe4cc118b4fb3d62dba49924f9d0316b4f6bcb96be802395e91893e279ac9ad2f33e77045a13177ad40c7c463ad7b3e39fc20c8c10a4a5adf10f15352a5ae8212c5c43d9ffc2a19a38a4d12d27be2c7d37bb602395251cd45934bf20915e77b96247b2c4f51facdf856398d78ce8c888898338525bf7b6e1a493b61721ff810c2023382780c80985102119112e42885292bbb9bf4492a0729febca1e2e3cbf295f828ccf429e9c43c9b47ad203e7282acab20c8101588888882137c33e69366fcf259460f20fee30c334e289c0f9d82d5dd897be7057280c2b38dccd12da6d8b15220c72758718f1a3a59a624d79e482daee6f075a1237b03710be4e8845b413fa7cc539b044d8f0de4e0c449d75366db9996fc01e23f7a6020c726d01c34a73359d4494a46e5d0847a97297c8ea9aacb44f6f596bf6251e55223986846ef55ac2fcf461bd920371011d921db3d2ef15d0a259ea2c7ae243c440819490e4b74a93bbf5bb1fa6db34a184ff8ffb9ec12eb6b4309d635ac9dc9272ef62993285dd4b3b390cc5e26e53fce40634806724882dbafbe8d0abfa39690489463b23c39b7632b854644444a8f1e374083bd2191989c4e34f1a4d4f9a48c88888c91e311a4ef989882054f6af21ac8e108cdaf4c30b184aa10ef888888101191202547234a3929ba329a26a16184a697c418c9b039d98f888804d99133811c8bb0dc3a8652d97c5a760e4590daf91d69192ec53b92885e5b3aa754ccdd2b4b200722eaf8d2b16b32a5f48f0d7203df334444ca086246072cc72114f5ca154fb212347a0c29e31f90c310658fe525cbfe981feb610e8d4260627759efaca38578040484287977e8e5ca2e9e1d042b1f2c2be5e819fdba8f17115181480e41f4269fa9fd9f94298e3d828c9c320281e9a53ff1cceb3d8188889020ae821c80f834fe058b72396383f000081a7ff04365d199cfdb1d06c8d8400e3f24c8979fe05973dc2873f4a19ecee4a369a4fbda11ccc18763ac4d8efdd375a5eee1b0ecf999e47ea9f4107239f4b08d5ca893cf548b4a8e3c24586bc8d2d047f0405d58868e1e7521a711111107e4b803bfb1d16d2bbe5ed2ecb0069ded9c27b425c2327c88889481069012e4a8836b31bf92e7e78c499c0e8b84497fae95a4b129c71cc80d76c936a597ab8f0f207b02325290430e97a8d3275e6e994c1a08e48883ba26516a724acdce11c801874c3bd879daf6933bfe811c6f50b2436d96e95f6634871b8e7917333f3f2222d26900c18088481a4036c9d186345cf6b9ace570fa8f88882820071b72ac214146a3bc05fbdeb4070d0211112121c8a186e583542c2971472f1f0912c487f8d0808848bb8f33cc58801939d2505a0e35911335367472a061bb7aab9bed98c7cc111191213e7200c44790112114c871865a343bc247400e3350fb12b6bec173f6b10c658d397ca790c1fde80e62ea3976e58ce1897fcf12cf505bb5c921063ee57787eafc2729382222d26684812c79935829d5f78649b71960284a6cf56aca173e6596e30b8ec409ab613188a6c9487b901d2939bc50ba0b675259e7da98638e2ed0122706ed14fa376a3c30e3c78f1da91c5c6052fe3b78febca7d58c32cc0872c6e5d8822272a96fa23432460e2d24ca46c6ca49ce480f33864420471630e953ba4ceead90cc488fe363c80e1102811c58b8c46c95cad241e20c8888508e2b7ca2279857484bf2ab233f725861db8aca31bcaade25f7810f2120c85185d3964ba56e0b213a15f01837b4c48d12fd9e427bf294bce3f9c275a4903029d7f1d36492e5287c265e5ff265c8761f0a7ee797e4192e5694ff84aa3fa58cb1b4d682ef84523a295b749224dd926f02b5696fd67298a6926782eb06c86860c125098fb194573cf2276add892b0ae5763d5ec2ad48f81cbd2b6e9a5262567072c79c6bfa55acaf169bc7ab53bc558195e578613c6dbc3b157a4876af6ce8bb0f2a78d1cf797982a8784ec1068d37dd31056e9af93a255f5a4b615f1e4de164354b2d294c397c495311be9a1c855e11af3195bc665014f4a592a762688c790b85e7563946fd646d624081670dbebabb19af4fd866e14e9394f5c4baba1f6126db097ec5da92981e7ba1e584dabad9eaee23f64d24a5789ad42a4dacb2bdc124590b9dcf447e39f527fa98f8ba82965cd1cd4dbf44df3164deea24af7f2c410555cf2958fb578e5602197d0d4daf9745564a98135e52d0a895724e2791e017a3a5ae44fba692e82fa693928a46e24a929d2c7fe7fa8a42c2cf32f14c8c99842ad147f83ef5e1c47f97f338a20a3d999abfeae46c23ccd0e4164b8a995c658419e357916e75952e4235c1a4cae15404abed152f59d7e66022be2a3361b3467dd91071189b93deb5431c6ed96973e69fc50c71e7c88cfaef941c538824d9f267ca689fd79284482fe4b64d7e2e4fe520be4b7d1b63f9fb5a2988433e953cd1faa9aa0c8466fbe17372539c4902e21c3e68ee31c93f98d366bf37bf9459927e60bae634fe4d0c97b20f69f2558fb29e753b1fbedf94d9a45b62caf79065b7cfc1654e8a1d3d5cd26bce54e26bd59a07eeb2fab5e495fc493c6ca29b333e79872b4757fff62de4b403675739437950f7b00e4ac8269df5746e1d1d9cbd32134b32a9a46e0e7fca98a56de440a5a7abfc1716f638a0c19345644f6b07077f4a3de4058d49a8bc01cf5abb29c665dd4237341267e14dbe0d499b262d19678362c1841e13ad98d36bd0e4601bdfde6258590daa79869267a366d5342c6752673cf5d06054dab967a9674def0c5ea5cf9d2daac64e9e19b69824937c93338d796548d6aeae3585cb793c326c9298d377fdfda4f1c6b0a579eb4edb57064f0c5f94d49c2e5de87b1706b336beb5c40e0cb6858c998d26cd96fb022687b58995367ca69c173217dda84ead4b52d785634c552594d8d8858e0bffc7efb1f1d49ff62decbd91f96149f6752d58f91f4f34f49bf4b12c98339428f51f7368d8b0708573937d4f0c69d7ae4079bea0d5ffad256a56286f496b162bceee6655e8e35b7a2e95b56d4685533a3605d3648dbbd6fb9d149342e2faff4b4568899345a1fb6cd9f9d146ec8782bfc9f793a72beffc13dcb86b71729b727627681dc7cad64344f62658a2b55afc4657ec4c68d64d37df9878b15e823d155d39a9848493d279a479923b4dc2fba7f1d226399b1409a592e266ff23f0d5926e97327e491aa19857bdc3440314e1dc759a4e4d4a239a018870697cab482975b70c63d1dca02946364f4518262b0dd1f379192d138c273a098c37a673746adc9594f94579c3eae54f69e37b5f1465935495f65e24e6fafa6e31e94b9c179c5592c22f899999de051d5bc563dc253139baa0730ab59c63d39b9c8b3a5fa60bb5d1c45e71b16fc86c9dd65bbcd9e7e1e4bc3763da02b18f136b5d62895d2dfe3cd924c9b7428b5c67f6cd720c993665164cc99a233a7dca1e5364a1646b68682b13a55262815fcca13a961458786e72bcf5d77d969457bce157b34c867f0e1557509536e4a66492b07e2b4c8d9b934d86d0a4b382729199f2d82cab575189a1b934846613635524c60cbda1cd42c635157d12ce4db2767d5751617f96bc0d6a41da3cc535ad9da25d6da99942cf97d349e1196e2ea5e8375c8464fbee5648b189afc142c9e249aa519867ce3565a2604f7ecdf9336da524a1a0c7734d3ad13fa9041459a5da7d699674527dc2dc7a995258d633a1f244a255e994f79d30b45eeef07b3b31cf89cfec6d66f24df471ebf3a352f7a3897b362521349d09df3f07134a7ebedb4a294ae34b9463a6d37db7b7762d61d656c9345952092b6336a144f661e7c44e615127c924ca594476a3af9b9c249104a7977f669f1397248984a627a78fba25e5db21d16c8cb996ade6657f44efdbed39590ae5ba239cf3d47fe96ec45effc92c57491d1d466ce9f9c6826a4a1e2f628dc1565ddeaf265644aaa91a19c6e325d9449472936c559356ba2222d1625ad0704b6ba287d83aad3596a4214e9e312761d348e50b71dbbe8915c3f63f24c4eeaea93f4cf4dd83c8c37570ef685e5d09e2a8919f4c12cf4322122310a7493994d89d78d3a518803874e62426c14e2e0d1f0762fc414fdbd79f2c539a588ae10736ddcfbca849b12fd3074f334f4d92f7b7fc440c3efc1d1232ef62d11917630f4b5c8aabd271535c0c3dd8137e351b7b52fc744444240f7efd7fb547e578c8e0c1b34d492aad24794d0bb9432668c78fd9a44c4912b343a659deb165e6dba63abc9fad8227d9d2801874489239d9d399cdb0760e6e77fcb4efb14a799403268bc9d2d964132fc5e240bee6db7c3925fb8ee0408af4cfd4efe6fdca1b56fd9ebbcbe11f4edf80186e20b4c3f5c58eb4ff6c1b1cb17872da5131d850f694542d6e8719d780aba9ac988692fefc2168a801354d5aa6b9648fe2488f34e4a21e639f98194183186728cf44d69a9c36c39f73e99414334f30b11f6794018234322044c89e60054284ec196294814e713916e3971d928fff1680a0031de8c10331c8e0a6ec911bdfb6c4ee801863a84b9849dbac16c5f06f4a5ad9d35fdde7464444ce2a0ca65613ef120c659861c600c4f84203f1c1032068ec10c30ba7df144ca2f5e27a427e788f2066a0185d28daa52635474f675dc88a8888c1053aac27b953c8764962437ea021c453f0e3870f11888898e10307626c213929059f333b0dc96881f697ce3d99ac64cd1192053c346d77fa5db38fb170bed097c14d8ac8cc5e41b99d10afcab9db99056258e18f26e5ce5e0ae1d1a40a7f495bb84977425eae0231a8507cbf75ccf73ad91306624cc13449b24cee570ac490c232a5fdd1627fca981e1111398118515846df5755ba4ece1d28ec579e32e33a22225202319ed09875bfd749d125762322226238a1d4d4fc133d670f51204613ccb1927cf18f400c265c925d749e0d09f303622cc1bfb9d20c15cc80184a502c8ca52c212ec30362240139c9738c59f9e78018485853ca9353ca21dbc20704621ce113a3da450d161111213d80041181184648b44df9337cdb4ab2c8408c221ca6b77943e66c2e8d1844f053f894aa256584c730c8f8f797525f6118dc42ead80926491a0b065179b5b1dd041886477eb0bbd4f27a79fca20aed41336ecac317c65c8cf165a6299689011ebd78c7e62c577e4d9ed320133081444306c683175aa524adb5d7276a6717a524cf78d078c209939e71c6fe80872ef23c256ff618fa83bab9387d25b1ad3c279345c2457e3176aacc07518b6ed1ae896165b2859e62b9e5e1e7ab36ed3dfc0c338e900cf0a8c531bdbe4c0e9ae671fee0418b72dd2a6b667c44360b74939c4a8afcf550b70a1921e0218bbbdd733dd56d364c38e0118b53a61fad12b1589f61919ca9d1515392b63c7a45969d96824f4da9ee4078b882cc1d0be719d6d16239e0d18a523c337f933fc737c58315e6f28e2926b35771ce14dcf4acc243159e249d98354995e8faa722b9939f70a2a7c8bd242a4c5e21fadf9298a4db9cc224f88e9e9476989498e27082859d6f548ae7a7663c093d29ccd0dd78bd73795719859dda62c7b43029899b2166fc5041193c4471f4602b1f1e85c2f0d7efa1841293249880620d8b0c9bbff2893fdf8c8a274b72a7db1367718f9dafe37ebcc7a3135aa7ee18bd3f9ca0ed2fbd329ae7b4ca26fcd9cdac165b2d42d304b3dae993709d39c52e13fdc7ca26f978a8f41b266afbb4fed2cc147d018011785ce23051bb616942547421f0b004f69792e00e7cb4087854c29d532d694d793e2694307c92bf9417d929eb4924fa650cbdf2e9f38425918897a6984c427d4c8098e123043c2271cc270fae6d6249e220e001094355d589a97f84c9bc4bf0184f48dde88c2d63033c1c4199dc2b8f4698536145638c8ce03287c7cb175d4461b59afb3be8b4953c1491f4a94e924c4c51fae48c3486a4e0023c127127a14326a94b4a92c488603ebb94983a87e8f2105b4c1ac6e2a5d0e93f1e8630c793c3eace39222222e4878f2168248f4238eb415bee72ceee4d10337a0009e2438068800721be925a364f3a87f800e243c8b200081a3d4444caf032068165ce25761eef4ddabf6fc0cf486305cb431026ab6b13735da75c07c2123f2dd7939b185a407c7d2bd74976444464480f1cc809c818e2e3c7192a0832a4063cfe906478eb2531ed87c2a8b5658949bc8cb30ff97dba4de92b1e7c404b8e277517736e9cc71ebee89c24f55c0f9f5e30fd2a0f9f4294074b2fa538a0017840dc3cdd62adb6427b07f3b77bb4942ffac3c60e4b05ab4fa2e475602d06f38c954cb9ac15f0a0c37a1225d895fc6f26c4630e85bebc6163b4da53cf0f33cc6016f090c3d25e576979d6fc7f3248c0230eb9d8c6cee9bb63c5118d1f1ee48ce30187739c13337644e6b6e2f106fbbadd5de62e95a92343f60422222343f60c119134868c80871b3ef51c2a257dcfa3191e6de0b624d9bbcef16083e1962f9e244db24997352c5e51e4dcae15f050c3b777e267c5acd9a44f439329fe47955877b7a2e1cf6bd5e2fa194a52b7588a999afda219c8bc143d4965e1b796321cbffea253d222bbe14186def534d35553c85f3cc690a68f26b45954fddd27c0430c57679d504b7f62cc3f0c67faf794db3d621e18126de4a44a35fbccf0f8c210216804e9d1e3003cbc6049524a3132b95ede982e546247694d577181cb7632975797721e796ca1cc95e6299a46afc95a402f89a671e2a3fd9e0534c97fad498c4b1b331e5868a7cdd2cf25b13fe6063caea0688c9accf246e9ed38c0c30a5dd59688aa011e5528860a1aa357125d43d303ff4010e7811923283ca8603a41f6f35ca749a54ee114d6524733f915bbf4101181010f2924e6f6ff349782808c6e1df088c2b5edfd267da46e4cf380021963ae635e134f2c9b041e4f305eac9fb7f8f84d1525f07082a37f52fc143dcab4c7a3099e4baf6f7e0a0f269c3ec7bafc7f4977353c96b0c9f95387b09f8712e893756ecb3c4709f24882b3711ee6f340c21fe3e429fb1c1e4730c747264ef43dddc6c308949c2b6e7b4eafa97d1e4548d6d2382e61e17a331e4438fbcfcdddc79d4f9d61a4a55e6ed2091bdf83c2d8243a659750f22dee0846965534c6a514189497e469b2bd4699f42f4c2d7d337d65fa6a7e61872f988acaf79e241363da56d8d10beaa3c3583ced4b993ac20e5e24c555e4cdc6f88a294dd8b18b36d24ed3a8d57acaea829c91b314323c49c2c98e5cf895c7d385ceb9c1eb1db878b5e459eff01a35c95b3c7792eec62cefb0c53177fc7ca627b530c854b43ff9a4425d5a54727277188ff92e6b165e8598894d91451f4e775fac8cc52968c813472c37091958d09d62b00db193e48e9760c72b521f2f31597f446a78821dae48b213a3849dbcf60aef6885b92d97e5e4b0dc7ab28315a534e1937b27cf3ce22a8e9daf4ad2ffe6ef0c1a670001010e76048ec0a8e4aea2e1a1a1402410884461201018b23b2d0163130820384c248d0543b170a64ceb031400004c2c1e4c3c22242a16121c140e0783c17028140c8602814020140a8783a1706b1c6689de1a7b6fe0a978b81dddcec048ecede675b908cb654a248e814824d07e23252a4f03ba357858320eea88b4b85ba1522699f4820015c8d362ed265f305e3d800ec47d61514e34c3ce14878e6c63b12db962a47223049309bafc64baf382f05ace6311e8d8edb5c58b1cbf322e4f7ffa7dada483e759771f63e46a0bd328208bf9d1fade204967fb32c4c44bb8bba538a49ab465c8e22f49e3f79da15e33a644180e8de8e1280782fcaf73c299ba4174ab2a1fa7ddb09cb232696843d53b4a8c633aeae5a782c151bb5dc7141afda5cc2474fda3c738a6d120af87e4d2c4ccca6de73ae9a3a4e806e5b529194b9bab82a80155fa7ab38fd0974d403aee3267a98259a638cd4cc48931156e10b4c7a6bda7d4c1bf904d6741410272852aca823326277de7bfde3a9d5f73ae941358bd228e15799915482eaeec6aa6413b696d92dc36efca90d876b1b9255d29ee8d1bd6f24e5fef722742383bb407f35d7ce480823f1d93beb68045cb35c60577c5b63a075cf7e4f9d98bf214953499d0e75e7ea6c90ed73d060a90363e8a1067321ad19aecaf77727620b0ac8f25b9350668d6c9a191eb0470b9474a92ec9f52ee928c80fc4f24ff8ea7c4ef510e19c74f1c2171eebdb9b0d660d289acab0f0be4d2662998a47bf9abde14f538c72eb35a8f01caf7748cc202cef36159e366092600f1de60578dde181a38d25c8ec8daa19210b02c2a228418783e8f1561759278bca7813f67c78d8c18ffdef60b2c804e72c88aeab4907eb730286afa428d54f70ed075f1d11faf168538e5c58cf9f7233a233a30643cfe52ab53752b172e5b294e4e61d5b8a335053042c24db70dfe44915ef32e39fd42a984f1f74a3611ca00ff8535c16e0ed85c4d42805ca77fe944871558fcdcc8061e8675e385e4b3988669825a5b79142d727f3fd3a805003c95b4653d65502253d0a1cf50e953bba9213772b91d21eb62e3918f8f8d1db419302f7d1b895e0fa920ed9d3e1c85097a4fb1af330b74edba7637d9fb07f2ae060bf7de1185ccfd74c86199559af259944ca7942be49b588d992d5b2c3cea6e814f5523a6ab341ffd4756213262cf16fb4473acaebe3b76a1ae79e2224bc2a5f15f9f686eccb0b752db89200d05cf778906e9b814ff59dbde0d7724d6389a4d27bf53e582390b370e132ab556f5bcce5024f5f832513513b5da046b1f08503b9707769e8283ae38bfce18b549f53a6d445b33c9fd549d57b3486e0c6f9104a353502a9d2b438268dc9fe8945ba83980ce9ef7213ee6af616a571cfcc9955cce5ca7688c319dd4f1495992ef85a3f8bb627a403185c75c9571e6c03f269288727244547980785fad0f71bfd4a9b93aad62467ca10b5d21f790e2166991120fda368b5770f7bbbda3e6b6964441b55fd13919a3397f75888046b4fb0012c2c0c6bf0bb7c1c1f66957c9821a39ce8fb65f9f73d66289015e504262ae7ab8b292a91cb235330e74bf0b5f2450f605452e98193cf8fc5da1e8b78bca8f0ebdba6855d3992a7a730a4fb53ed79de43e54d873121f9d3ce006af5fa342483939eff99e091a0b4121056bd802f181f0006783a3ee5c281f719588cae6eb74509d7e92755cbcc7c9f663c5133a973dbbd1c4ba07b63a2cae4268db872f4f31e29978a20a070b279602982967292f5b6c0f1534af484ff20fff25c0570b7f1809487a3928db2a8f923ac343b6be0dc15bd74019a01e345e23678d02f789b919c85dd4c0c30e192292d1db6b103abc5415bb3c17129d84b49dfdaf453381a1b371b1747bcdb29cfe392cf44ec77daca14349f05fad18840b00087d4aa146b3855fcb5feae4a7b1122e51c786730303f6355c49f6cc6710400b1b8e4ef7640060e85ba212e799580458848b1dd3e0243cfd78957c7def284be15c8aa4ef13a2a1e1b27ef6ac9fef5bc0f34d278572fa9e7981361fcaa23ef7c9f81623a4b1a9ae7d5d18f8ada59010cb80ce4d0dc316f8634bfcfbd6e58270e3be553f7b4432725eb4d1cae7398b1278a09f5bc62609a042c1f9e6d8e7a8bb11a491113c776f812415d936fc065798a6c1ee0e02f6c8f1802824b0be527eb4a8be29b02e31e2782fb8b7844715ae77e234346bfc54ff8c5eb8cf6e01392d38889c80f965b0f010e6601e1eec0be0cae0efccb3cf035be1dacf7ce84abc7508fd204055575ed6a423cd791dbe0fba1a44b8fb89bdf8c2a7340ed69d883a76efb965aae8af3c2f99ece431f1164f793748eaf5d785130f35ce1a07f0f6df539214c6070c3386df2a635b4dad23f800157316fed9754ff12050fdabf165109bbdc459c18e1260a170682691b92924debb745af349ee22f5d68581144ce1958e3b4b79aeeecbbb94a23dae535d63191a2fd6954c57fe8e903a9a5d3deced0b6b2b7cb94a644cf974fff68eed5b620c6f2d80de6b5eea9dae2328522b6cba850d4d106cd2c0f2abbe6077a69fef98fee447a7e43cde5afcd3ce9f9587063d0a8af9515e7b43a50ddf1c09a8a1a3d6a5d8f466ee663f55b2aad1389dcefd2699c9689eab70d2e270c994fb768df1e377c1fbf2e9da45fccef7382e08a4bc2b604f9fe22f586a910a9693eb789377e4ef5071f3d8e1d4e22763af6b16ddcfa5d43935c3ff3374ffa05d9ac610390344c1e00f5b534aa08c62836c7a74c493ccf08b867c3731c7dba0c4c4e4a2c712df443b2f00e13cd0bf491b79e97e12dddd33abaa7aac536703d33635f04752ab35c3286ec8aeb9277936b159dd35d584335d073f83a92656595af7d7ee1a1a2e551857d637b7ea3a5af2458a8eb97adf577c95d43aa57db659d06a4996fad187e779c5f1d388ad922946fcfac86c96150305dd9458014e00116efc10de669eb44232b3b71bc6c9e8d0db160ec2495a2331acbd71f14bbc7cb556a7e4e1667e04398375ce325d011d76780b8300dfd8f310188651da85c0deb8dcf53371ad72d114610181a6f969d7a5dad8681369d59cb0a7564e5ddef1b2c9746a23fff6f84a498db2ca19fbcb3799fe46465ca54245f46db86bf87ea038c249cc39cf30681199d9b2ef53e74b581a098b712c59b46e35e80b4b58823e777c2b47a5a74c2d9a94c865d2655c9a548dcf4113a3dc567b21b802ffe2c27d43cae34b171653d579fb1581dbc6730ea6f302819e259f3a93bcd93a6f0ab8db763dc468d3d64b9a5fcb7486f6cadc25a6269d6a6c247c0bdb90e70b744edc6200249553418197c331ee2e784a5a0d57be4cae5c30f38bec3b7b0a4a697cd90d347dfee1a2ce5216aae72e1761be0d0d29d48f5abf9757a0ca9d047dc7002dbf1f220e01cc07c8e1531a80877d778d7c5ef1a6134a4133071190a8bb0e95340afe0407b15446515649dd2cc59b6fdbfc2e30d0d865c48d82c05af8bdedea5df818541750dc26221a76ad28ce1e16797e2fd5f92bea49095fa98121dff2bd68fc8676ec82a9a6fb5fbffd4da7da1fa29213631bfb429779722bfbdc0b2a5932347242e2eb9de18f9bb1196856364a8ca9d33a658819d9273acc31d22ebd5bcf84a6863d70065c27dc1b9414dd83f0e881ab3fa6a40c34828c058fbaf6f49417eb14ae932efe5f323b9971fd570145e158cba2f67df981e71eef0f19d42929d7242829acd8af0b402d157d9479acfa7f552a0a3d7e05f4ba8eb8feb9e56c5b6d6a7e1f584dab117a88f9215af290e1c4b28a3cc9f2f966020db8ace5935bd74158cf21c01816f5d86b758fc1e874c511348f26949afa03e836edfeece028ff1bf9d9c24bbc7a2950a4a3a730e8e7b6ad70c58a74628f169edd79fc8e14880ac7760a96b8775cf63ae6691b9935a2173e8c94dd5395413e9126b36af4a50bed5226ece42562b8017701d061d8f7391c92d146f45bcbd1ee2140fe5f1e8d50afeb64894f61ec41ecbb3b7234e78ac37c8f842be8c62fa2619cc1e9f60f28d847234a14ba3c5ff5bc7eb6dabb75eeed04032eaad91273f9666e68ebd8e097995c80a242bdb39199e794a170bda3de15949c62f3b8fb8e19a775b9f2ef00dc4a379caff9411f20502ee6f627ab906712fa68bc291596d2aa2f7a21b16b0eb345a2787861c39d01943b7e36e6f797e9407e27234eab73d97795c8e009f826015ae87c2342b5f46f00e50f3175b97f9986bcc5a8b33a078f8726cb4e4b813680d60c5b4c32a8494f0a666ea9ed6fba141c07b128f6360ae92b428432af2a45acdd1b8a238ce8747a25462d5867fbaceea2b279148d703e579c92bd147aacd401fd0fea85701ea07cc5592cee26f38e5f55fad8d9893aec529370bfa3d4ee7b4647231d083ac304c9afe89300c03b0046374966ba7b5a9f5fc5ed36c1092cefb1c3c5a5c359205bf5ee81f12382f3529d4125fcea3d747ab7313e7f27b8c90a7087cca56075aed158692516d2df800a2d017b69e45673a3de05f17b66047058fa7c48de5ca06d43f01492b55b4170c763d15c564d20652bc639d0c244eb7b180d1038b3f578e7ac6582ccff49924f8c55fd6612428afedc6f7496b0bbf51bbd9b757e60a26af64b7905b9f40ec5fc2ded64096b6ec7d8c9a38f27569522e005adff90591a2793d41bae2da328c759ae0fd7df413c8c922ac984725542c6ca5d4f6e33a747c284c51f1e4a22dca14450eb310e44683342c1942e777d921e248168ba4bea789764bebe880362d96498bdf90e86c4e50be01be993b02de761627594aea9a2002afcb443b2e18bb7f9bb5e4caa2740da9c1cc0ec34ccda900bea918a15d42302a4916e80d99095005856a5545b94a68305e89d4c051e507bf54aaa7d00b5feca659f07e276991a57119f261a8ea632cc3191831bacb3e3082c3ddcde2fe02034cc5055747aca9918545f479668401329e03ff7a643c0c3284cfc927e698a3176e1ca487964e2019a45126aa6bca7690378f5d027ca2933225718861d6718a870a010a0d098aba72e5008a0a117743cefd89859b0e004604111a4eb393eed0f89b26108040f874b2932e7b37fbca0da04f6d3c2d99729c89afbd08944fae544a43f40d4375563185a615b145250ab92f58c78371d8204bafd279958c41b1dc5d48d68a698375a245555ae165c4b2744e9d303da539ed1f171ec18e79252caf65e5187922169510bacf12dfe8f66cce45c5db70992d5c55aff32765b0eea215c3f0e9e582511d62aa12ee2f155e5977561bc442d833b2b82fd7c3ec5ec1e18fcd3483840710a541f8e48314d82363c74cbedc2061f73095d0b540c0bd39e96a449b6e196ad2985962ef339010a28aa683f88b312632cd5963de66a4b1dafecb682dcc015851d6f4fb996c5a5bc9b1f6d45caf3f2758ebaf12d2ef48ed9b1237ec239eec2606ef173e6e7720b017b66aa98268483392b0a9cd477ca766794fed1e24d7e3f341c3746f0b138975694145a4e63944d472394f98a716b55955118c3b8592a195cf0e46fb5297a556b4b4c74a2036032d882954790abc611f3a7626257e9f1272a7e935b8130b6ea1b80b632ff209181bd47442786273ad340a76826e2594f78a6f3408e888032697d700dedce92f9d4472fc1769b4acfd966f53044dcbc67253b3820c862cae8e1155e4f99bba7d075b49836b1f0ba55039a06c6df5c6e58b8800326c34cb6ccc0df1fd89b8513a461b0217d016ab9bd49c98383ec03fbdae31112f0ed1e79253a1876cde5f4c2c7efcaaa1aeb8e7606411504b3db0a917ea960998fe2bf517552ad4a1544d596ae68b66c570b471fa6c0ed91c2de394bfe185d991303ec96e9b593a4b0da8443164131f60c0cc2e813d81188e1c417362b6906a775a5d5caf6237ae37d9b9af1eb60a43395a41152ea413bca48f563e12f213110c527c780d3f27890aced8a041425fc262e1c1f87cdf508ab7105d685e3afbcb8637a9b6bf2fc1306ccde571f801f8014bcab18567fe5e997289f42d3bc69fa9541a1c8e8ace9c7838dfa52437b4d868d0421af42bb3406d8704231eb8642e62e9c2c60763de67481298320be94ea886300b4bdc4be40120978e975fe4ba993ffb12931efe4166f0f14ada90610068f944b8e784810bbe56b624821244f644587168791f678b91039ce9383ca44fe4b5386511afa314096d6cbd8ff8ca6331f83df37995234f87288d404111cb5f02bf1b2a18014062bfe8fe9afe957862e5a91d274e3300f8c6695519ff758fb6fc1268c7894489366ba5237cd3731ed8dd1a905f4d1e872ed5e156cb7951307f1b9adf802e168de331eb4a9573e850f675cf8824394e85ebfe36fe0869d5147cc70cdab0fcd4a0d571b2c2257f501430eddbacc01859524369870a6c6988e768b2ee1753cdc820fa713f2db36589a2e6694ebb3c87bdda1becaabb9f6556e1039ebd0b6d3e273faac7f897a983699f42cb47ea985d0ea115522526aea15f5b29bbbea6eff7fb06346ab6a8ac5a963072f50e143743b14e86fb7ffb5415240675a41ef60c0e47e6123715cefea64f50323048a01ac740e3ed11d73e2dc3f429d1456104912a10013926925f34efce6beb59f91969cc65597bcae998a93d733dfbcf9dd9e1ef2da2530038adebbdf08e70b26b5e6fa707c73b4e627a9c9362aba8328db6ae600ac88d7aedf57dd2b1873c1d47e753f1d5ed803d728e799d93d36620a1fe2a1c83b147b31738e14c47e1a7b86a321cf09371e46b32d3ef1b78ebc962d8540781f2f5ddd46f6866aa4e4f6bb1890544decbeff607715142da32ea577d7441aae853902286bedea42071d2edec2ff72744261e2fb247663175fce0547e7ae89b404321aa95ad0e17121556715f8309aab344a0a5769a6c4eb12dbd2b10ca2d6141ff97c955a66e917b82a208b008d19f33cc7c53e3a404127d81ec30e1f5ed5ab58d43ee05aabbe0af6a6c7696369a70df37679cd010e820e2f14429c442412a0259451dad1d9864cf67c2449eda5c6321e59a3485dc52008f8fa8d9d21a3ea678c74fc962763c7536121f647dd07526ba6a1a542fcc62482a864a52d8fccf1e8378a9303869f12e4ee624e123a873367a2924505431ed5226aaa59b6aaf721a95a84456d85f7856f0e7996de210baa368d71a6e8fe9294489ca95d9da2e710b20cd053825999cc27d6dc93ae8c812b8e66ec40ab50beb09393d1abae4c8105da6658e47ed0c55d5ac41247115daed6baa2e143ba64b2baa1394cbca2286308b17a61e874f5747937565493e5dad227120dc02d2db260ca95a6a4208aff614175f1338e4d43e9d295696369d2933d3782700c38f9dd7437458913a07dadc9c42cda571e3a28fc715fa32729038bd6a57dbeae6ca931b61d4a68895a22edfdfd8faa8544d0747467d77b134ceb884631e32c348ec50b4d94cdc41742a67883441e9378474150d4c50248cb0c4b3923d376f4be1e7a3077179044152a8708e3a9b6e911e3ad2ec811e2ae3e6891e2ab7e106162fff4f78f9d2079d6564262fbe259cab3f9c3544a64f8c55bb26439c55baa44f1eef50dd4e9c148574961dc3d5e985f8eb4066801ddaca97883f7d7d63f2421d9d53502c3b2840b9c9dc4d02babb4f9e0c792da37c79edc6007622427019804c96d19cf04ca65f992068d8fd6644397bee24c439434b19724f415026f03594d921fa2d2b11cb9850eadd2042e50cb8e5a6f8e688ad194c7d17da47183b0f15174b9b695496439b696593479d66ebfb07bf3e4e1b42ee78e56cd662de20f3d0d393567a7751a02886a689c56a5dcd66466f5bc0f104335f154915922a20fb5d7f019a62c4da393a42a4bb1a1932edfd63e8bc46da0c7ff0e737b8024f39300f865b6b2608aec343a2ab9fb07d1ea30b2ccef4bdb9263a617712fc432cd73bf0655b0cc1b2c05bb51bf01fd26a625f36691147bcfa25d87611971150f4850c8fa7a6d910ba64c91d4d5f98665fb1e181ceeb287ae3671f598fc97c70bd6535aeb1be367e9b02bec73218daafdc92a601704a4f092dd8675739453f08d48ff2baf92047fe6a0e49fb87bf239d951f6e60cf8a99868f17852c76399cd2ce0a26e61aceac6349d2179d8ef99be5ed6b3ee16144fc57aca3b987deb8a195aa0af1d6a83aabbfeb61ae1290ebfa9279aad7ae31d98645468922be690661c4cb89288c8856e177c9f0eb0bdc8dd9e8272f1b236d0afc1ffca9fc1cc4933fd1cb65c419d41ddb9dac0e149280173a96703b4fb6a3a85f45beec642e9c5474a46df475c093332aaf3afe7e7803bc6f5b0ee638945132d2a34b2287bdb1ce5c0dae4c6bd82231d30c95438c534fe165eb2175d1317eb2bbcfc1691ae3998c650751029d8d3a139ae85aff5622dd023224a15844f0228acab7d7aacd27cd7260a1ae1a050bb1b1aa76f38e33b48a176e1cc3212c7892b9f524424ee5baf229359badcd429ba8bccdf52b55e148bea18454679ff7a32e8fd3ba293a640efddf12933435619f54b93eb053d18c5688fac60e832c38e6f37d9234cc4732bebc8aa810143beb331140a32e2aab27283f2b8bf683c593daee8fc1e01969d2ff6e45c7a1d8c73595ab6ab646c7a04c755237e350af98dffe987ca1661e14e383ced756e4f86ac4f6bd5a97130bbb1881ff7395f94fe90871870c6cf41a11cec2a91eaa51724ad4c304e7fa2b1f8cdb67385a761060f13015502002fa01f368504ba4ea5a189c4f498003b465a41d668fbb0f14f83146ab11f7d6e65000e14c96197450c455fdadb346c80aa16a0401b25e0c292ec427d97b48cc432101da8360d1be86d90c3919bbb4e7259fa24e024832cee5c8d4911a4ca0320ef08ce0f561128d15c166ed7610ed67b5b65177bf410360f01b73db8d84ac6ccc850c976dd65eacb21cccfb999c15f21751e626a7981a9c45e3982984622ba93c1dcffc51eebbdcaa46e2574e242798e18a1d1efb93e4b7e71d6a2107eb13bd42a30ded95329e777307f369687d8cc70a66ae872806f93696a7a000b3c71af5dd5368facf13696b9efaa6f352306d075d930f4aef0518a74d7ad3c00883c8660baf6b21df3caf6ea975ba66859a0d21008d7fc2a0770410b0632d682233078123d659cb3737a72e1f31dd410fde9518a0d96840e6cdf9e936d0852ad202b2b2beeef48a24b922e2ebc86b4acd5c58040718094b052510e349ece8df682768917c08244cd8e7619ec52af6d2f8383450197f6e359e72fe99269254e6c069fcbdd73749a5d291500cd68d4322d09d24c6b37c97526faf7f6f600d7477adf9a714fa40595a0ff65098737d40871df7dfbd838033a35f27a3ec4b203899d6ff1587e378c096450d2624f3e8fcfb3a4d8f16584b4619fb6141a3704df4692b3c9fbfaea9637103f7b63de88eb543587f5f20bf7da96d41be869b0e0bb3a1e9b01fe3224c539d535b8901e64f6603ec604533860be5d4877ef1c0283596fe0d1d0724bd6cec7dc758bf1dabf63fa01091bd63de82480b7f25d5deb6faa18e6a82088eb576dd3d601da434e60bf228e0915010fa648fc365e0e0aad948f8bef597b93b22d4e79074195f0444643dbd575b71c5066758edf6b0df5cd1e75fe7c0a81fa342e14e7f6189f52f991845e386c5b625e5c193f4a746a0e93c51278887f7dae1a97f7b10584bff8b8edc9b89ee31061b0663113198e438e08e50b2717d64cac2af5003ad8f10c84327dc564d3a1c59fa7be8b2d6cbe4c7dc9e9e70a4af69e729dce1134b1211d8ab9278618876ebdba543fef8bbef7e94abdbec8722a067efb6240b17d5a2c223743c1a3c26aaf4196a7898b9d3e478a4c494ace8b0d2e61293fa46584a43fcb7cbdd263f6e0e32eb3b1e8d2bdd76ab8f78de5cc47a410a979089ef39490fe20245e1a3f61444e1e836423156beba2450275f0390802bcba5747a75d90e61b58b4f19927158e431dbdb2b37deebf750ab11aa465d1fdd88060382243323a831bc2c2651db387c77644f5ae678f99167a4c305c6d96b3b4bbf66c85a53165865e6015a5f75c8b66edcd29adb61fad927f7e6fd1e1b70f453cf308ccb63ecf6d2d62c55842c067dfb76c65fb266319c4583617cb8ac53254b199368e59590879253ae56bc79c5e7bf56d577c5af091d236b41d446fb7bead8018161adf7302889c48e083dc97d4a2fab87d454ba9dade5e838a2ee0cd0395740e56f3441d13f7b127946e5770bb42d9443f977c9185ac629f058be4dd5c001bcca2de08387801403b9e218c92a1b18a73d9caea0a9c8f44831b96481ef707bc67b9017ef28493e38bee5c730f8726656e8bf1dedc7149495d5944bc3138b4d6958d26a1d2ce78ce71f52ede3842a2197ebd07b8305d76c08d30624431bcf4d4a227df7ecff8d3c0425e184fe3cc872d39ee7fe2d08f4784e217281066ba38b86ef80f17601026d554f45defb651cbd3a73a292d62a709cf1bf17162fdb6ae34e39a760b1d128f3c87044b475f650333a63e896eb0a4ae359f71d35126e02f9de024b15b1a0ea6442bbbb44e83cd0875636607a80e5030d63abd090c8524db52d94b57e087c20a1d37b00fe4e2d25d6ac2418df0197af19469a96f87449859c30595dc4490dd727a49dad63a2f43a7c51891b5bfa30929e6c7b0b516feca15faadd7456c19f344ecfca27bbac116c09ed51df0828a145ec5973176b078c808eb2616319817587ceac456b74216114b2690d9018477d786b2d690c6ad3947dda0eb1ff5fec5d404bf0ff78fd001d543da2080d76bdc0f373dcf6ffd09b035b8f7dae5766e7a70e979b7ef73e759bf5c20ec037a45cdec37036784e35b90536c325a9ad429b22abc2e69bd693c1ca9e21075294ae83ea4b62f9803c2f9b849e28f0a056695484b2315a97af1daa1a84f92583278736c4b1198532248e428fe2d4cf8151654739f1c73683d461702e7001cac1d696150d799e922a8d03dccbb499d35839630c6353a32ceb127a44bffb4a8335b1487098461a2f9f20fed4d54733ba8aa45743938544703bfe1fb4d1999076882db18ee73a81c59da8e4825c055d4d06356f4689400c13704e80a87a00fc72bc3f56d0be17d903dc9f00b7165d9eaa95c99676531a40c83d4daef2da6022e1a1ff883b30d1870634e8d305e8673789582df6681ee5906ff9187fe18d5f83a377270175f16211ac01f5e704eaa038ff25b4e54acad044278a908b58a681f9f64e2442b9c064075977d0259746fe4e9aa68089bcd8c6d0f89840def21a1725c5d7735f71032a1bd7cb1cd13205aef96a346a187690510c78093814d73aab9221da11f5e672e3c7e34405b58dd4095109d3ca35d7fe189db8336f92c8532d0389fb215deb041e278fbbf0d0da1f1235d465ca319163d7708747d69784c139410c17a7020bb7c5cc010aedc8808e5913cfd809f44f660c3ca65711f24fc3702077d86e91acaa732904883963930711e1dd868127a1b8d7e83a9f20f089f88f33223109595ecb3627cbee301d192137fedbe5795ef495418587bf4859beb30d4eb25aec2a3355c3385284fb218484c055dcbd64b1b78d2163bcd0e335c6c8870ee180f973548a88b5941d6ac8aa7a3aa179bb14fc69dc633808da368562c3c9588f1d8d2f787e19e539043106d7b1560572c4c457e986e81882f888f6526c56b4570826cfd7ce37ca3c81068861946d4db6467eada7a717fe54e7e5bfafe3eddc8caa2116845835e4e539dab72fdf98eb659bcac81b2b218016e693423d5d1d02a472bc71f37e85f2024e436d5075dce2da78aa62571054a4b7e8cba49604619a2d4b3a0397d2894d9020722311c3d7d257b1f9a85cdd254e12d41cf49bb11053afd00df0e746bd83086aab9cb978a255309e19e63d43d49f61e66d563a2f0fd5f3026aa36f3a6b42967af9985196a216d15a76be2d6b274a346d60d17f46038040b460c06c6477da8738ed45419a9ce48077a3b629870fb3ddb972f56cbdc438c19406794b56a3bfcef35b7491d6964dea1242cc28cd40811c225fbc29af96dc67187bcd55d17bcab33cead1ca3681cc2c43f471dc7e83795538c8735a243b0d8eb38c60978d4923a07a7a37c8cf64c6d7fd8d71221b7ed72528c7a22f68efe31ea0a35e86464d48ecf224491ecb26d77cc262d4e78ab1802953607632879d53622b79614af5b85bba6c7372233187a4d194cfcc4cb9000065e62e886157fcb545bfeead4242f0a1c70da9171e0be0dc437f8e12721bcb109ba3ef7afff96cf6371b56263c57ee8c0835b845e877d30ee7a36839f3d78f2395608c3b6d4d46cd29cd66880a09dfbabdd5359ca3523590f14004ce7515ae332d074c254d18c592c228bacdf7a821ba6aacd2adf77408df1f596538eb2a6b230fcfc83dbebd4086a5ee091817f1cc9c9ac37d9147c20e8cb3e9a0599fd9192d064cbf4ebfce6c60c84958cab1184b1c491fa2724df192d5e5f151caecbc381af5cc65c8818271b65486f56203160b7f028ea57815cb96a8daca089e9d3da8cca013a503c90fb10239a5986505b89384246b344f6b7f287b560aad35e700c5b7aa469ccbc465727750da8bb7cfbf4ec5e1b145a0be1bf9f406750bc1d13b22481a2fb423608782eb641dd539458ab56f249cb5134ac36cb4332433c9118d8385a9acb44aa2cb9d929e543ba4a9c178f6744de2e73240c64c08082c62029bef3d8fdd2e9f126075915bf331eaa8fa5d3bf57340a0d3c7ed55c6efbf67acb116219eee62fd5d4a0f521166933dbaec81b23530573766e4b1ea47b0677a52bc33e9b2e178695925fd0bc85b019907244a501950454cb14915414a8c7a78d2ff86440489c9f783e86ad874762622ccd8ec8dafb0c68fe6cd738c040e8d3d4ab1137c06990e27f0f42fce93498dd983b72afecff7717682e060d4eb0012d691bcd13011b621d2154fc0be1023b6a1fe91f942e6253ea059349571f92fd44055c8f5d5b3c106ae66362e01d58645b62e1198691aa02bd5bfa80e417371334fd12769e57d5c0bc02442d87b7407698cf88c4b6d1ece9c215aee5142dd49ff635045743bc7d97d7e478a57fb7428521bb7ab42f8ac226b5e08ee62b8489741cdcf831d41e5e908c53bf5a3b73d2bbb3686afe5614769687050fb6e6189ffa5ee8244564e66313929f0e58a015053910e4faacd63d5e73d539a75622a786b3a5608f9198d570fa7ca2593594ed846e9bb1732146d9adf1cdf533438e556960fe8c830eeb79ef9fceeceb090582d35203e1b34f8f37341dbf8da0e1d15d4ae67a9e788ae30c9b65f39a28b6b506adba5ee0930d9e861589c290f29d2cbf1da7808bb647c64476ec20f602da4b4947a558ee9291a9f25c1ec0675b57859924590db0f979aef30581857ac996caf8b8a416e902b42f7d868f5cbbe4d3436cf1a5bcef6b842f6d53cad60ab39c93c287587b0a01d899d2ed3f77752a601d631f7c53c0778734e91272a9ee9b25d42dd031804609874466f0e714e3f1af5d6a21d33c77821da180744d23a238c13821f4ee8df6fdc68681007b88ee9f2e0c9cbfde37f731d83f7c45f701f03c385e3927e5a7efc202329d7d0047a3b91aa216ec4c1d6e4e34580270d278c83e179702e0d3f6129b4821d225314d51433dfc6b6ed60ab06f07dbce168b8e3c11d8e72f37a894de9e99653d3d9a2588ba21fbf9a5fa8a54a9b06754f6bb970ed60c7d6933e8ef4c2b1838fb9f692eb4c8493af59480604be85143c57f529d2da5ec9619a929858d22e2081cec09bc3c5a711c960f2d746fba6765e44cc11e5bf7f028e393e5f064855e11a04a47268dab1209f263987e44f9f0e4e3d32df8c40a1787d4d7a4a1ac75afa235a65d07e0b80c132977cd1da6d71b745282e3adcd3fee69364ac75b3e470d669efcc90942db75d8bb7592b92a80d2ceac64bed36b4457293336885774ea95463f6941c9c9c89b9f2d06aa6009e527da8fef9fe4154285ef1e64120fd0a66dd77e4d4b3ceab2267040d20740c1ba95182806432f127c15acd1cbc53f957c1fff49540aa992b5fe18bf90c2d14da54ddba183e63b852576a619f1adfd89192d689e73e001456c52993f47ae69d653881b81e6c90d173cf1f482997fc1598d55f677f7ebf8b1921436caf68265adf4bb72926451b0bc66a892f5dc49e5dd57b57075665903b225d4b8d15df5a03f22405db2c6ef9df08f68970db8d04868c426689916b2f515515b00992c3b44073c0963e3516ecb9d30db3827ab9f52aa7ffb15dff6c22b8a825e331fb7d060e4c018ea27523c924cad1d3780a7fff74ac030bbac594e4180438d9775df3adedd17d78bf396f315c5c7ef742aca009de157953b7207305ea4fabba16011373457f31311b7ae752c7e98735e493d7ebabc6ccc1db2bfb19f9252f553af188e696f7b9743c528cebc2551147028efda8f1acdb18725aee84fe136affc6966e96a1ca4d7f87eeb0506838bd4d886865af2c31ab6cf026ab4451fbd624e3a18cdf819296942175f1ae4f75f490134faefb8189fd051f9bbc3e07d3e974f7523b6b535d88e94465c733ca98f7719baea52b1a44e927057fdb1a27c3efac45b03aee0553421dc0e51eeb19c0a2236ff6a5e95d5625b476676db333788729dabf549c8b5e35c3c0d6de83320f544b4bb8069a3c7c4d5a3a1dca26bfd362394c54da4810e9f026a6f2a76cfba7adf77397947e9f4168d4dbe9ef720b5dddb2ae492c6864443311b75b53e4f24fa5261b86ef24394e096c8b165edc483079e25a6e35c234d5b3d4b0321955272f949b1323e1373d91756b86da16164c61eb7900b2fa6a6be09d453ef06b62b559528ee26eaa709af33c9c7c124d648f8b0ec7fe7278d3e41e7456a2fe072d352f5aef4d8d3680c91b2ba9da04c1a69a40a566fe69a2112956db6f656feff304beafc34eafcf5d54674016d67011aabe26d3440ae24c901033ccff33ccff33ccf9f738efe10a2ff1d341091524a8ff27767139172570ad1afb39f70beb1d96f0fd87708bf2304a206d906ab069fa416b83897fab72bdda699852af3ec3a6ec5030b49397bcf9d242e33530808881820f0b8c299cf36b855c8c30a974d678f8da638b1c4a30a78e6d83827bbbdfc7d635b50070f2a9ccb738a1b3c1b52a3141e5358c3455fe5cf5a929c14d6b7aa9253fcaebc570ee70119376a340e1b9f031facc00a1e51c02e43dcc7a4902146282cb3a59f21522da2e409c5387f9bde4301f0838713d49cccf5398e8566b409bb5fa61caf289fa9f1c18309771a3ff58cb761535d42517b494eb53493a2898712e8d0acfa797597164f42fb7b265a654d82854e7b20e10c2a52c174ececff23d82567ce699a1f242a0f23942e2cb2f3c929821aa4bda673c72a2d65e04184bc7b344e8ef75cd7c370dd92681573b68c159a218c4a9acc3ebf280d7930fe7057bd1f63ca6b1a189ecc78cc61622119fe2259a3868d5f65862f5c55cb4daa2a1ece64462ff6c94e9b7caf19bcf84aee8adddfe19f97001461c62ec878c2d566c95c3137b60c1d704017d99aa8c9ff312120203372f185c7933f99580a6eccc085d69556e11a2c26e11a336e71588a4db29865737000046473380808dbc8e138fc062c98610b5bc3493e25582cb9de9019b5b0ec6a5386a8dbc9f10c5a64e2f96975de6aaacfcf9885b9ea9f827b8e9cbf7860862c12bc43b6c947747963713023166676b09cc25c7fe6de3360819b6c56d984fc06335e51d2193f69e3d2809ae18ad296cc7869ba13dd56d061194c3b09f679f3acd8465ba72aa66c60c62a4e93c4dbdf3b318bcfaa28ea6faedb4ba5e20af6dba63ea36255edec9222e2e729334e91f5c4a7cdae31c99e630a4e9e976694c24eb3ad97d14edcd899418aa48b75d6a17219c4c18c5178dda9bfbd57418e1d3a76862888cd722907cf983bcc9050a41ad751e14e9eec720628d8ebb8175e5c7dd38702333e51f824799dccaad4a35ec08e32c668c00c4f60a91ef59d8289fc55333ac18971c24952ca19b33c270a267d95a44e6da2cf92fcf28ed0ce279af83689d9336a32414b9a918952d00ee7e1970b6660c2bfcf9e4f516e63d071d8386390c120202a987189adfcb78359aeb0949e6109b7a37536848c82199538777c5fc46b13cca044f5296bf7973f77d937664c42b594c34d864fa92d3a431279fc907f96d27612cf8c4854f9a4285ea24282ec4cfb268ca724af5782198f78bc93e427d45db2ce3be20cd53a53e9b34baf11fee7981a6b690f27348311698ac48c7a547aee3316d18949cce6b279b7be2228cdbe953d2e9c2711562edfac8ab95c7144f42767c8e0973f4f7e082e2661d5ed3fb7d686303ff46c3d877ece8538bcbd7ddab8b21c2604b1fe615a42eacb832857dad38e379954411c5eb27f5906338b0642d7d471e2a580e04fbe124db2793acd1fcc1a7227327b3f98cebdf5c424e538d9f7a15862e9f9e30325a5fc695fd258b687fa644bd51a4cfaa9f4f0c91a1d3606ab8ce7610bf918c73ec9fae2e1ae9b3ff593a41893fc1d5015b79cb24d454bdb2161b33e55689228eb507c5450a91cc5a41c3a1c2e74f8b50f5ae139d49763a13df4b464e550f78e4ff6ea33e2608e7191498ec2a18b16b2dcbf27547d439ba5b9e5433714336eb496f2d818b661db0a2a1daf41f76303bd5267316d70b1185f43b19e32c915afc498e26ac84e43e68eb6cda2791a3ed1e4da509294a73347c37f826a12cb44932df333d05d49b0752ba1f372331caf3d9fda675c4be26578436e0a3f39193e49908eb3f0ef2bf918d098f39eb6c2e4f4c580f9a7eb4e73d26e3a0ce4494287ece0625783a1984c4aeb89ab8cf90b8da9f46bc5547db117cc0c1ad387f98df07421c9a48f31f3c91abfc3053aa78aa1a9ddc29573161fbde412522d9cff365ed64c839c05e3aa692c39c1e40f62a1fbbe93b7bf2bb81ba663ce392b3c963da595d42895528562feb78f5e9bea332a246679889d9eaccea660dfbced99e687bea4c045c9d9e5531d853e9895dfda68be928642514fcee4fe315428e927e01ba24753d84eb046329bd8e61363df84f5e2a41c56a7c56526a4d1b334cf2d77949750fcfd28a1d32dffea56fd7993409de6fbac8d75ae48c032a5527dcbae2779044f0a933c866e9c198d502629c3c8ae487537a308eaca4d46bc07df6d0611a874fb244f8f955fc350c2929444d964612416066da5e6e15a4f3be260ec1bce72bf8dbeffc048b813ae2a52c4fd5f5c9de424dfdcbee8b4d2cbe458f7b67b61e58a4c789817a74e99f3afd556f8ec22394ba786940a7339ba30532cafb42d6bb6e6e292a643461717e59c5c9b3acb76306fc1c97f0ea1157b4dd216a8bad5a464c91ac25aa4654990cda105af256767dff021bc5920529ec27a8c2c8ac92aab7a6271975c26984962ae5adcc755a58f1d0c1dcff96c574f7eeba05c7cc5b78f0e449a6c1a63eee6b0bfeb988ce6b94e727260a75bb2cbea344a2e0e7a89af1a7f38782799e617afbc72f3062c57b889bbbaedc7b8e1db8aab9cab2fe5316d68d389d5175e36e816c37ec8ea64f2b7065d3a9787ec4a7fa5066a3eb4f2cbe79827260d091b7a2567cfebf4a3610b9bb64c9f2e1e3f43725eed133cbffec7663027e16c529394326ce3259f566e64b83525fbb4d9c6f0b4b94972f8784f8f89c1905116e329a5bad2c25092e4a09b73a45c4c313018e7d2cea2c949d8ccbee007f5de9d9a6a97cb0ba591e892b773b6105717ce9e6ca22cc805b52c9e078dd7305f6d213f3984c692fc1c27a585e24c84ec9f27933e0b252db58831d7b81d0b45c5b25497af50921b4ffd5ccfb849ac70b6704d52948946156c79bd601e6cbb84890615f43649d2f2c6fcf9108d29b8415b4ef4570a6ee84f7b94f77a370a8f5caced0f42a1998f1ea6ab6247f109f76bfcfe946c96a213764b676d9a26b83ee9baaaf93e5b9860bde7971bcd61652d81b48c11fd7935aa44092629c9b69e14672a118d24dcb6f5aee12431a88676bc8d101d35682001cbe9b37c6238f6c609121a47308889397aa4cf973885280d23ac992694c79cc9f4348a90dc9db341db6230d1d020c29eca3493c69c31b665079e0210901d788ef7188629cec6fe8e8ad9ae968730cc5862658a16b739a70f10830667117040091a90058f60d8d1c2e5c8cc24567580e1099b92d7b5fd8b4b32b9a66c7b7c7cf245abfb9eee244e8a5ad2810ae0e8c01864e4e80008088ed7a1c3a31796b4a14ccc190da9d138b6c68dcde1c18b336597ea92b366cc700808c88db5b123c7e6a0c62e92e3baec6ff84c524cbdc04317565ae8cb601f53c6bc8ebd31038f5c5ced7abb321e259b250404240764b0c003177ad8df7fb8ecea490c04440cf616e06881c72d92a45fd9b421516a5388182a28410e13e8c071c3c31658da2cb1cb9bbf445505251083bd05018882472dd0641f2df377ba90a9101db43899904948bdbbd9f7434062e0310b7c5546aa42939cd30c0101b181872c8a1b3d3322c3d3a72432f088c5aa7fe1f17566366dc818630c32983d60d1596d5af69e13b1860e1ceef10a36ce5358d1350404440c15940004c46f9c63a386872bdacc9012ed2f1e90a10110101e909123c77bb4a2981f4b46f7670562c992ece8c64a957bac22dfd0713975a8051eaad8369594969ba184eb535112c572a69cd2cf89395498c73afe9dca895ed1298a6d5f55ee1ebebf3785fae65b612f2a851f6ac43d69c6d992430a6f56fb2dc53c2bd1f518c59b7721674fb23a7d0f5134f17b17cc2ce3558c3d42f125bd53133a74a38a1ea068638636bf58ef953c21787c22c9534e77964f3c3cc179d8e5dca5321ae220787422b7e8ed6ff9f51b32f0e044e2719704bb50a3e1dec49dd7d3c24939a6ada02636d794af9f534cf899094fb2bde029984850ab947653e54dbc5ce2aef7d23096af4f7841208687255293f5ba3e7aca2152258c31ca732d94586e552fa67c963bc6933845ab33492b27f1639384ba9b5d463d2512e958f2cca821487cb1cc4e9c3cf178c469dcfdaac657ddb516783882f914322ba7ef36a71bf16e7cfb952e79faa3147830a2d136a93351c73c948b60bdebe2c46edfe07a480edf81c315b1a513bb0e6fcb1fe744b465c9538e194a2ea1ae810722887d73950b966a637988743ecc8e6e46f70ccfc0c310e7d124c513f33726831e85f024b5c81ca34d6e3e32f020c45242a7cd6d2c176d10e7c7e4498e4db1c043106ef212b76dab452a2ef0080463d32261b62999d8a2c00310e658d7fc56f2a60e7afc819cdad7d4b7dadb280e0f3fd0493cff2df865c58f471faa24bd893187d78c69e4431d53ea6977b97c3d8787f0e00b1e7b5032794e2a966dba233dec1e4afe354950b193759071836fe0918736a56fa704170f4731c17c7cc3674d9a1c2c09eef04abfe94967cb332f0c3cec603ea1a47bfdd1cde1f1a883a7a37a9eb7a1832d7a5297e6b59c0a79cca10aff245d421e72c8a6a7f3a71db3fcbb471ccaa136656c79b3b98e071c922c86bab89921dbf67803fb169dfb93abb55ad8c30df88aa6b9941391caa30d7dcecd2cb12ba5bb65c39a7a2c557dad4fb4d6809d9c5dec2449bfd6d5e006d1f0df4a1a6c93fc5fccc484c0030d9ca88f5e6eccf7ef0c58b64cf2a5facd407ede2bb7fdca4ca22138767894c18aaf1a2db879c9dd64e0666acee43e3bca20410edf01021d788ca11433aa9398184bc871f88d1a3bd8430cb4f7a52c1597eb666fa00223f00883f29ede2f7bd454ad85d8a8e1031c9b838c1a2cf000c35947f44bd4f3a6b91e5f48dde4cc8a9a9324e709088887179ccb27871aff5c39945cc0a30b496e36e3a9939906d7830bc6952cde9bbb347d8cc716bab38f8baff1260123f8c0041f1a2e5a6fd85ca89750ec533b498b6a9e6889c4943a2c6b85548573051f9528277163ddbd06d7d0bae08312c9d93b25eea7b8d5960794951101073ce16312a60e1e2e97a856f5c10e24e143127b8e9893912af1ed2dc2472412a9182cfcdd09597a0808880e4814dd9254639bff4eb85bf0f1083dfe691ab9f8bb510d59b67143070a3e1c71666b1ecf924f7cf446109a2c8bfae56b30f90020e18311a76c39f8585c4df396456452eca53ef5983c498a682af4bce67d627c244213f573a7c62c3184f58108f3d6e87a86fcc79439446279e8fca4bdae29d2f16108c6c27b76e59f5d9b05848f4220d3b94a9476436adc581b3b3a87093e08816477f7cff946f992908f4118b7567a625c3a4f4c10895b54e8ea85d8fd0341e6946e4539a16c7740142ee3a68c1efe039e49accf3cc9a27c42c1871f529f908c9b621fbcf1389fc5d27a49221ff29c929a06f5345fda1ed433df92523cadfaf560785093437fce2589251f79d82cf8573ee1badac783c1cf3f6d645c92b27e87e582cb7e364f093eec4025d1849a8e49ec1cfa3a98a63f0997592f7c2ce9604c6eeacfd9b54ee79018956543097ec841314d62e89a133724fa88031bf61bf32b6559723fe0e0874a9d14618e8f37e461ec73d20c97825b16f870c36692a1423b4617a9948f365cba9e557e7b7b888e3272a0810f36b0a904cf60f9120202520304e4021f6b6076c3a6b974d4e8f3def85043e31526a5aa34e0230d854f9a84bb5025c9ff22f840c39ac29b7d94ca193ecc70c7a92d21cd2f439ef25e5ac8c9608a25df65b5bf68cf8cc18d9dd830df57eb9518de2afbceec50c1a2e4230cc6a6af3bf153ac2fd9aa0f30e4e92ac6eb1c27d6371f5f3827b9939c662b57532f1f5e4812190be7bb69f1a72ef431d67c5a8c553999f5c105c3363d574a83e512df422191d943a738792e470b5dcceba9933cf4624916d8aebc201ec604fb101630b90aa71b55223cfa7185f35a2a2f3121c4636905d737fa7dd668fd54a942aa7e9fe34742534a12f24105aa624ce9daceb7a47e4c610b1e56bf4afa9e33ff430a44497ac9f345353ea270092d1f5d725bc9dfd18181137c4061d76419d633b645ec8f279836066993764a9c4c8790b103070852f0e1047b4eccbb6ad27644ac828f2658ee269594ebed7ffe10127c306149f9fa92d524b518fe584231f642766654dfd23f9490f479f4a44f2e191f4930c9276a5a1243561f484870dffc9c73c2649b847c1c6159cb1ba5528655658d60e64fd4c55c7a8ae9471188dd60729fa249fa291f44a0cb3fa547dec8f8456318f9bdd8557c5dfdfd690823cf3cdf397430402318aed7bb58cca2a9abd234809118bb27aed2469dae6440e317cd2529486776132fd20bd0f0451f2d67f5ea6940a3177809efb9fa6e563c0fa1c18b53ec186229b94649e2bbf872adf947da97dfad8b4ceef0d59fa539c5391745678f35e21d5ca41a6ba7dd5a92b87b0bff3f06ad7cd516c9274929fbcf598be365ccb14c5c1b190d2dac99d7bdf298593c9537aa2a9cdcf8268bf34942f7a79c73831f0b2a2f76b6ac4ab32a581c935c5571a2e60a77f20a935dd8fadd6d031aae583454634cb44ba315c9d525d744daff9534a0c18ac44365bb941c7cdb5b45e289719f27a864d05045ff4998f3d39462c24b2315c55dfb7d749e51f17240031594189b9327cc65f4b07440e3146912aa935caaf1c67b1aa650cf6aec2dab690c5729aa95934daaba36539f145fb0bb2446b5e8fe5308688cc26499248b316751d4a3e36e51b33b72ec09404023149adcaa298deafe6d078a4ea888b5cc523b767a80c6274e26afb16330574b171a9e30764e163e854f93b1ff018d4e9c254586d7494d297e8640831367ff7aca750e73dbe8d051011fd0d844527aecb89c4bde5f33848626bacd1cf673d0fac83f137aef6664beac4bef314175aa893dbd5f22b9d2d5680a3af6b95b82b3b896ba335e89ac6e65fbbd4d36e594389cf02e9f3d3bccc427617596b3b1568b7d5b125cc9795ff24c8e6d762430a93e25e70209aff7a5c4b0222626e947147fa9b5b3dcc6fc71c4d6b6256448eee98d4847643f6dd47852cb88426f44e8b54d8c092fa294e4b43bd37f8d1d455425ac579c79ee7e1371a5688275c59c9ae5410427b23d1def2aff7308264fafbbe73de78a212c0fff290593e492af2d84e63f9a4fceef25fc8428e78f215366c75b1f045231b22b8a9ca7b4208e21df3999570ca706e2d8d6f3de71437b0404e9f65226c9db276dfea0781cdbe861da46f3c39529ad6d7788e84bfbc0fce54f9151574c960f468c5b07f70ee1c1dc83eae1c4fe3dcb4abd1e50b9cc92dbcf53cc7930ba2d2fc7327a7c110f644a22de69c433ea3b9c0e0fc892809204aaa816e08005f8e0c68e901bd8c021019ac0dd041206f00e1d65e42040007c7024900000ecd051460a061000bfc19671031c7be30002d09103012c00400ec66027988d1d394274dca85103010a68c20ed731868f51000530818ca92b2e06db13f31fb284843e61ffbe2c4783254409a4bcaf5c4ea7149004bc4bcc5132a5a79f2800096663c70e6c7419356a20400147301b3b6e84e8d81a3510a00023f80ec818630c1fc3000a2882d5a871000510c10739e0317c8c0244866136763419639451a3060222c2c863b3c45e65ac591692959401023286e7a8b1031b5da3fd07366eac8d1d3676dcb051c6a9916304916094462fa744aba5a4251160f41bafb16a3f962451e417885d2751d2fb73ceb72f92eaa4501964eb059f1efdc2b5c50bcb2d575c4acddf61adb10b9398c3e534a11e3a264310d1c576a325942485c728915c3025846d92ecbd3e65e8000189e0823531b5891bb736431e020212b985264b668d1b4a4f268ad8c20d1a3b7b0cd994cd482d1226ee324b9e1788d0a2b8b77b27ff264986c988cce293e4febbf214594462018b57b8a215ac58051d4eff2ca6a91c5b09711cec0a461051851b3ab7a224136bf74f85626e65da29658f214550718726c12fcf7f0a7b4a96f9d053c414495e9f2ecfb2c7bb934829cef79792844f4548c1e5cdf1c828fe5bf5f5ddddddba39228ad7f25dca686d93a386a234b72e715f1fba6704146426516b2f6aac62f61326f94dcd63c7524b16f144aaa9a32907fded98473a719b1c6c2cbd63fc6cc289dae34b8cd5bd6cb79b6893a8f84dbf49315542229a386945972c313ae564278864c28e765db57b463081bb446a656a77928c5ca2a01125a58d7b9ec73b2296d04e54d974112176a60e229548ccfcb0955e5726d208257af1bdfc9b25478d253209f42c63cc9fc636e74822f128d53c8b47c294d2e4a819a682ae09894738a258cd8c6d31c74d8d1bc108a5dfc2651ff14ceb0041056e90c349105984220a73bafd176b2bdb9588bbee523eb5320404c471808040208208534d8acfcbbb4ad19b1039c4156345dd4a5672f668c4102e88144237d398c4cc6dd5492a4288414404110840fce1d79cb2514c0a39d2fac118e273a84e25d207e3c31e92da24e50b3d13dae3881ef26078f0929d7091698bdce1d826ea6d274d2ed27e206207a274ba53e82825b617a943e286652dcb311dce99cadf492273d892655ecebeb20f45e45094b9f66837399c552271a8e2c2597a65058f397030a755d0d0dee62f165044dea06d8a923de52096ffb9c1a0d5f26742e53b6cd408c02b226d68d63d0916538f988411369c66d36aca4f7995ad236bd893144293664cfa8d6a68544b5a4fcc6c10498326474b1b93d6d792682894e895aaa5e259e832889c61f7f513d79310657d9ae1f14ee983b696d4e63210834819cad3cafaddb262924f065ea32b660ed9ce29263286634c5da29d3e51524a0808480e1b7b0210904444c41086b6e3e42499a4f6a879040ca5578c9a4c92605e9244be50fece9823239371192044bc504a9e837a9894fb94a9f241a40b66768e78f85831a6e00f225c306b76c50d36a1dfed16ea0c5553a9ed20a20534facaa75b15334fab7244b2609667b01433bbb545d5830816a8f5f13de9addd4abfc29e35bef119b640c40ac89898d4f3e4912a906d16e622211b274945a8d05692f9f812a22032054e2763fcf8f962f89455440ab9b75989e5f4515315c3225140d2637ab9e5c898c454040af5e8b4bebb49480d1c64e0a8a1c3466920f204c4d288c5f14c994b459c70f54da79a47d97867459a605277a97b9b74b3985584098a6612be34494796b04b6a4e951151429f532ccab8c602a08448120a9d1e3dd6685df47f0811247492143a48aa66ace62d7284ef2293d8b539edee190202e283b791818811e8ace4e9f4652db7c473e40004a40391222477aeab9694c44c0a47885068564db9d2d2af398c4d761deb1a5318d7d4a58291306b13ea9e34a58d3e80f10bcf62dbc3a6d866b3f1c529db358fcea7fcddf6e29852a8fcaa246a2e0d2f340b9e84d490f7d7ea6317675ce860971531ce872ed88b504b279e0b5c2c9e83d5464d1112e9c72d36792b9d5025131e4b5bfc5d42a55499eab4c28f5ad02231a6df9097a3b3f0c7f3bc7310b32c9a2c4adf15ff4aa2a2ba148b42374a6747df126cff800525c9df26565f4ba9fe8a722a93e412bac4cbd6ba42c9982a586fb6b764adc047f7b3c65b322da10f56b4957f5725fe3b7bfa5885be3a3296d51a7ca802b973d7defa9a8d2e031fa9a0e3bdc7ec927a9ba3e239c174f7e2925827cbe0e314d6275fcb15bcf3720e061fa6f0e75b342ead3ff83b838f52649eded75345830f52609bcde724f3dfec160c7c8c42149ce790d1e83dc9c30c848f5028395562fb5674ca988122dd78629e46f5e3134a496ba6707a37ea7b825e9bbbe8619e64d24e3c955b4e5cf950779d139b3017dc7fb71f7c6c423fb9f4e383c5b6aa77f0a109c6d2d8774a9572ca4b0f3e32118631a8933639200e87511005510c8230a437251da3135070c83c1a0ec6a2711ca9c20f138000cbc2c060381088c24151200c053114c4300cc34008c33024c34064c628a38c1e5f9c7a4763b44aefa8c58a5d091e1706b92db3db941f39306c66f7753634d143f52531aa001971eff8651410532eba83c458bbb10e65d5865af2913db980c2d7083412b5482c27a9205981561849aa150e79d7b11b4d42b56fa4c95890cc71009c7e53afbd388118c041a22ae2abce92872c60c6fee26200e0bf5f7eee30e5b898bea40b56292d51935be1190770f20ee2dffb74de20fa89e721bbe812e1fd619620e3a350f9b25283d6bb63606901d90d39810ebf22fff833992f6bf88a59180faec3c27a3d9a1e08439a9e253158fd95d3a625f40d8192bf6c908420cfea5105a0b87ec9ac4811e8c0650301b0e64facf2034dfc516c1fc4e24e00cd67b2db5c3bafbf26eade68c40d9abb7bf4ffb99eeb621aaa8b49fd41baba5229c2e7db1888f82f7db147ad9069b7f51a5cfa01dde1da574f8b46addf28513ebdbecdff39ff04e206d2fd3a099e8f055b9228940f241768f664fd6b234b2a7a01a80745725a40e47478dd5edfaa0e239cfdd4228658a74975ae7f88f215159e28e7f7c2dd1efa6d4613f24b0e045facf20e325ee1ad9e75d0216bf6664a9e8f11cd8b8b62ea12ccb17164a5e1b9a91ceaf5613f449cd187a4775260f3fbac0033730c4636dd4c18fc3a1d00359523fe8f5f383dad9d2a9a08b688aba810e6e37a88b6d5fe778eb82f8477bf4c49d20b3f8316209465ebc6f1a82e2b8d4a8d44720724bacc60118cb18dac76a3fb950d84dae2a53efb6feec4990fef8864784f2ee1ef1bb205b17421cc4f08bdddfd77a109e5487d3e42e8a89eef445f93fb896b0dd348bd445c5db4928f10e50725a6a93d0053ee3a8dec8a8c554974ca1c9f3e782cfbb8a374fae2eaad9c48140821f8472519dc60ca5103929b396866b4410aefa48032d4ee23f96b7844941ecc374511bdc59bd6e47841517f436f2443d3c3d5137c73b8c6b19fca26a62bc1869dbea0cb735ee0582ab6acac241941eb1a8399b6dc1c50cf8ce30ecb96c117882c95089335977749c11d413f487b75b522f89f47e081cc7793707d2758b79cba86fe6599d2e24acb66769ecf0924d7d01f591e42f1e8b22893dca599f36ccc02d63bb7875f537f12bc5a43da2c00c4ecd657e56fa1397c393ff15c3d8453abf3c2912c00ce24448a447c20c6d4dce9214ecd1f4075353e76c69b6fe5ce20fafeb0ad6c5f03dec04266f4cea2f235858fef7cd72a3b14105c008bb7a3e2fcca66868b74506f4c150dc533c66a17d4841dacb60b41541b258640a0f59fa7d8face0eafc996742062d11cd68fefb6ff9e6d8ff37f6a4b36c50bcd0de4daea347aa096818387896122904cc82529d48ed5bc38453552899f3552a2958ccaf8c572e663fd1f7488e22c9340ccc23928a51795d624639c5211e7be46f5b2f3f074f8e3e611f7802b7e968c35bbde28963f3cb7dd105890aa36641675bb294017a88ca00f74b4b60a4f3f5ef8f1df9530272bb748e7257852d249189d07460a7b99d0c82a4b05e5d04031c1cb51f929b8a43e6a6217b9f2affd7550f4b0637973998db0cb75ed1e60f6baa7b90cd8099c4c515ccad9e42a6df646b895ff4b44971473118bd01579ab0471c76e318d831ae48d47351f62e28f069781f37bc33772f1c2d27b2fbaece93dc8eb842c91bd315334b753ab3116074d8facb48713489b741c6efb397220f2cfe06370a9b0888857d07564ee66903e517492c990135c12b9b682cb58cbcac5bfbef48c0641b079f5e74e50d030f1f9d797842617f81b8fc72c369b59d7ad5f2c40d1e93656bd3dbcb11ef981529eedfe7eb460f22254e99015614fd30f0d7c85853ea662bdda8c830a766bfc9960f0b7a83eb25c0e35dd6b4c204d337acd6b91eb7e33ccd3ed6c1bca18b733d23ab48e681d84f89b436f23cf1d89e9066d53aa6d4980ec5fcd587a70084e6365598f2fac058f5e935a05f51bce14a0295491735601c8f58318fd044aeed6b2a627f2c84449090ae2ba14a1d3d0495869b7e88677eafd29d171b5a587f9115603fabf7c7a11a576ab4275d84bac9e4fded3c2d5af03c3bec05a8ba07e3bbd6a4ccaf5274c149c3593823ae369d1e23c8748e2c40270447479bdef73855a95ba359c93b1e76677ab1968cc4ff01940235fd9f9aff2d022f2f23c0561bc3e510a8176c0f3ebbad853cfe3cfd980855e247a872a01d7a3c101dfec16902babbdfef8531ea23233c2108fb3686f91e34245b5863c1756992d2e65f7e8eedb985df47045198588aad6418b350e60a32c7b75ef0b4b3ff6591bb921ffcfa2e0e1d088eae8b469b42a1a6a59672d6b8abf3d763722bb753f53320a56a86360bb041a65b294c73001f6f09a7fdfe6f2968e23c3d897e2bfa0122245d2195eb3828b201c5ce384225090c6939b0acd0996a955b433967151d0547533caa4986b6d0f3d5f4c653620105247655e257b4e440c6d44ac52806ff40fcdfc57483b1b14821134400721045b3adbc6d3747f317c4f44853be001274ec6d90648b3db664e0df71118cd689e04c59c39446532b80fc5a78454737330ec11c4c0024cf8f6905f1f54ed2e1611b9ad30de3d0e5a32ad5b65f53831873f3f246211b1c65a8aacf21d8e7a280563b8b34d2d3d7d541c591c21fe50c047e276d3837cf09191fd36b3bdd5d6c365fbefc4caf81ee30ed6689bdd3eb85db6fcc0e1b9b03002b1c5eb041fa752d43ba950127066339316f12c056a3c1315fec238580ec8223bef3e802a4a07afa3650d51c661e739f806a9639c5122d273f35a09a2f47ef1c136878451de97a51c6101bb352e0892fdbf8ffe89d3e4210f8ec7b34cc65b167e6af9b49350f3917767764c45cdecd86f985bf7c6046d24cee758ded220f67ed913d92378d057deb8985ccd6e6a255f35e98f4a5ec05c9658a98ca66e684f8441627eab4244bc0141b6f42eb417fce5b291414f23e76abd20ccfe7fa5bde47399b6fca00c88d95ae4cc46db2a2c4f756569f9c847b195a465448e994efb83482f733c3cb6e683391ce08b9d260e59ea533ee1ba4130beb04c5b0d860402929bf3915a9aad93f56db5756d6a85d8e1a4464d4aa0b324e213c4c18469061e21153e5b7d8e4bf88168349b018d9506cf9c4ff01c4039dfcd6fc30a50d9100ba36902af9ee013a121d0edfb7e7cca7dead586b0950ebcda7d9dead49ecb14837ccf0ad8aaa53a5eb6ec113301862ec604a3a054ab036ed9311ee267da95d12b7094cc6ae92f71c12adc19489837f135bd805997607026ea3955f0df311c3d303081ed49bccab80424791101a724f6888faa0f78de1f1a1712d2b96dcb03c09573fb09a44de51611ea55e1281ea34e0874ab12fda3cde5cedbed43d7694e49aad3b74230f74c8d0ffd1af398cbb8d38e598e03de751cf57ec22e64e5b61f871756c90025e8d199ef40384a1658ea61cb5e4f28c59e0cead024c4b88b4cd1bf8184c0aba16f6c55a4775921a2ef4d9cd3d816e53152cba3e66957b5879adad7d845e5d575c6ad06d6114f276caf096253c8967990d9160cc56ac96154cbe3ce736f2f1fa7ddebd7f6e761172f73a3057cad238e461301a7a6f854d298f9abd35833a15ec97e23a42c3518dbe89e2f3207c70c8db8e299c093150bf6c72678f2854542b9bdeb281353f1f0686d19e5def01c616cb19c5baa034c4019573a0b4b50de975e3ec532ea4808f4bb30976037b3e26ccb125608bf06ff0fe2be720de818bc23aa4764aa0b32f5dc2c2a3c859a7746035505954aca3a663b58b735825476e15ceb90aa5896d6373b283d44211c936ce267815501091355da8a8725da46c2c082cbecc5895f6cc2f7ca82a7db3d4241e43a77c1731bc7018cf1ddad7f4ac7bceacdbc9b74abe5742c82f6a6e89450f3ffa55fdc0370139ccbdd365f45208a554727e7adf8466d7df1f752130c97d6aaf5727280896774ea59e47aafe61b6b3b1a16c11b373fd43a89cf044c84891d420274b63184328050cfd6e7d6d1ea5b7dcc3f88cbbba25860c4cf00d345fe00b856e8aa5f6337a479c6e0f44b8755908b0d3960c3062a478c6c6e2b98cbf175225ea3a79cf12f2c67e8fedcb3e418a9374285dbbaeed712beb02bdb2399f856f9e0f11d52af5ee498baa4bbc92b10237cef8c7045102290aca055d113e603303d0a3170fc117be22ae194c591b7c8728316750a958f4deae13bf6ab8e40295ef8dfd4ab77a7edb3f15dc9f47f8a13d9230fe62730c837c11d7a6f09dd29df1ddb0367030ed3d89208404f19c2c2d37e5044b4198792fdb2c8239c8f20ce9e8997ca20df628863697eba96ea41b12a7aa7381ff304109868b0fba5a49f65923c6d10a1b8d4df30a707b91138c5a64159853adea822530ecced363544a4d5e17a5fb4a0571e8203161ef47aad7112f920cc00bf958e4680fba3f6a349965f1a8a269f253011e78e33fcfc9a6d606f796ee5909e5b3773306d397704e72b6a4acd946bb1dc77fa90baed3ea9c20c95b50f919b0fb2bc2824caf864329887d058b3ebc5daa8f4e0541eafc55822c18fd9d389920c570f13b68432498b23ba5f6a1c7b1cd8f9260ff6d979dd8891f949e45c8068a104e700662c507e5d013d6728f44b182db1ddd4f66cc460976af2cbc9e1952b9157f29ca3e4ab598886cac9e07376d289dd467f58d0dae7c2d402061eec7ab8b6d548fc1456a8a6383e82bd031512f339cae32d12e71093bce8967b7043ad4300763708ac3269862c5bc59ca3e241c5dd5941975a8fb00b8b627b50ba280ab74e74f938e8f916aa2b3a6fb0e29a14d79881a42333e9d904e92663ce0c47ad07506bdce2ce764311ddac7b4a3c19bac9129507b227b9994bdc4e825061f5e09e207b19fde4790c32ecb170274c7f3dc7a4d862e43a7c9fe0320001c940ab4b0ce95533b4128ae6e9b25ea0c7ac4be6b4eceefacb21fc6cf5933325a4ff4f2a605a83f4549d0e1dec83b568efecce140a728ac213071bf962ae8ebdf85e08ffa17427d540435e938c1908ec36a91e2cdff72194f1d0870232b7c86e2487da52081754a69a9ce349bf261d7a33e228a176ef7e2f4f15102cf63162eee54724daac20c5387ec17a52b3602165e11736c3c94fa269ecbb98505e2700a65d0a27faee321b9fefcb464c44103639531c9f22555417a882b80072cec78c8ef15e3ad0b3283fdd3778f85ab6229d6ac4505764c48267d7a36aa1787c470944df84363d6c3c1dff9ac3630d3d0b8ccf181466820c4652f15670fdebe94903f405572fe31f32af6ecf8d05faa3b4f7a3679ce03877f98c7d2b943fce08ff1ca35d0f5b82b7d96cba1f3e369e2d239ae8812d2045af21a0557ed54ecad28b75cc43103fdd5ef336f935e677143603ce4ebe11635ebc65a6e78228ad47ed7ad755f3c9070ccae75bb10f00357db40a8a87ab5c2b0f13811b67221d83a08da21624a0e949f83b7def703773a6844e12a160fbc739f89a52f6007cf531d4484b5d085f1a5ab13736c15654b32b97ed9507c16ff2d1d9e451a3e4aaff08fc83392047b0912c0fa44cb86f6607b3fd50bf6bb32c3721f094e1d2814abd559cdbb27116be3c1a06630496e0ceea84c4cc4f6ddc29e97f5fff6275a415570c231e416a3cfd773c25382ec79a1639f13a1c7f265e734c0049d12cb8b9f721865080e43b297d4e0a3f922bb56adbd0404443df98b92ef3bc7c3649c0d353e7a4b9a44a0583a74bf49caee8dfa2f4fd884fcc9fa9f6cea0ece931e03b0e863f0fcd1004379517ec4bb88d10b79de920d124bc2f29f430f2dfe615a73fdbe2aa835e8cae488b5d55fe4b830fbd1f9c7526209b158b822bafb43e0896addcc753cc6bf1a52c221a640ae68244aa2487fc832c9595d0ca517b3ea7b31bc72053969a5655678a3e130d57851c72a68dbe8249efe9071f6be64dbeb5117b5c6b985120eb6615805fe0851e3ed9c6b2e163910b1610bc751193f6a8844e34e99f69b5f520dbf848249468df3cf5ced20597a67eb153897bd2808b436c7e32a0466447942eb7720ccdeb5715e469e43e9e1462c13daac5bb32214b8a085b1f94805a96d86d692c3497c9b93a96b0ad2f618ba9cdba9d067bbf5add457aded77e5e95401b911d497adccb294d6d2ba9e4c6a5a6db8836e9b5d3404d4c17c06a33f6b54ba238181eb0ed300cdf60a6d7c90ea08cf796462384e4dbc6cb3f6a981002f009", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x1094e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe320676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702cc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x1094e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe320676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702cc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602", + "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0x52bc71c1eca5353749542dfdf0af97bf764f9c2f44e860cd485f1cd86400f649", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x88fbb13c02428a6ba0e3c362f503d78c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x9ba1b78972885c5d3fc221d6771e8ba20f4cf0917788d791142ff6c1f216e7b3": "0x01", + "0x9ba1b78972885c5d3fc221d6771e8ba24e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000100000000000000000", + "0xcd5c1f6df63bc97f4a8ce37f14a50ca74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb34db9bf7072c23e5fcc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e": "0xcc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb364a2023e1987811b741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602": "0x741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb37428b13f2e5363940676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702": "0x0676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3add4f66f85260a9b94e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe32": "0x94e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe32", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195092cf984b8a6521a76175726180741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602": "0x741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b861e1707ac2446d61757261800676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702": "0x0676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950bb7409db8b905d2f6175726180cc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e": "0xcc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d9ae2954d96d8a5d617572618094e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe32": "0x94e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe32", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x1094e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe320676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702cc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x1094e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe3294e8f841122bad62ecd5016f80587ef7d91043c828e3112f668523db811cbe320676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda7020676ce35043f553c1a3775a10ba54bd5757e48ebe38bbf2b4c4986896dcda702cc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5ecc4c407bd279279ebbfdb5ae2cd29b04ac748a90bcc23a910e303104e47b8c5e741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602741100320fca26c26c665a09fda76a2b2b11ab6d36acb8942132be5b436e7602", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x03000000", + "0xe81713b6b40972bbcd298d67597a495f0f4cf0917788d791142ff6c1f216e7b3": "0x01", + "0xe81713b6b40972bbcd298d67597a495f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf7327be699d4ca1e710c5cb7cfa19d3c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file From 58fef4fd972dffc53f676e1c7a48886d63857ad7 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 5 Oct 2022 16:25:34 +0200 Subject: [PATCH 74/91] [BridgeHub] ChainSpec Bridge-Hub Rococo live read from file --- parachains/chain-specs/bridge-hub-wococo.json | 1 + .../src/chain_spec/bridge_hubs.rs | 104 +++++------------- polkadot-parachain/src/command.rs | 2 +- 3 files changed, 27 insertions(+), 80 deletions(-) create mode 100644 parachains/chain-specs/bridge-hub-wococo.json diff --git a/parachains/chain-specs/bridge-hub-wococo.json b/parachains/chain-specs/bridge-hub-wococo.json new file mode 100644 index 00000000000..b96b5d4afb1 --- /dev/null +++ b/parachains/chain-specs/bridge-hub-wococo.json @@ -0,0 +1 @@ +TODO: setup diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index ed2f678ee54..7855f378667 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -54,23 +54,37 @@ impl BridgeHubRuntimeType { })) } - pub fn load_config(&self) -> Box { - Box::new(match self { + pub fn load_config(&self) -> Result, String> { + match self { BridgeHubRuntimeType::Rococo => - rococo::live_config(rococo::BRIDGE_HUB_ROCOCO, "Rococo BrideHub", "rococo", ParaId::new(1013)), - BridgeHubRuntimeType::RococoLocal => - rococo::local_config(rococo::BRIDGE_HUB_ROCOCO_LOCAL, "Rococo BrideHub Local", "rococo-local", ParaId::new(1013)), + Ok(Box::new(rococo::BridgeHubChainSpec::from_json_bytes( + &include_bytes!("../../../parachains/chain-specs/bridge-hub-rococo.json")[..], + )?)), + BridgeHubRuntimeType::RococoLocal => Ok(Box::new(rococo::local_config( + rococo::BRIDGE_HUB_ROCOCO_LOCAL, + "Rococo BrideHub Local", + "rococo-local", + ParaId::new(1013), + ))), BridgeHubRuntimeType::Wococo => - wococo::live_config(wococo::BRIDGE_HUB_WOCOCO, "Wococo BrideHub", "wococo", ParaId::new(1013)), - BridgeHubRuntimeType::WococoLocal => - wococo::local_config(wococo::BRIDGE_HUB_WOCOCO_LOCAL, "Wococo BrideHub Local", "wococo-local", ParaId::new(1013)), - }) + Ok(Box::new(rococo::BridgeHubChainSpec::from_json_bytes( + &include_bytes!("../../../parachains/chain-specs/bridge-hub-wococo.json")[..], + )?)), + BridgeHubRuntimeType::WococoLocal => Ok(Box::new(wococo::local_config( + wococo::BRIDGE_HUB_WOCOCO_LOCAL, + "Wococo BrideHub Local", + "wococo-local", + ParaId::new(1013), + ))), + } } pub fn runtime_version(&self) -> &'static RuntimeVersion { match self { - BridgeHubRuntimeType::Rococo | BridgeHubRuntimeType::Wococo | - BridgeHubRuntimeType::RococoLocal | BridgeHubRuntimeType::WococoLocal => { + BridgeHubRuntimeType::Rococo | + BridgeHubRuntimeType::Wococo | + BridgeHubRuntimeType::RococoLocal | + BridgeHubRuntimeType::WococoLocal => { // this is intentional, for Rococo/Wococo we just want to have one runtime, which is configured for both sides &bridge_hub_rococo_runtime::VERSION }, @@ -110,65 +124,6 @@ pub mod rococo { pub type RuntimeApi = bridge_hub_rococo_runtime::RuntimeApi; - pub fn live_config( - id: &str, - chain_name: &str, - relay_chain: &str, - para_id: ParaId, - ) -> BridgeHubChainSpec { - let properties = sc_chain_spec::Properties::new(); - // TODO: check - // properties.insert("ss58Format".into(), 2.into()); - // properties.insert("tokenSymbol".into(), "ROC".into()); - // properties.insert("tokenDecimals".into(), 12.into()); - - BridgeHubChainSpec::from_genesis( - // Name - chain_name, - // ID - super::ensure_id(id).expect("invalid id"), - ChainType::Live, - move || { - genesis( - // initial collators. - vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - para_id, - None, - None, - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, - ) - } - pub fn local_config( id: &str, chain_name: &str, @@ -302,13 +257,4 @@ pub mod wococo { ) -> BridgeHubChainSpec { rococo::local_config(id, chain_name, relay_chain, para_id) } - - pub fn live_config( - id: &str, - chain_name: &str, - relay_chain: &str, - para_id: ParaId, - ) -> BridgeHubChainSpec { - rococo::live_config(id, chain_name, relay_chain, para_id) - } } diff --git a/polkadot-parachain/src/command.rs b/polkadot-parachain/src/command.rs index 5e0950a5673..fe8dc655869 100644 --- a/polkadot-parachain/src/command.rs +++ b/polkadot-parachain/src/command.rs @@ -203,7 +203,7 @@ fn load_spec(id: &str) -> std::result::Result, String> { bridge_like_id .parse::() .expect("invalid value") - .load_config(), + .load_config()?, // -- Penpall "penpal-kusama" => Box::new(chain_spec::penpal::get_penpal_chain_spec( From d9441aa144a9efab3df75bca2901a20e9edfed7a Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 13 Oct 2022 23:42:04 +0200 Subject: [PATCH 75/91] [BridgeHub] Added default chain_spec for live Rococo/Wococo --- .../src/chain_spec/bridge_hubs.rs | 116 +++++++++++++----- polkadot-parachain/src/command.rs | 8 +- 2 files changed, 90 insertions(+), 34 deletions(-) diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index 7855f378667..ce1705822a6 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -14,17 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed}; use cumulus_primitives_core::ParaId; -use sc_chain_spec::ChainSpec; +use sc_chain_spec::{ChainSpec, ChainType}; use sc_cli::RuntimeVersion; +use sp_core::sr25519; use std::{path::PathBuf, str::FromStr}; /// Collects all supported BridgeHub configurations #[derive(Debug, PartialEq)] pub enum BridgeHubRuntimeType { - Rococo, + Rococo { default_config: bool }, RococoLocal, - Wococo, + Wococo { default_config: bool }, WococoLocal, } @@ -33,9 +35,13 @@ impl FromStr for BridgeHubRuntimeType { fn from_str(value: &str) -> Result { match value { - rococo::BRIDGE_HUB_ROCOCO => Ok(BridgeHubRuntimeType::Rococo), + rococo::BRIDGE_HUB_ROCOCO => Ok(BridgeHubRuntimeType::Rococo { default_config: false }), + rococo::BRIDGE_HUB_ROCOCO_DEFAULT => + Ok(BridgeHubRuntimeType::Rococo { default_config: true }), rococo::BRIDGE_HUB_ROCOCO_LOCAL => Ok(BridgeHubRuntimeType::RococoLocal), - wococo::BRIDGE_HUB_WOCOCO => Ok(BridgeHubRuntimeType::Wococo), + wococo::BRIDGE_HUB_WOCOCO => Ok(BridgeHubRuntimeType::Wococo { default_config: false }), + wococo::BRIDGE_HUB_WOCOCO_DEFAULT => + Ok(BridgeHubRuntimeType::Wococo { default_config: true }), wococo::BRIDGE_HUB_WOCOCO_LOCAL => Ok(BridgeHubRuntimeType::WococoLocal), _ => Err(format!("Value '{}' is not configured yet", value)), } @@ -47,42 +53,74 @@ impl BridgeHubRuntimeType { pub fn chain_spec_from_json_file(&self, path: PathBuf) -> Result, String> { Ok(Box::new(match self { - BridgeHubRuntimeType::Rococo => rococo::BridgeHubChainSpec::from_json_file(path)?, + BridgeHubRuntimeType::Rococo { .. } => + rococo::BridgeHubChainSpec::from_json_file(path)?, BridgeHubRuntimeType::RococoLocal => rococo::BridgeHubChainSpec::from_json_file(path)?, - BridgeHubRuntimeType::Wococo => wococo::BridgeHubChainSpec::from_json_file(path)?, + BridgeHubRuntimeType::Wococo { .. } => + wococo::BridgeHubChainSpec::from_json_file(path)?, BridgeHubRuntimeType::WococoLocal => wococo::BridgeHubChainSpec::from_json_file(path)?, })) } pub fn load_config(&self) -> Result, String> { match self { - BridgeHubRuntimeType::Rococo => - Ok(Box::new(rococo::BridgeHubChainSpec::from_json_bytes( - &include_bytes!("../../../parachains/chain-specs/bridge-hub-rococo.json")[..], - )?)), - BridgeHubRuntimeType::RococoLocal => Ok(Box::new(rococo::local_config( + BridgeHubRuntimeType::Rococo { default_config } => + if *default_config { + Ok(Box::new(rococo::default_config( + rococo::BRIDGE_HUB_ROCOCO, + "Rococo BrideHub", + ChainType::Live, + "rococo", + ParaId::new(1013), + None, + None, + ))) + } else { + Ok(Box::new(rococo::BridgeHubChainSpec::from_json_bytes( + &include_bytes!("../../../parachains/chain-specs/bridge-hub-rococo.json")[..], + )?)) + }, + BridgeHubRuntimeType::RococoLocal => Ok(Box::new(rococo::default_config( rococo::BRIDGE_HUB_ROCOCO_LOCAL, "Rococo BrideHub Local", + ChainType::Local, "rococo-local", ParaId::new(1013), + Some("Alice".to_string()), + Some("Bob".to_string()), ))), - BridgeHubRuntimeType::Wococo => - Ok(Box::new(rococo::BridgeHubChainSpec::from_json_bytes( - &include_bytes!("../../../parachains/chain-specs/bridge-hub-wococo.json")[..], - )?)), - BridgeHubRuntimeType::WococoLocal => Ok(Box::new(wococo::local_config( + BridgeHubRuntimeType::Wococo { default_config } => + if *default_config { + Ok(Box::new(wococo::default_config( + wococo::BRIDGE_HUB_WOCOCO, + "Wococo BrideHub", + ChainType::Live, + "wococo", + ParaId::new(1013), + None, + None, + ))) + } else { + Ok(Box::new(rococo::BridgeHubChainSpec::from_json_bytes( + &include_bytes!("../../../parachains/chain-specs/bridge-hub-wococo.json")[..], + )?)) + }, + BridgeHubRuntimeType::WococoLocal => Ok(Box::new(wococo::default_config( wococo::BRIDGE_HUB_WOCOCO_LOCAL, "Wococo BrideHub Local", + ChainType::Local, "wococo-local", ParaId::new(1013), + Some("Alice".to_string()), + Some("Bob".to_string()), ))), } } pub fn runtime_version(&self) -> &'static RuntimeVersion { match self { - BridgeHubRuntimeType::Rococo | - BridgeHubRuntimeType::Wococo | + BridgeHubRuntimeType::Rococo { .. } | + BridgeHubRuntimeType::Wococo { .. } | BridgeHubRuntimeType::RococoLocal | BridgeHubRuntimeType::WococoLocal => { // this is intentional, for Rococo/Wococo we just want to have one runtime, which is configured for both sides @@ -107,15 +145,13 @@ fn ensure_id(id: &str) -> Result<&str, String> { /// Sub-module for Rococo setup pub mod rococo { - use super::ParaId; - use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, - }; + use super::{get_account_id_from_seed, get_collator_keys_from_seed, sr25519, ParaId}; + use crate::chain_spec::{Extensions, SAFE_XCM_VERSION}; use parachains_common::{AccountId, AuraId}; use sc_chain_spec::ChainType; - use sp_core::sr25519; pub(crate) const BRIDGE_HUB_ROCOCO: &str = "bridge-hub-rococo"; + pub(crate) const BRIDGE_HUB_ROCOCO_DEFAULT: &str = "bridge-hub-rococo-default"; pub(crate) const BRIDGE_HUB_ROCOCO_LOCAL: &str = "bridge-hub-rococo-local"; /// Specialized `ChainSpec` for the normal parachain runtime. @@ -124,11 +160,14 @@ pub mod rococo { pub type RuntimeApi = bridge_hub_rococo_runtime::RuntimeApi; - pub fn local_config( + pub fn default_config( id: &str, chain_name: &str, + chain_type: ChainType, relay_chain: &str, para_id: ParaId, + root_key_seed: Option, + bridges_pallet_owner_seed: Option, ) -> BridgeHubChainSpec { let properties = sc_chain_spec::Properties::new(); // TODO: check @@ -141,7 +180,7 @@ pub mod rococo { chain_name, // ID super::ensure_id(id).expect("invalid id"), - ChainType::Local, + chain_type, move || { genesis( // initial collators. @@ -170,8 +209,12 @@ pub mod rococo { get_account_id_from_seed::("Ferdie//stash"), ], para_id, - Some(get_account_id_from_seed::("Alice")), - Some(get_account_id_from_seed::("Bob")), + root_key_seed + .as_ref() + .map(|seed| get_account_id_from_seed::(&seed)), + bridges_pallet_owner_seed + .as_ref() + .map(|seed| get_account_id_from_seed::(&seed)), ) }, Vec::new(), @@ -242,19 +285,32 @@ pub mod rococo { pub mod wococo { use super::ParaId; use crate::chain_spec::bridge_hubs::rococo; + use sc_chain_spec::ChainType; pub(crate) const BRIDGE_HUB_WOCOCO: &str = "bridge-hub-wococo"; + pub(crate) const BRIDGE_HUB_WOCOCO_DEFAULT: &str = "bridge-hub-wococo-default"; pub(crate) const BRIDGE_HUB_WOCOCO_LOCAL: &str = "bridge-hub-wococo-local"; pub type BridgeHubChainSpec = rococo::BridgeHubChainSpec; pub type RuntimeApi = rococo::RuntimeApi; - pub fn local_config( + pub fn default_config( id: &str, chain_name: &str, + chain_type: ChainType, relay_chain: &str, para_id: ParaId, + root_key_seed: Option, + bridges_pallet_owner_seed: Option, ) -> BridgeHubChainSpec { - rococo::local_config(id, chain_name, relay_chain, para_id) + rococo::default_config( + id, + chain_name, + chain_type, + relay_chain, + para_id, + root_key_seed, + bridges_pallet_owner_seed, + ) } } diff --git a/polkadot-parachain/src/command.rs b/polkadot-parachain/src/command.rs index fe8dc655869..2845d2b2ec7 100644 --- a/polkadot-parachain/src/command.rs +++ b/polkadot-parachain/src/command.rs @@ -481,12 +481,12 @@ macro_rules! construct_async_run { Runtime::BridgeHub(bridge_hub_runtime_type) => { runner.async_run(|$config| { let $components = match bridge_hub_runtime_type { - chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | + chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo { .. } | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => new_partial::( &$config, crate::service::aura_build_import_queue::<_, AuraId>, )?, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo | + chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo { .. } | chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => new_partial::( &$config, crate::service::aura_build_import_queue::<_, AuraId>, @@ -781,13 +781,13 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), Runtime::BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { - chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | + chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo { .. } | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal => crate::service::start_generic_aura_node::< chain_spec::bridge_hubs::rococo::RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench), - chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo | + chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo { .. } | chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => crate::service::start_generic_aura_node::< chain_spec::bridge_hubs::wococo::RuntimeApi, From 1f27ee76901e056d3e8c934f84ae2e828d293332 Mon Sep 17 00:00:00 2001 From: Anthony Lazam Date: Fri, 14 Oct 2022 15:32:37 +0800 Subject: [PATCH 76/91] [BridgeHub] Add Live bridge-hub-wococo chainspec --- parachains/chain-specs/bridge-hub-wococo.json | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/parachains/chain-specs/bridge-hub-wococo.json b/parachains/chain-specs/bridge-hub-wococo.json index b96b5d4afb1..9d354ad11c7 100644 --- a/parachains/chain-specs/bridge-hub-wococo.json +++ b/parachains/chain-specs/bridge-hub-wococo.json @@ -1 +1,85 @@ -TODO: setup +{ + "name": "Wococo BrideHub", + "id": "bridge-hub-wococo", + "chainType": "Live", + "bootNodes": [ + "/dns/wococo-bridge-hub-collator-0.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWCNomXYZWuhwHsWhZpmrFmswEG8W89UY9NjEGExM38yCr", + "/dns/wococo-bridge-hub-collator-1.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWKSq37RLqP3Ws3FtJDYB1xsjoBeJmehVYDZcCDRNLBXas", + "/dns/wococo-bridge-hub-collator-2.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWDkSQzQYC7VwpJKF8VJtJZMG8bcvWXm1UEJSKk8UE2iv5", + "/dns/wococo-bridge-hub-collator-3.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWQoUFxyPbpotTdUpfnsxQfQ4uyxz1beW5Z39LGM8JPhLi", + "/dns/wococo-bridge-hub-collator-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWCNomXYZWuhwHsWhZpmrFmswEG8W89UY9NjEGExM38yCr", + "/dns/wococo-bridge-hub-collator-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWKSq37RLqP3Ws3FtJDYB1xsjoBeJmehVYDZcCDRNLBXas", + "/dns/wococo-bridge-hub-collator-2.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDkSQzQYC7VwpJKF8VJtJZMG8bcvWXm1UEJSKk8UE2iv5", + "/dns/wococo-bridge-hub-collator-3.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWQoUFxyPbpotTdUpfnsxQfQ4uyxz1beW5Z39LGM8JPhLi" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": {}, + "relay_chain": "wococo", + "para_id": 1013, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xf5030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x0a000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9050f9ffb4503e7865bae8a399c89a5da52bc71c1eca5353749542dfdf0af97bf764f9c2f44e860cd485f1cd86400f649": "0x0000000000000000010000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b4dbfc3b7761206de75b3a8d70fc3d44a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b908aa810c364ce8c3bd964ff3d424cc926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f52c4b3c3fd1c798e3843e21a38f1421b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9ff9bdc7d7afef8c14d5b253d4e25b33db0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x04446272696467652d6875622d726f636f636f", + "0x365c9cdbf82b9bda69e4bbdf1b38a7834e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd00587c78048e21c5d10f4f107868930efab1b838f4ab956e37666c7f315033ee36fc62e8f5cc1f2729a56a67ad16541632755b67b4c33c707182dcceb49492c72443bd03548e239738f428dd94584236217b6fb2a59449ca94649a119b0ee10e5f4d2b20202028702a8fb7706a8fb7dc3d1b1f3e809203256859831668e0583e3935df72adcb5b4fd4f016ad3d353fa9e683e6be6a2fe984db6bebdab39938eba767b3404040404138eb6fd318b5ce9b8969d66160a0f1e0b1772b2668f0aec7ca9f552d845560e20926618c728caa3c7e7df46eb504973ff1389d14703ab976e7d78e19259e8d7b342e0b22092020a0a0af4900010141f92a2c775915418e63addff0e4546d97ced1a1d79e9a973e19c53eb70468edeea3dff0dce6bb673371985d22b1bf9422a9f9487920f6bbde1b929ae7798e316add653a903a463dea18b57ec39a64eec1055385265fa582227c118420044901065ec071ee4ca7d37b43dad9df7b8fd9613b3fc8db868d437addbd89530b9d73a0df706f335504398e749ff741c838e288838d7fb40a497f6a70ae88744608f8cf61fbf3a63efb90c2dfcdfdcfaa9ab70dd09d5ff0b9f3eb6dbcb1679be5ac5f2fb6477fbe59ce8b7dc188c12ce77596b3ce59bfcd8070bf0eb7fdb7bb65092d5fe7333b33b316cfccec0eba1eac84dd3985e25485274e3defc1a9fdb744d03fc7d8e6e1ec571ef82b8f7ffe1c72aac7a33c3523a8d1a0d0dc4a0552bc5b29a1c657d4b31be0dd4a89d5bb2c47bb614dc338c5a9e8ce21a7a4bff34b6eeee3b67fb90368d2e5c336cb890f5049bf360b22baa4357af48bb320b48fee32c828e82ecb69bfe1e82ebb5c366955fd74b9d529fde6c9adc297eeb29ce997dc6a035efae5328bd624de72b955b77ae971ab8e51492839a248065e7a758caa44bcf41a817871736f6d4b5e1c71c4f1e5a3cbadfaf0d0afc5e4067d08f9726e4be4475af7255db271c411471c5f55dfd1e77d53c7a86afaf59a773aa8e642262e675a41e8725a139a5b2961e5eb7cb752c28a778eb1cde2383f71caad80beb8af3cdeb9ebe16e2ec703a79cbbcd3d6ffbbbedf3e61e728addf9b5d7ba6c97a413063718e5febd2c87a973241d313c6194fbf5b75d0fe36d9fb37597417ac32ecb998c7ad762f0626cb308b867df2b5cee997d99e90dab761f70adef366a83b602e2f2153efb5c9fd33d1bc73455fbf5aa3ddc35e23d7eefbdf71eb3cf673a23043b74665ad91ff7a8feec974b69973b80e67cdd6539ce37fb210920202025de51c7a83a9ffd62cc71ea39a7d6318afd8677838c62ef41abda33fb8953eb264eadb36b9c728c72ee5d06372472f826aaf9cfe73f3a23c473e8cf21f5d95fea8fd6fdf51e4c79fc9d0eaab79b85cbbdeb51b567bf26a7ea8e1928da7ac59e0ba0ad3f77eeb221d6791e9d8caaf09ddff0b5dbfb8ccb238189aec576835e7e897c244abfce9e39214c16e8972ce091c07e9d5de4843059a05fb2804722fe3afbc80961b240bf6401bfceeeb2202010d0afdff0c5987342d6eb73c728e7935375ffb9f3eb6d9351ce6bb4e7ce5da60319e5fcd1c928e7377c31b61b3fe68430d9d523117f9d3d7342982cd02f59c0afb36f368406f4eb18a7dcb3bb2c8805faf51bbee11bbe61b7394639773d98f268ef7450f1b2172ec797a3d1d69d3beb723eb475e7cebad61da7aaf3a58e51ce9dab76735c6e0554e59dbc9c8db6eedc593bbcb03168628dd00e27ec586307187678b1a38b1d58764461870c764471e2b2c3891d2a908942260d1930648620636507143257c8f840e60732639009834c1176ac40460aa73064a89cbe2013049918c8b0e0d486cc0a64c07072e304874c904c133267c8084106caa90b272fec68c10e169cced81124d3458609192fc890e1b4e56485531664c420f305992ec8a071da824c144e65d801744a43460b3259908142e6c9a98c1319322dd8f16487122736645eb0038acc1464b2c87091f942468acc123b9890992263864c19323090c1828c0b64542003a4e38b0e357490a1a30b3accd051868e2ae8004387157458d1d1021d5062b2104346cc1662ca881923264b4c1562cc88e9124385182bc480117385182d315dc44021e64acc14629e10f3454c1762bc00c325460b31528809430c1860ba605ac2a40293122620983860e0807903c60d98364c544c3330b5c0c4021314a628a6274c4e987660a2c2a403530e4c38305531ddc03485c90626294c3530d1c00403d30b4c2e304931f5c06485a90ad3149836c0b001f305660d130f4c4c989ec0a801460d9834c09c01c60b4c193064c08001660b4c1660b000a305183360b480c9022606302c8079018c0a60942879297da12446c98b52124a482819a15483120d4a3328518909a314a5e444690525264a2f28c9a00403d217121ba438486f90d44082838406d28a7485d4041214485e90ba207141a202290be908242290b22899413242290d120c483f204dd1dad0c4407a01a90a4d0d5a1ab42f9a1ada1a4854907a40b2421b834606cd0b9a14342c34236835d068a0cd40a3a2c94063c1a88d111b34218cd8d09ed054a029315283d683d117edc9680c2334465db4238cc61889611486111846678cbc30e232326354c6888c87863584ac0a182690bcb40a4852387d094de199e134060764164e67b07cf0ce7002c3b252c40a82d4c22bc3c90ceb07325b6494909962cdb07a6009e191c102c2c9cb96f1d2d8d104a90d323d901143260da735442684a22013c649cb8e2564aa20a3857fc03ec8b630dbc8c68039c2cb12b190899171c1da41a90dad091a1332282218528bf4e28d4172414e418221b110c292fd00a60c3a7ea0c3073a5c3000016443785e62164f8ac9847641e68337c5f5020b0c8c0818191a1118800302b9ca40113ab890da2805959e2845b15cc431b42160565c49905f946220577021e162036a8155805fc82e4030ae2fd913305e30726d712de16242898cd214fc45282804c5cac2324208286eb1e2885798638cdc082961b9b1830d27345855ac362c36e415425e94c840b28175c555053ac14fc87a00f345d602182962ce904143a90c302d904f985cd809310bcc84c601b606d6054c0c581830366064c0e0c0dec0dcc0be606dc0dac0d8c0d0c0ca809d81a501c302f38279014303b6066c0c981ab02ed811302360596045c0c080a5817d013303a6c66586eb0c5719a6174a6c685dd0b86866686568646863685cd0b6a089a16dd1b4a06541c38216867605cd0a1a189a16ed0bad0a1a15b42cda142617320c19052985521b200e32317017328d1831ac12768b98342c1ebc2e5c858404790566b1aa8054c89a28d9c0b24266592690b4882c601c9cda10d910cd8864c4322217183490a63861217a414279522e3730155c6dc89e904d218302f6e4822373429625bb72b58129119f8850c4284f0d0f0dd9161917d912de12deea31616a51eac14442c7a067d0516445c8b0785108bd71311182c3ba8235062b0d0b8deb0d6c892b0e182c9da57bd0573410fa07ed83d6415b89f9421625931243063986dc82dcd2001d5a1a8a24230b426c41e98be946480a3035804cc0277009d803a8021854aac2e88da7a514049826c044c157202581adc060482cec85b5c62c639bb04bd880e46257d20a18d03a41963144c602d9c55cc2e98d0c8a7c2286cbb5067b81b9bc28d918486980a1522a833405d612adc05c442e30d08363e36028c42d40200b0eeb8d69460c8357d9166e42e720d34214236a619231b9c0572414a4183c8561d0636834f849a84b480ccc44c88c10975019a12e84c80881217406cb80a970143c038e01d78069c04d48332417192f325778714427da8ba482ac02cc19d209305f886ad08016c50215809141044a6ec0cc00260d181a2c20472b02901f062080b644a3e1da777b59c1e2b7d3c40808882831b273041122d73e2390ec1021120a054104a3800187589c113e4d764040e488923781cf0f498a485244920938f4a2082249428024889e9e240cf02962a7a72709030600bb704d7a8e18f1d93182c8ce4e080200a1e0928400c911222660b2438225448e28791f243b23308111221c5e713b45204982848811224794fc4e11489244c0070909921451c4df009f60449222768cc8013ac13531c10e0f91264d4cb023049224479a34d9a1c006b9702538a2e499ecf0304982812136b8723b3e468c38d2630409223b24d82181042a60c48e084460a40203804d704d788ad839a20826b8263e487698ec1c81e488c3252039128219b8852be24813244798f824b143c4043d4a941821c264c7889e2324c0e12922490f0e0fa804370222467c8c1861c4670702249884131ce93172c4e70892233920121c4f11497a404064c7870445ec24b153825a3826491880c123b8263b477c8e304972840f098cc0c02c1c9224477c8c30218111447c92f434217244c91b614282cfe176a70704467c90902004447c8ad86172c4082447881091397518d28a176858f14e75b91510d0afeac9c44e43a76195ac84106008303f000210780033333b21de830f76538b5a9bb2badfa36db545adb62c8b52eb596dd186164c5916b52c4aa9d56df1c285bd16a5d4b2bad96aabdf7b56bfd76b5956aff5baad5d6bb7772df8765fec85fda0739673d239b7d182fd245cf6615952b6dcb52cd8ddab492861b7b56c59afb7ad7dddfc42b043d87b5648f65bab03f042f1bd197b614f29313892326ad02decd7bb6fbb771db00fcab600b0bb413cade577af655996cdeb7ebd0bf9b525e542b96dc1deddb576dff6badb82fdac65b8bd16ef365b0ba55c6ba1e5203f08a1f5ace79ee51e5bdd2b65eb10daee1ec95d51ef5a701b4aa84509b1910c8542efc96eeb416cf7ed42cb52809523df7b96730c84ce0f393e721ac0d1b22cb62cca009cc73ee4636959dab3ac8c2d6bb7dbea0e3d0831f8205cb8fd2486edcaf4955dd66f775ba6b7a5f76e745c988f05f4c3ba77a1732b7ba58c50133d0865c3c7ddf2c151f7cb2e0000404a0877b15dd83e307ab990d51b820a48c07ccf877e6f77d7b9f77a080000b04377335ce6dde56eebf56ef6de5bcbb29ed53bec055993526212c27dfba08d8d1004d874109bb7701976f7a67adfce744bd7ddec03be968d59f4bded03583dccbceeee1d7a33b8bb52ee6e76b56561eff50efb5caf65657d6def5a0680b017f67b0feef6dbdeeef72c8bd2b79c5deff57b565bcfb220907e0da4002feeaeb5d6ab793f9e65edf0f6317c10428861bbdb524a299f84b0350d6a1a848d61ef4909a17c0f4ad9566318f62ccb7addfdaeb69a00dd6bc1dd0e6d6843d9927677d8de87f9d819282526257cdd52b60ed27296dbe12ccbb5eb0e59d6f6c8b2ac1db6f77aeff57bfd963188c1d7af777978dd036801f485c3d52bb769b6bbe57ba4b55001e80e4000baad05c07b4fb6ecf7ba17c2b743f7ae65596b5996d5ddfd7ab7772526b79be19390610f1d74d04187d188beb796b5dbcef56e5b2d174228bbb79f7bafbbdf83af1bc207bbfbc1f73608a1b5d6f65addbb5af77bafe1ae6575ef7b0dfb75871ab2f5dada6ee77a8717769cf170700518c1111f242460424921f021c10d3b4710f141728489122245343932021e6ec40e931d23982421923a00909d234c7c041080924cb683a7082320e8e94922801c76e080030e3237f008214972c4a708234d901861e243c4e7c8084690049583926690ec28516284470fa91b924852021f1c520700e23704015200224076788cf480808808901c39a28991233ce4c3c4884f930000d181878f11263b487644c0a4c90e330ec84e93263b4c948c2049114498ec2091a4881d26448c3431a2881d2205e0491d00889222769ad8cc238af03102022247343982e448087684e008931d248820a0000738001023768824f15162022645e8305d017690d8e92192849111e420e30a2082243d444480e488084490a447c6003d3d47786a4e01d0e180f82449126384911d262813ecf010f1496207090f39441000932b40139f264792d811008c0392c49110ec30515253da11811126497c9a1031a2e7c8114510f1e921819110ec1841e34680c3eeec3009011126497c8c1491a4a7c7088f92cf4e134702c9ce23b902104192e44813263b4810616264a7a7082249ec1881e4081124478c1c514408429084884f123b1409243bafe4d8044c928c6067041109243b4f009913f8e7e7272ba25ad5aa56aa463ff0a97ee4fb51c1f8f3a352a97e7e7e7e7e54aa56a97e7e54b0886a7f7e7e7e7ef6e7c7a97eb888fb51a95e919f755711a752858aa8d6a97e5e919f2da2fa51a97e9eeae7e70716f951edcf0f17f9d99f9f9f57c4fdfc7011f7b3459c4a1671aa59c4a9ac223fabda224e158bfcec8f8a8b38c74554eb545d44b5ac82459c6a552ad52be2545b44b5aa2d1201588675ceed089a187101ce41d823cd6e9593101614c2a8c41e8568557dc8656341524223cac38d3911591bb9167b442b77f7955dec1dbb636f557b9593d8eb5850c4a87c7d1787a46cc7821a93f2f585a2ef7b756251fef9e5fc176beef4d518e5b531b65fbb5d8c41b659208cf64667325f7f1cf46ca09492631cf4b03dd21a447bf4d81eb1202c4a8c118bf2ab653a9847ca13fdf211b6d5fd1679894a89fe60bcbde4994e3b8fa4589bb629a3c33c8755a4a425116d6cdded625bfcc591fbe3d6edb1afe758cc74a45bfeb6f69b27a19413a7f89daf149c6a1dfed1a322e5a14329cf065691f2d031d236a384b6405f998a94d3e54af97a30c6b0ae3e52ca902f6f68433dd806fd4acc89fdb13d58c823d0bffaa83c7458e5d9cc4703ab48f9998bbc6ed08732c7bcc20f5d2edd97aff333af4c65b4414665b4fac8972ea275833efacc30cc891db217c59c08853c6639212cca5b524a893901adbcd341455a1d628c768b287f8d78708c31b6739ccc1e63f45dc63cfa0d5f7146cab38e398c3e1bd3aae9dd6c6b37cb9d0c824170fa064d2acf83cd29e54a918e697ced92345af93127d1cab4cec998740c6290e9ccac0941e8106e106f94b8512c6b8439368a21d7a076f1c843158eac2d7eca5db93bb1b86e5d5bf49bb7eb040fece7faa4153eedd988a8bbe314e6ec9007fa74e9157b098798749d88decfb1e98df9e513a32197983f8cd6ab9faf13d343749d88316e94b7e69c739d783fabba9ea68618a984ba3cbf6a3895b9f32beeeeb6fb30cfe88ab13b8e16c2071f7cf0a23ceca37e508323bfe1fd91c3adceef7972d48373fae5789bee48da08d2d07541bf2ee8d705fd62359e47facf4327ba2eb855f830e4708730f49b07b7ca69c44b4479422e619c2f8f6c1a42ba8956e62e8f56ed1f3d3948af904bafd085904cebc258e4d9dc94300b4287d1e10e811ea963d44238b72cd3890e290ff49163d17a240d92ae4c273acfa579a8c82bd13a314ab26884ae5959ce3e2a126ef12564873288eb47ecd37b83be5bbb88b1b843ea42086bbca35f1774b8b9df0c3eed3dcfa0a4f5147a91d6f910f240bf5cc22132aff3333a237dbd9f3feb8568f4aef37542ce7359212f72f9bbbcce7779d5aed3f3b84342d12ddf2c67b9cb4fd8f1e5e61e42e8371c37f832c2575a131344ce58e6a8fd72cf79ad795783bc737f3623778ef28153d347dbbb2ee87363dfed72b941c7b61c3eda9ec390442f8788176985d167f4b958449f908a50c41cbef4d07ae8453931ca13c27cfd86378b1d125dca50496216adfc21bf61cb8a96f4d8b16347ca13f2cc63bc1e26e97adcaaa672229d23fb62219d6385547c85eec33c96ffbc64e7b9b4aa7e9d37e8d1178b1dc21ea9145fe1ce1be66c88ea155281eb30e4925648e5a1fbf0470f71bf7efd3a447962acab8594524ae83221a25b5acc555d2c71267376e80b4349225a4f414497434497be5638c558b094d5872ba4f2ecd7a6653a22979447bae5a36c08eca38bb6ba1f99e430d4444b543a7466afb014fde5900e5d94e9b0f32c15795664dd04d34b4bd0492c8ad1b12c2762f92bb4b9cfb2cd7d2846b9b5cb9859d0178bfd213d663aeb97c32d46ba56da633d391ce7379cd226e07046ed7b0ad17ae2d82b5acb37140a854494277a16ca6287489f9c82aeb97b36cf350a21e5893e7d5d8b97abc5ae16cbef6af995d34331140a85a2877cb320b2f5a8c52f9db17c5db69f4e9ad61e72e9bbcd40d17ce0af571f38b74aafabc54f1f6dd8361925a2d507ce4c675dee6af199af7ab1f46a09b96cdf2c428ef9f4b512b39c09a9fc1537f7a1addf0a6df1adcd7d8c31b4c5e8371cdae05b59ec0f193bf464b62c5035551eb5c7bbca430d66616ec977e57a53dee6259fd921f328bbdcaaf6fc7a7ec9cb6b9e5faf0a6f3c99fffcc3e1a255f59764e99cc38503901dc2de5e71706f1c68e5773645aa0fa1e7ce3ddace7cf9740fdfce94e7f2cc9ff7c0291eb2a6a6a6e655911b4f7649f9bce63d2a5fafec06af6fcaf4ca6fca47bf1e7cf0c1de497df82dcaf33cf366eac3bf2ecb833ef4f04d6b14e279d6178d99efc5589167e3664873c7291c20905120fba35dd2adc2cf81369d617f3637305e32c5bcf7a4d7c837a5e6f0a6fcba65c9cc2fb901d91fab853c078c03d96a0ed4bdc21ce40c6d9a03c981027957feba36f7cc9bfbebbdebdadee38c02a9796ee1b0617ec3ebe6b1553eb9cfd03a7d07addabb0cf56773a2247a3a8da8c95f44dda381a125209768442af26820d5469b7b3421d1e61ecd94d3e59397cb4933203df4e083bbe2b2549fae4b6b0f1f3da33cedd2eb5e98dc6a0fff2e0ac487bf8b5cee5ee637cfa608ada7d345abc6e5a103d921d263f7f0d5a77bf8f5cb2bcce0e53c190d5d5ea487cfdc872e573ef3eabc3c74593e735f80ec908bd61005f2a6bc25a5944016e8875521796282ab9c1c55e35e83e4e0d5ad70702cf67bfd056b2cbc85dcb90e4e7d73ccdd73701cde56359593e70ebead7618d7db414a7fafdd72b8439e5b2d77a037fcfc86e30e91dedecf01b08303a069e5bf7c075af92dbfe1ca5dd88b88ddb90774f9e5750767afd2994e8d0160f9e54ceb0ed4a7e737119c8271d7f4396f557b9593f6a5b07d3b0c9e1dfce77be576b3c31db2de7ef37aab9de56b943bf8f6881ea7d76e5a1bcbb3c31ce6306f1afbc17eb01fec1dbc69b5a84f57d9a707a47da9eca5a7564fcdec3becb0c230cf3017613e72e7a60e5e35eaf5f4bc79f5670b02178dfba3e9ccf572e89099be033553c323c80dec4d6b10eceded9b0dc1deeee00ee9c6f275bbbd3616d9587e9f37e569df81fd6d759f69dc1febeaa96acee626003ea27191003cf3b843da63651fc0a33e4d51a14ce7398fa400702b1b02fbe737a6181fc0c919e80980d6494369951445ebfce801a0af1d0034ee6e7ff9cbdadc5fbdc5bf36f716736fcdde3bc0fdb1dcbe5bf41edbba9f5bf50bebbe7934d04dae03ad33c629addac9375a4fed18adceeeb4a29ee7406b0d0eb40679e837cf666fa035c8d70dc283d69a476bcd0cad28a615b583566f5a5d86d6d389ba950e1a43dd2a0735b9d5d3bc4e9257ade4f5f430ee1c0c7b6944eb89b4b94723d236f768b2eb4929e565b99475af0c8b4438d7b47297e9a5f2976f571fd0b7e5753a7bc5ba7cdcddb5a84f3f4ffba496578cfaf4e4f5dfaedce5df9cecd8ebe9dba56f168445e3fe78de2eb73a7d3dfaae9e4beb39e60bf46c76f568a08fbc061179adc9bca22eaf8e793d595e35e9400f7d57cf86811e0df420225a6b325a5117ad7ec2683d59b46a5f77f568ea9474570f3d625eb9cb2f467dfa31af537ac49c47625d5eba97c95f1ed04bafa787ce40daac9a7bfe92569f3e7d65a0871e77081677b7b1747777c7e62aef7450c9fc3c15f636252ee8573df17857fd9d6ba168836d21d7e2b187719ec202a03fc0bb151458fe721a139a5b4141e551ef5651bcfce5341f686e0585157ffdc2df33feba1c16ca72760bb97b36dac8b3a522b7d6dffab3defab3fce659a2b54a8f56d5bf87bf22cb45d4bd1539bf12e5113d2f7135c1b889290cadfca29a57b17fb4f6605bdd9f6ff7511e914f5fbaec9708ebc129923b9df79569855bd55ee5e467654dc1b34b9fdfba5ebe46b7369ee93fcf5b7d41bc7f7ea978abf057fad2c75bdd2e5f7d38c7a24bded7fd3744e59f9e2ac5eac32ffd3d5af78c8feec3bffee6bff7a4bf67519ef5e96f25f40adfaad80ab13ebdeec558cdb371995f10984c679ec8b567e39a8ffcba5c068120d6d9d9371b62a55bbe413b84dda28e51d26fde0df3e69e9fab0fbf75c64b0f6d753fe458a603e34c79d8a75f305bdd5f79b9c6392c37852c18b72c8716ad392e133bcce4297f8936f76f73f1dfe65eb4a2ad9fb7fe6b31d1b6ce1b7c970dd1bf6f6ed0fe900e339df590c78d5d6ed14b9bf4de30bf79bbb9bf2e8c37c7a8e8374cda2a7b3bf48c6acfa6a92f758cd2e808fa73af73d2cab407136d8e51d714ed7c75af0c5b277648dd971ebd07a7d831adf0993dd61eccb27cc78c129acf7b11e561976ed16ad1ba513efab59873361ddd651090beb4eeb333666d15dab4579ff7ec966f16c45b67b7b6ba517ee9cc74e9f1adfbb3a97934d0a1bba5fed0f2d883fd7aedc15ee49957f8d2e7fdbac8a3d78df2d23bd359e711d1ba6ff9e5301b02fbf51a914311ad3dd85b9ed10a3f3a746c738ea66e94af3eef2d8fbe4eec90a5b589ea2dbfe4191fdab8afc5ac2dfeac2658665145c1b3b5b5b5c19f415c2fe93ab13f22e56d3a47c3ee7a40eb67dbb48519a69c93ba75c9850dc499ee4ec84e77739bd3a7dfb07b3693d67dc7a83967979f737a0096dead5690850867b0022fb60b2564e9220967c04c21658fafb2a74775ece5e9e56a055cbc74c84de04837710abea4b5a787072091d0bbd50a8ef03138881fe84007f88d1efc468f28685c09030c2e72500528b2685510c1800a9491172b780203415840c9728169cd69cd39e7cfa02b3f2580011eef564167f8f86e1584c67b3208dac2e93481d3a9a6860235354d7400061d2041092508016854027e15128890032b627c51831a808053e73b2d09226a6a88389d889859e282f204ca0d4410e8420a6cf0008b12499041160635e6950734518509230861053d808a142bacc0022b586006d604169a28432787949f2b98810b2b8c718616b2703561a50928e83c207bb7827284d7deada0547100cd0144fe798f5c0f9701b10f1ddbf8a391ee9ef7f3f5dafdbc8778ced3de8351b26bda9db3a97134d29bd6e8ce2819bdba10a3e789de7ec3284e455ab58f2edd391be768eafc3a1fba73369aa3910e698d0efd8a1b8a51d2e1e68c92de5b0f46490742d3de7df51762f4ed3d9c53d05f96039bb307f8b3591ce9284e35adfe8f4edecd310afaf35a8305a3dcb3af93d6d06a4d0da7aad603a7ea2908a7daa53b4e551f5eba7b241d316cd15eb597ee7a48cae3560f8ed3c901a7534d4d1035353ffcf0c30f1a7c67e34040402ac0a913c6069a9bb5e69da39e4d0e154478820a3080a20a96d0b36b7c006dbebfcbd87f298f86846bdd3921ce06e2382ceb6a0fef60c39ed3478736ec9587778ee2d41bc1a1b9d5932d4f7ab75ae20cefb2e7377cc329cba7af9f3865f93a0f4ed59aaf1695be3e3713a3221338634c2146151b5c91460d2c26809868e22d5a25eda979cb2f6b3b595eb537316add0ab974f76c42547b34eb16adfbd3ebfc2b7d808610fb8e8d046b087b10638c715fbf28c419a3e851b10837ecf5ecd8cf8debcb1790f064891e30bb3ad41d7d4ac30e586db513183b4ad9b16110ae6bb79bc747d3c7181d724f8f29523a948c4d7b7af4786b5c70017ec5b5281557c86a2c674a1861f74346ed5b0b81ba3bca4b4a295fc7eee618a5a118d8c57e51099dc69bef05529855260caa30c8497ce77cd42488708fa6be2d4ea478e8a4ca1ab4eaa44a7542e5a1d7d7a507063df4a610023d7c5f1e3e2f0f5f97f7b83cf4f7200ede96af507a9182b65e2195c7e52ba402e16c322ed7a33a2d18467f507ef50667c32b862c89c518a316b224ccc07ce935210b3a2cd2144a8f0d29a4d00cf3084b906fbcd1dd3076775b69b8a5bbb7bbbb9763c73034a4224229d367ca1fa05f9f53b4ba542a0339e7a3dd6512601cf76828f3368f7836d29bae15ffbcfaccef9642eb5e292c65ad783892f29729522a4b817308381f4a71c135197b1dfbc535ba09cec431c6c8bc576f0febc7ccd272369a2907a3d157ed87fea628e7e3b905c52e9b4cb1caac09b90bd11fd4b35ea3aa704763cd884a62a268cd5b830e2deee2e253a55e5c1eba8443833d1797cd5d5c7a709941806f7a79e12d2e2f97978bcbc545879622c418e3741d63bcd2100b58951983dc46a087fde5617b79d85d1e7a737936a7def26ca4435a7b62d00fdeeb37fd7dcbd748652501c691f4f2d286b65e23957fd7977f70d9884133063df61ea4c239f7e68430385d1263ef8ddbbf37cb613a4d61d0b21cf6ab2100633784668c76fd7a536c8c39c411471c3fb593437dd7d420df3eb10699a35035255a6b48b406095d3ec25c64d1ea5fb58cd2d345ad392dafa7f7e9b1659492fac40855f2b5170ddb62dc6d7b376aab69f17218cc72a4654d8fedd1e5a4d22f890d01bd37c93276364343d03687bd35d3c97d01b2b73ff66dead87908de76cc34a1b1bb87ce0cd9b320d8b709d79cf389fc89e88f0f2ff31ae461011ed23cf4ede1b4aa8cf47af2e18a934d061ecd4b3987aa39e4f0f5f4d8e93a85acb9d90fece3f93585b61e217465f8e713d37cf0e1e296428a8b89a13ffb240d766a7c3b37bedd08b6d1dc1861907ce8a1f2c03cd88429d9273ca8c96182a971d44727d18ad268adf95af341822899232aa2d8157af20ddd19df373fdda1da390bc2ad7ebaebf13d638cfb649f3ca4500a0c4b27ecb74f2fa0fb45e7221531c6e8457ca94f6f3f255c2ea54de79c17e7bc7cf4eb82b4a21e3a8f6a7abe78ab739d733da67697dbeafcddaee776178a9cf3b15e4dbfbcbd2d791c2964ae27e8bcc400d35c97057a02066d7df90de7053a2feb2592d1daee6e53e9844c1a682d047fa4d0f988113a878ad1097c195b529ff731ca88c47ba8bdd341d5fb5af0def437d75fcff5be7dc9f6ceef31bd84a793e684408f3da61cc8d3b559cd121ae6d964d4dbbf9e5b16410a2b0ade39548cd0747ac3044d0fa90be248bacbe88fd432b3d09e57d20a915aa840d1e4171fbd927ee0f14d2517e7e3f9baa1ad33d9af92cb3f6fd831d21ea9654a2d0f47569e0aaed63408b15d6a6adfadb79737e66da6da12e78478df146639a79305bb61a075f7947347c7de2c88960b3707ab936ff89dc311d26c88e8912201bfe1f71733084a4d45bd6773d55c7273327047d37e6dced168ae59ae9540ff2ef2d0fd25eda999353fac9a0ca15c36ecaefdb5c319285ac78e1bfc6b8768872eb7992622030101013d0be1c3cfec4bde17a2e332af8bfea070aec8c047875b4541ede7d683faf768dea669367ed220eaa14fcd071aa685b6208c7a7e7a36d67c594e531f84a0add720ffa61093513b310da2500f19e5b84c6e17b823be5deb397d3b34610fa3f668dee9a1bba9030d3a9c5afba492b39c48518c7a3ebbd0d62bea1f13f815f5cf1d9ca753642805f4a841934e3b3c3d843d58c5beb7296f4bde6adae904d9663a64d4290a0dbee7909e18f57e342020202570ae80ee92c77b9b81a2c1f66bc74c131a74f7ed32cb69efec012a98050123cd425634cd74ea18690ff6b0219d118222c1fe3514d2cabf4722d11f77c5ed9342be817c0b20f3ea2737a35367a36d4e08e6edd3d9d44097b28484bc7ddd9007ec8f1e7705f5a6d0a43f226a9e8d9207ec908db23f9e9f342da7abe667a495bfba2c5bf4d0b66f6dd0a1da953c9be968a2ec10e77e4cf793172fff9c8fca5f37ca3f27e2d9ac9447f37c4e4d5b29d8b6bf52fc7492f2d339e19b09ee964518dd9c8ff68b56fa819cb87d13795910fea175a8ee6f482b0a601644af6391733e600e7470b2a43deeca379c11822271571e52b856443cbd40e770b5582d168bcd620b187cf901bc5bbd608cbf468e53b527424e415f9f70c309621464ba44befc79764210b7bfb7e13cd91f1007a8719ad81f903e955c3e827c4f3e21a04fd781d355ffb60a9fb3f7f3a8fae7c3df14c8fe6055ef46e19a98938bf16eeee566394b368eff617fd21920dc4fd7713fe9cc0a34ff3a552fb7ea2fe756a533f4567d6f3efd90c6fdc1aadeede2723d42b3975d12dc8551f22577619494ee17f3e72e48ebfc2b44ebd4d69b0e1161dc1ff0cabf1b463d7f9056270f7d37da0b21ac3f1f7d3476f70d9d6ab1387a21e59ff63d50efdc39759d1e8d13b2965f939a34ad07086e9887421b06b3497743a2d520ff823c9b231ecdf3d9e582d8cbdc08341ecbfbabeb123aa247894f124e16885321e8cf376883360a0c51b83fe61402fef49bb74d3bb0658051cfe55624039cbabcfdf9558453d29f5fbcb18b700af3e73dd363cf8725d1a4bbaf41b68c40db0fc229e8cf7de0d4a6dc3f6f00a72e7ffe004e5dedb4605ff76bdc1f93721a8c5a8f02840c70aa06498253d56d0942c53ff701084e3def00a748fffc084eb95594a07fdec3368be382bce9abeb12f747a4707f2c7971c4717a364146b44ed1293bfdebc568dde957100f05b182bca49306f9b752dee9a0ca16039a9d0562795778b53fe96ec1a849570bc65cfa9cb4772ead9a97dc710ac6abf3d0140519f9d25af393e4d372180bc6e7a3a51eec4b90d69a972eaad8ca75f825caa395d6455a0c691e6acd4b251812e5d9a5169d8d5d57cb6de5ee2e85d26f98477ac86fb88b165db4c82276c9e76a010393451673ce2cb4e0d4246d16736ab1138b2cb4d82e3fbbb380df353fb7fd921aa7e2b5d8cbb691c74de497e6033f7af42ab275d329461ff8d335c8284dd3a6e6fc8637d3992ea29051d1e768734c13a9746d34da32cd1fcd434d4dbab5c13a5fcaea9af4e7daf4a945165a64b13fab0a35cc0faecb613030f0061898090343f29a1d12e3248ff176203b24e622c5d0aafa18276d93449a93e4d33bcb99372f66bbc15d967303adaa87f11b2639901de29ee49be5906048d36b26903785539334616080ccd2ac3ed41e7ed2baed454e252030d34f3a64ae912ab615908a5785a5d86ffaa830aae99bc2a8183af97aebf0f61a721ede333b72d04a7218779c8af1aaf1a03373075de5a032ee9cb3dc399bf6222d43abcbf2edece845abe6c3379dd36336c7a81ae3ed3ebd44b1597213ada7afee18351d669b81a291a84f3f4c895a9e6d3350348dd6d3a4190ce521c15824b774d0cc6b8866fd18e521398caf732604ccc6501f2601c15a8e72e4a03f3d70ae10f90d5fbc998ef4756cdb7756486e8e5171cb1cd3c9dc44eb84a175d289f96ef2aa3d20d3c418797ce4ec874c871f1b8936c8284c933527d4af872890fd792af8b3aa52c31021c648f74b8c74d58891ae9718e9a61123dd2e31d23dc31a45af168db4ee19a590bbcb49ae457f36998de6a512add897e82b95a8639a52a9e4585672e86e9b81a285680ff6a19163d449db0c14eda23eef433e724849be990ecf74f821ca33a275bfe42177ee9c44a275a3f0581e72e99c0911924c4b6e519e394b32c6c5b28cfe9870aeb8a647ef2df270d477cfadf6983efa9c8c9a1d674462faa63cd325ed33ce3883a492324a59f2dd92b7972867435cbb45bf5c56a255f525df2d3ab1677496136f5e692391e4ee9e21618c67702ae67494b2a14de6b50713456fd974adcdfd68b3fc7a7ed16ab23cdbb447f37c54453601cd45ce598eb639a699ae613ffdf9cd1b6d96bbec8790639ae8d9e698c6f26b6ed5f4d3e3361f36b790bb4c27e4336ed597ecc71ccd6d13d3dc9f938fde1bbd79b2b7333c6e5515abea63eccd31ca716b625caaf62c2ff36ef5022b7f3dacab40291e159eb20fe94a6154bb0ba21b85510d7dc78c12d02bd3ce7ed85f778e56e7a1c997cfcd31cd452bfccbb3cdc2feba1c86b619289aa33dd83bc7420edf511ecc7928d4df9487dd09e1e2e59c298ff4fe970dd12f1ddca73d59e2ab9893fe603857b484dda7ef7e0e88de2af64b963c24d837e5817432eaedb33f91e5fdddca09a0bf1a73d2300be2bda5daad6196b3bb51e656f3685ea475391be2925b7bcb6ee9ed9de5f4cd8b9b555552fa6639f2729c6af70ddd43df289cea1cb85bd30bbe4b628e6da637855ef36ce6e6c0a66e65d19e3f775d4ebe1d6ef4e645b839b13fa043ef9bd7aa6fb8b9553f511a5c70c6af0bdeada4688157756f35492412892469e3a9d1a7ff44918844b27c45a6cd8a2d33918b30376d557b95139289e49064f29b671239b6f18cdcfd9a34930973b8434c8e893092dfb0b6d59fe7c98618b9c96f78c78c125a9dd88a72d459ca2149988f4c94e4eb0ec2d2e9a1c79128dbb06d6e5826c4f5215aa3fb0cfb94b15d46e105555e00ef562e50e3af51b6f154e9d07f5a9248de2d1241d2acf02110d7a21592b6aaa99c582e22892c1791fce6919674b97b8b8489444e22b9652d695d9623a24bde677ec3d8567f9ce4372c721f7e4b444bee9cc8395189443317b913398b5ca3753e8b7c44abf62ca2cbfefcdae2fed8d0e626ad2f9f90b29262454a500bbe3c9077ab165c79efbd6ceeee67cb5d6ef8adcb7e50fd845f1dcd9cef73becff93ee7fbdc8779daddbb4bca4c7af616023073e72e8b5996cdccb34ceaf0485a37948564c859fa484a4d935996d1aabdccdc722df351e6a22da315a3551592270d4e38572dc0a20551dcf800bc5bb1208c2b6f80772b1648799612ba6db7aaca74f6ebd20ab7aa3136e774b86e3f6e3c4b9cfc74f72e0be2ed92f7cd5d2a7ce775296c6cb31ce92ec318e54c8c722311e68ffaa36167cea83f1bcd79d27aca7cba68738e66d2ea3f2d1f9d7e4a76349256cd726d738e664246391f6da22ddbae4b47529e90e575529ee52c88f54c88eb9bd67578fa69d5c571853586412ba9a087d3b989539a8f7ca7bc5b2ad615b7e7bd591eda76d4a36dbab6993cb4adcbcdf2b8bd5c20bf3258b162c58a152bbb56fa81f120eca1a6c607ce82b05f30be1eee3ca05012839ac68351ec2f0b0de1d47331460d615a0dddea65216e4f4e7d75ceb9f7424798fcb60af7f9ee7b6f724a8a1876796f08fd5ebfa02140481f75af8acbf588509a18a569d04b5268ebeedd2a0aa0e7afa7e720b526880f3d9c6c186213737ec919a5c8391fefbd1ca85c2006126cd0461950a84215399ca2706b98e1e505523c49630c577080c210a230dfbbd514569071f16c868d02c80f3ee868da2ac1ec8eee1c0af6c2c6b4f95e8cf4075545f4980d01bf695873810665c7e60269350515d7a3f68e6683e6ed0f63943bf71ecf89e800db8ca4f0c029b7b2c1977fda0b033aeded98692f3c866b724a736f1f3be77c71dc7a51a4190acfe1c4486b68d59feba9078a87e72d966acfa1578d67a986d5f9a0a645bf7ad39c937f3e37f73c9fcde2f0a3354d5c4e7bedaf9fd7c9a8f7cfe1d3364a84103e6747cd1acdb9d349d3ea5c97f482eb707d5627a18c913281dfd427fe3af4e279d520adccfe2c440ecfc39136ad1aa4f5041d8df3f7f8bd5ea681f3c9a8754264f6f474382724669bef9c4df7a63d1ae739da14616469922c2d34ba44589661a26b14d22cd22c499868ea1c30e6e9e07db7edcb6032a71d229882eff175c63539b5836de4c4c6989cdaf16f93f9eb616f3bbd8ed8d8db623ec7cbad1d0dcc1efa36995910127b9be9af89bd0de62f0b7b5be9af10f636d25f17f636ed2f0c7bdbe82f91e82f11f6b6ecaf11f636ec2f0d7bdbf517097b5be82f184c6e0ee76dd65f30d8dbe65f26ec6df2af1cd8db62ffa5037b1bfceb84bdedfd2583bd8dffe2a123268709a644d246a20cbb4296cccc0e11743ea0bb87286898e53c274cdf176e20517142b43cb7058dc5d90663696d30feb516197c45c03faf0878f7dcf6ae004c2bcdd74972da1ab4da5d1a0d67e35c777142f61495d0d8683438274515052fa1845b6b09f2a45fd6c1e5092f371370e21a5af4c5712e15c63faf1d06180ef5bc3618ef60fe798579e7243784e47e3cd7f2cee7b369779a165fb5875534bbe67c3c8f9ba9b5381fcf7b9bbcc9ee8e910ab62949692d62c718bbb770aacee6d263f4191fadc01a50c7710dedb9fbda5c2043b854ae9cb850c1a20a0c04a2a2852a54d680cb5538557b8b159caa920a20b497f7ded26e689547171e5d9e8dc3792e0469f57ec9d6f90c860e2a3aa84c609faf168d9df2e8c2e59fd74bcbff40ad381f8f47978c5485b422ad4857485e486eb04d8e2aa42c51e8a0421a838485c485d485a406498b0e2a9caa3cbae8a8c2a94aaaa2430a1d544c5295ded25b7874e18186b3b1fc398f2e4ec84e97d1d949559e8d4579a0e1689ed31b94d0e21fad3cd8b012290f34fc2355f91d95e4c53fafbde57974d942ab3aa8fcf3251bc7f585f3f17874f9079d47174ef53fe791861bfffc062a3750c1360df49c871a3448e31fe98627ff62bef2f0f2ef86a07f5c64f082af3748f927450656bede20c53f1e5ffe41d24a0b164ed5eb0bd2154eddf08fc75752967fbb69f9423a21eccf296357b896ca067d51f268b868792fc8a3418578a0adefcf326b714d4ee938672d64d4baf3f1689decf5f9f5de53f1cff25ee17a7ebded87cc9986468b16146a4712587f963539d361d30533995faf32effc65ecd7c8fd6612708fa6ba153fe71fae1fae67cf69a273fda3cef9d0b99ee9cb74de0feb7cacaff3e11c663a9088271a13b3339d8e994ee427b499e9a06abff3b716a374d82d8baaea2b1ce558bf5ead6c02ebeed1bccdbd15c2f65ac7a299e958969c2111ef5f23f72efbc1391fce07afcce2723d2a898b4b5d0ec55e3bcb3bef28b8947b38168e8310fa0d47a065f243f4769d7d19631084f065f0871885f660545c6689cdf7dec42c0821b4b050777787b02bc61863bc302c5bbfa4a0d5ab4b033510b4025af1b43c2d5fbe7015aeb2589c17e74566399b45aba7af31c8ea784d9196e9fcb0e5f90d57f168c4649603bd19b524c7a85f9fed37ec54efe8d585093f13fe97e9e8c4156ccec7ba275a9d18e47cb02f75e7630dd71727c4d528e5daf2ecd735c6b3b3180ec5ce2bf67a7d89521e0d073d9aa8865663d073975fafd797277179a4335ced570c16adba2fab249e8d96436327a2034784e1ef245a4f4dab565d1a96ef867cb39c905b9665410b62385738dfcdf9a5b9ad9e7ef7bd4dba95cf6f7837e9eef90d275104deac4081f3d1bb99c0fd30811bc24afcb00fa9f8f650956fb7a4582a533a2809e7a3b12d34e84ecb17ac09ebad9a3f4f15dffc725d238701b1bf80d5d91f01b19051988443bea1c1bdb8ac4f9579048d7b2e2e90512db5cc2cb48d9be56c36c4737e4cad26b4d5b46543abf389708f66ca885910c2ee8edddd31084a44c51310524a1eceed1367eed8b163afe320524a1fde3133333b8805792b1e18e5de63ae9199ce5ea3f9d8e76467824c7999979779e5bce6ac26b90b29c7759e96150af5f0ceaf0bc3b20ce34dd4ce47080b8d424a704030ca31bb2cbc3c7d209c9a735ab0a0551fdef9e5a6cba0f3716540ec638c728e6118c6cccc0c99c2d1f32cfb39e15cc19e65394c2d28dad28a8946da88a495b6344718863114add6bc7315fbc7fb4455f5aea39c9995852ef8325e91e86755fc6cd4b85c8f3abd3cf46b179b9a763ab9d704598a2541ab6ec55e7975f36cdc53807b70b1dc30ce5af168dacbb30939745e3d1b25fce5d9600ee7cab93ea0adf268a0ef9667b3563c1ae85bde56e937b42d551e7a5c8356ddea9d13b27e124303e36114b6fda9a56a1bff0ea765cb9ae168a08b619e618d38a0a0dca1368a82763dc1f980d6c65cb81fd0af7542907842dc10cb9dbfec078b085a7561e8ec13c1384b368eb7281603edb9733eeabc20b666389b9b15c3c33dc3c35de3e1c6f190a13cd4f268a087f8cb43c7b6a0877e33e1e51aad3e725176c202f32d2e5a7975a5f266e195f3011d4382f69cd72baf1eba923935cd8296579f5a24adda8b53cbc3a63ebdc01fa882d995cbf5a851ca94710ea117edc6817836d17778e7729b9b7b346ad0a24b7727e4376783e3c40862850238293cf44a73e37c40bf614b091a0fb772ab18747372ab9b062871c236252d6e25c5196e0c87c571715d9c1a4e8b73aa9e78f4e0e1c4234a79a703c32a38873d9b27164e138e73c57a9d57b0576d3a6fd66e37734fec89f3019d09e70302457140a42dcf2654495f1e7aa92175a9242e0fd9a5ab7096e98c04a24baf4074499d5cc1ee447adc75a615881ef2107512f24855384cab02b040961e72583020ecc994876e4979c682b0280fad78e8eb355a11dd39af793618841edab60b035a87eb4b44a4376cadcf8dce32684034ad93085ab5c78038f445db67ed35270453c2f9800e9de46c30152c0d7488cd00f582871573e2a1579987be1906e47c40b794a0618c825eb1f5b8cb4ec2029dd73120b8eeb25eaf18d0439fe966c780a0634fa06356b02dd8c6041d8b12e52116291e06557968c5432c1e62521e7af302c1a93a89e054c51ec0a999872e8c87137a09887f58c5f0d2c2e57ad4d26a2db7ec8515c87899bf1acb020c65414acc1b0b164356d8a08d12b28256b115a68553cedb312e9c9aee9ef9afc5b015a736f7d8154e89bc1d0b03dbf2ed6cb02b4cd35eb119a513e9ecf04bb115a3bcd0da9fc8615fb115b6aad8aa0bade7053177edd8b1452b51793697d79216252b4a4a567cd752150fb90a272e9d9180e521af80e521eae48a7527a148ab02b47cf4a515b0fcf28b3ab9dca22a9ca55501583e4aace7b714038e775d8881e5799b8974c9c6f1cf7f7ceab5293aa8f80bae834fb4e49d94f0d7c318e8a3ef8a07553e521ffe25f29b40de4a57f8ab31de3030fc1531de4a63a0e12f89f1665ae26326fd9a186f2119fc35656cf8789d3543a225a38f2e37ec4af6bbb92ec8c0c1efd8d18616f2cb2bb6da206c855dc1b08cf1150be3dbdfa625cbb7efc082c60446afb1cb041c8ee54a9e0db6c2a9f3dbf48df5d2aafdb585b60b5b79f9e8fd05cb7704d2f24458b4b9d01e8cea8f34b486b66f79c86bcd1321012d4e8b57ab9d79a87142302e9c8ff6f602381bcc094bd3deb4ca7cc556a7a515b3c2b757d22f4da435f4d5fa5a83ad9c8f764b091a8a51eda1adb93c9ae8d6e61e4ded2e1fd9a0bdf4529013b2df3e6a6bf9e8d8ead9389cee2d8f26bacbe2d68351ed73f5ed33734ac7569cea6fc7ae70caad6a00846fc7bc606eb4635938e55636c8f25db131be312c5cbe5f0db8f88a75f9ce806a50c6574c8d6f9c8a69f9f629e30671aaf690c2a98aa2c2a99929a91d5f570ae8ed284eb1b7f3505a3d157ca5298286c07ed934c9c4a899e8ae8b2a563c9c89f4872e7030e5dbd97970caad62f0e4d981a800dbc45861efa1460ff2ab2f4e0df2046a9912c513d10a08080808e70ab78ac1135f5d96679c8ad241a3caa613062b9b4e18b0487114523ccb3cbf0e1a54306afb2a821ce7511e4cc37ec3a727b4755c1e83a1c6ddd434d3890784703601058a2c5f5cd1832514210e1c787a08af3ca1e221ad73427ee8cc30e30554f8c2c60fa0a44187a3a66cf1d079e0d4d6ecf06e35258a294190892b4b74b9a282386a9c76f21e281ea419a643ea3d1865a561d19e9a8710c2e90e5e5c1ce53d5c73a804846b78780507647868519781e6de4f26cdbd078a0707ad294a58ef110511a2c8c157af8953505181030d5c9ac08427c6a041ccb36b39927099010a52a020021ba4800626704ccf6b2842501c4c7c9104a03560948065214217be0c010657384309d382129270fa6bd4b1a5b7c309e194c20b66808083248864a0c1772b1c6491a901074070160e72e0840e37e1bc0207321859a18ddead701064d12a6d4061cad6d0b0338ca468d817ced0b42b88b8683ab6c8daa822041b549102091af66e55458a11b4ebddaa0a136668d917086f5086874fe040e8ee8484bec74ed120a427910fb41217445c683058bad0648430ca8236ab804333bd5bdd00064137684214042d634354052de60b2325b41cef565354e1d747514cc1c59fe4e6ceb07f9a82080f213cfd0ddfb07cb79ae2077fbd5b4d9145088acb7965666606b0718dd87372c0e1061e333b644e3a627298604a246d24cab02b644d191b3e5ee7c3c5f1553cbf61f7a873343fab626632dc7bcf9756e7f03de7edcfbdf79ef3f7de838ec2e1ced9d739e79c5bf75270396733a77c34ce7f52d0f9ea5cce0961afee72fbced79d0fe7aadd062c95ed90bbe1e6ba3da62075ec91c2c7b11d13f890c6651fb0c309dd41778c2a81a1b143e89c63770d61437e4940c1af37840e9274ba4c61144307218410428f5a19ca48c51a638cf30a97a3d0a75fbddece4d27a338b4d2d8314e3d77ee4a6068f0d7d979d621a31cdc2de3d2a183feec93112906cb6182295d215126351ed494683d392a07ad35989218d2489449fae3c64b08e4f0245f2bcf268715d35a592beedc19245a5552688df94a79366b6544eb2994518cd60e58b1b256d64a94d2c4561bdd9c8a514c878a8e7d744213614c62622f0a1cd76cec04cde5a15f720d5a9d13703837dc5c9e1d724f7399417b99a2add7f6d25e9a4b73797845416a6e770a89de6e39211616c593266a70a63340fb93e94d384e28f413daa23340cfef1f7ee89765d1fe86578431a49ca894ea562a843dcc39a6d08c6004000009531500304018128a4524424d17750f1480118aa64c62529887b32887510a21638c01001042000004064468060d005ff9116c4f27b09d0ad933b01a8b40c70a8144530f025bc3986ac94b0c63b5e66d540a02eaee8d39f174c33d135858070a81b591accc5f05fae8dd37dc0e6a0a5b6839d1bf786c4678dac37cc4e002853804a541e38152c6103fcb376cce6ba0f4f59c4c6f03c062dcceb0207af9a3c81cadbbd2a1087743fa36441e9f4b5fa0e0f2960fc8b48b6ebd1109edfce22a2e077b16f221684f287e1006106b2327a54db4f062beab16012e9f1b5f201cd35bb49101c0da521ac65add23b6be00bf106a1e4f9cb377474a614d7ad147f469b54d38a06ca71be6314e51011b9b334a9cccc2b2851e555e0f81283b1e89a20f4b8541de1449323e668d0396829ce2dbd10056927aae828993f4a7125e44d453fddd0f91954b52501413648e358f7176f24dbcb66df0da146c6aadf484eb21afe02c2c20b587aa9081faf1bb2fcb0cd743374e9b0ed14039e838407f5e8503b28a4c87efe8892a5db54393644afa077d3e5c39218af2a76e2ed486683f0115a2cbdb12cce0dc8b85a415df4b1f84f4e620a8bee947d9682baf8818407a1d5a7d2ac3cfd0395d6648666731789861549d4e51c86c3929888c0363798341e586e491f45050773eddaae96176106675845bdadecf317d82cac92716dea34cc9cfe5f6ba31747d5877de65bd5ccc899b2da73b6862d72a5544410464c52e4d4f5d7506b85d5af7b4bdd88ee7e18f455f11b7c0e722e7334bcfc85bc5905efe2ae2a0bd18ae49f19f1b613a3b445c77b2776d2dd2e0a5799d816d5a92111029b5493ba14e3719fdcd737fe8ffa672ae8fabb024917a330234be65bfb3ac755f5f489a77a7abf405f0fdc6394b96c033482d6b08731335b33480d0976cda8c8ae5ab8149cc97a07d5199656fa2efe03462e6e44a02053713937dc2d21602051693dbbc23e3d0e04bfeafdd498b192fe5e0d35a0508ff2618a7070e7a21feda028df13c29d865f9d83fe9ca90221f8a01605e2a78b6a2a6ea9d92c8e852137575883f0bb9558127ded1cda67e239da08c1e4fb83d56b40c165bb8618991108bfce6d202c20102582c43cc8b232011c70441d06003c9873bc3f41bc95bc955cb798643960003d65544c8593c3cf29c717b61297cdc8d1fda4b508f84d5f4f1822fd797e816c188c43c0015d6210a88bcbdfda547282d3a06c2040936d859929a4510ec128e11039b590f4807ce832c6c231cc3b924f6f612c43e8d894e1e911a7e90b839c55a39f9564c4701da5e1be5f890d16e4a35d85be8516bd6f083c1f5e503f91137c424c3ba7d6124ef4b2fd2efc473506239557fee747033b893c66f442b7d920a2c6ab774b107443ee034c839506b3622fffa2983ad904bba6bfcce9613b941ff1437fa6699adb0ff0b70fc52c6e56584896ddf7030c3288ad026266e0084d4b71838a8b21278384c4fdd87dd32d30d42956556635f94207c2eaaeacf7f186c987e223e29f41dc7a562b6b1e0d392854d5f7967d8f750e72b0c0ed89a697a8562edbde903a96bac37b4dd10e17e76c599cb9a43bae333065df0f43b5221a418b1473e17aa3810147890b993a2b078f3d34156aa7587baae867e29ac254cd52a2429bdb2688b47014eccf0c6fbb86328b08933136d76e5c69ca3a23455e554304b8549aa4d508d846aa55429a58a49aa4d526d864a645429a75a39d54c5261924a13542013120ea688f6d093b21e0c02454611febdb8dcf7682cc76730fcd3af8776d387ae78bf3ab15985b14c894c7a9d7e682d89b2d01367a68f5ee5b25152ab8e15bb7d6e634862bca552f60ce655adf3fa634a8d1679c7a38920e33da2bbeef2b702013f74c87754e620e895c67a0b7165bb464b675290ae3bc73077c20f77373b0e34e43e5202b776bef3546233d4bd6cc1ea42e65a5a23b481dab7dd21e4204d29d4c6ae07751207d529d96aa6f834cc58b0d3d7ba5fbe090b3b67857d3ec20af293ef257d20da4a4e2040c9cbf17deabe4fd4723889c34348f47641a3169e680df13a10534910babf0b5bb2645bb476c029522798def4337083fddf1ecf990d0692c7251e0ca3c8afa0a24f441ffe9b84d2a6c8cf3f2b663fd6a77b1d7601847bcca75f4a2bbfe5fe3a04607ea46deb93cd93f6c67415333f8ca5bd43c243f2f5a7306f09afc2228c8eae48b35b27c5dba4ed94b9427b4aafacbd4d359bfe43b5a2906278c435ad18700fa9aece4262df92efd8028134a61bdc95d236ceb876bdbe05b6c2c75e4b5c4858020f80d58b69e78ce33d81da3ac4b74f1e55dc3ac228f30c18a2089f5d8f9cfe62f81ffc357dd50e2e07e14fdca8f1904639555280ae1295caa826c4b0a2153f3a558b003b21b7634f72f453065f88706cd2a0b8316b7341a81893084b057e6802e42c009b86a05e8a07b420bdd6269c7abe11c73f60494645bfd7545b3c613ac3b99342741826bcd432589942e07f6bdc69eb222a571940c2a112cc981f51fe178337e2217c72c1f0ee88086e5304810009507c940d753e6efad7cfceec74b5d44a48710afdb37a42244e935b9f953c9a6330e8edc8a55d3d7cbe21bb5acee8cd380e7a5a40232de79ed7b8a20ed2ce8aa5ba1423ecb0d3c803a09d2be454867060617918af775e530cc3e23aaab485167cd467d37260af770250b9253aa4a595ef25fef6e2d9824b7fdd761a4a6f6e156fa9911d68864b54a0cff06ade8933eec39768e4863a1d3efec68b62edb9b31af0e490af838131e87f3a89cac596809834d55e9dc07a9a15fba5d383c9fd572fc7f632b5766018f9453e735986e6ab71fec86101469cce55af3cded7e37b712c41b087e4d448d6215b80270a6188984465c418534de20f661a0b5d867ebf31b0a41bad68ad31369132158c0cfb3968ab2a2694adb8a85012902307e93b8c2ad89564b863b527b802eeb0e75e95905bdb1404c8e3737e72e111a2707d347e10a43a99a8e1fb175598ab75358b6dd16f3b01c2e6924cb9f0ee3e6d4c13c63680c7f0a67b885016fc23aac541648ff9a85293370db3a0dce1067617fb7afbd03c216ed4661264daafbfe2cfed2a7f44457b4b7d296add1ceeaddef46441280836f1a024e6853a56df5dc1ae1b80316ab9d701aff0dd7569a24e5ba4d2e01f892f4a7925949239df3a53a32097fd98132283db01f2e07412d74306a536997a177f5c2416914690faf2f782337a328f02a558897b0005fd1612ff3b65001eaabebb6e4a61f99e4acf57b38867cf59159cc3ceb13955730a2a761f8a14946753dafb3ac8e7a179272e887550cd6bec7d5c11f36007b63ca6dec405b10ee680036ffedfdee212f10776e001ee0005db9c99e74fa75330ac6395be58625fed69f21820723f726176adaa62236513f7b64ee19c2134a7bc2f995205a4d29333178148695416da4d64de0ace4db85c5c73ddc2e25895cb4d92163d8fd263add83ba797a913833090d0857b1f13609d2dd227b1cbe5e3d67a5e3b834d62744bfad6a19c5155ef832e9955a770e316b3ec39e3f55b4c0c58cdc84b380e2e3785b14ae7cf866e16679ad03275ae8057badc2ee1c5e7ae7803e74790f5af624098653b6687066bef2493156a7becf732421895a47411085bfd39e6cd5a801e4dc141dc28adfca8f01557926e7633893b989056df0228ee82dd5290506f9ca066ccb4d9e25f3768b2ea59c7520c9d2d27dfc0fb8f4a76c39648ca09a226e85cb24552e525aa86ec77e5c81615d0f023bb85d76657262925100c40ceb593ee6edb907877a2d3e1417b7db0bb82e4551472a0158ca7bbbef13bba7210b2e933bdc8aa5059c13792eef0c14614fcbf13b4dd4889ac09bae75da32e2fb2252b05dfab9c8692c60d53803c15a2012495b42d9c90d65ecc17c54963025367714d858b4034b48de3961eee4fe8bc4503e074046393315f3aa2e26c3a2334d3cc8ce7702ae27c752a340f39a1b00b121ebffdcb0f59646d50cb353c3e172176a0cbae44886ccab9bc532ad51f77ce6c11f5c0ce312bfccfcd3c25a6f8ee397b0866dae8799b67d8d967abcd37ebe6032192d3263b12cd9275b5246c754e761d97928a0c8e144bdd0b697f600dfb210850168ca63cd7f641d43eb9c123527d6801f7f68f502fe89efae08c5cae05b9a6da1db2b68b098efa4dfae779e25f313f5d09b628874252665506b46a459ebc482982ebd67b06f82d14e027ddedd67ecdf8beea3823b29bf9d5fb81c22d1655bfabecef7963b5fc9c33363b90672aaebb8b3b3cdf129d5b8423b5c31b3980aadb23c3db3545902f8b9d25f41c2492a02517a4ed6d60f6a552d280b5fa9762a557fa9ea4e6a00ea80e5cf6e7d667013b6e42ec27b3396ff6d6e4e7ddc9fd59d7435cd9e7670a3ac4d5602e49312d92563ab03b231123958b61f54deb206ca5ee689aa9bdacf676f4958ec5641f1ef9eefd38e7fa8d2f864d54e4df8c3c1972d8dfadd1a2b2241026afee4db62523c02445a61bb72e50d4f702eaf0d9753a7b4d999bb03e79c61f38f182fbcd4d21d6e51368130438549a1242acc1f950445a3a5b618daadf2cfef20c101054d3b137979632483a2878158bce5aee07d1dca4a0c131398bad83b6f2dc1e307ea3f11869bb86ab3dd7b9270f570978cfa6956c5ebf06567762fcea9d14ee6d5f84d0050ffb555d1c0f46be44d7c20807f6f0ff872f904ed7a8f272ba42702bf6ffe0699814d219d8e6785716c26187b15f483f051dea488534f2deb809a6c8bd399cc1c0b21520c0a66f0e5e1ac9e664fa21aa4541576546f46fd87d260d8e5af1b7ff06c3a545b48045601701d24bd5581ed06d703c6ca9a6ec3e7070100d8c9fe5585a26656598420c2342e0d9dfde2500a548bce0b92bcdc2642afd8f47ca190f8519ba749c44d0d8c946b99dabc0e10311b7a03a2229ae8a1faab9af3c5c682ca45af7fe2704b7ff124f8be06aecf61a88574f2a08a624c2fb9562c932e52df62fe6ba05366b53e934c833e438fdcafb9ad3ba96caf53c43085265279ae71ab2262a233de2905866905ed10eac38b4f2a24a727579d889a12753ef8b004db33a5e0b57aac0d0c4f1990b4d98ddb3aba021c183eadeddebe33ed4acd95d2754cb00522e9c665e546d226ea2d71467bd41ae0ca4b63dae81cc592eac1a522c27ee1346c6a07026244b19c1d47732ee0ee70bb016dedba11f99b0ab9fd95df4fbe8548a92579a183b9a5523950e239d4a1fb6a356e1db741f314344f6c1a059eee865ecc4c3ebd142a911cad24373be3db40a2426dabd203e36089164a060b3a3ca205d74976a93acac6ef13c8c27b45fb0addc2069690a0dc491544aafc57636c01f3e304db3e03e65d39a59067eb32505bab1930173ff2ec6322b26e14167c066b6f6737d71403a68361f7c5bbf66a408522427d780e554ff83b0d9dc397ca5fe6a0321780baf953f11b301c2e99990843a43f9a1c9a306013ada5c85d0bce338a8992ae096cd1372110c5d8fc5ae6e28cf99cdfd438e3072b7f0abd11e231cc02dafb2c9e4c7c444ae1a6b5c261b40762bc0308f9ef915ca15f296633d659d7f38e36f88b7a132ca8e040c71028831350f7fa28ed7938b541d87e2d31d6fa2439b8481ee1d0286806a5e7be4c45d19cf3b406b68d8ebfc93159024deb509c839127c0e20b0c4011ef86682de15dceeb2f982738ea37a0e858da23e3a0de9b44ea77ed4e94fbb4f9b0cf5632c7134cbe09201b20e9da02750b67a90eb360fe007ecdca17187e6ed7e595e043a7d758851dc2b7505e925360e3ab105179be05213177003bad946c44ad915da6eb9b6ce3a6439c6b776203c878a0aebe9e36bbfddf38de304a24677a7010c848b76f8010d7b1047d6fc80ce4b3333babde7a4fbac6ddb708d46e29c1fc7495386ddf61325d4a4e4e7376595704c40af12d2f22368173f8187934b18ae971b4102b412cbfa63947dc1ed8d984489784aa44bb29b9c9268fffce105b7a2c9d3ce7a05f5179cc227ee1fc50916e5fe32c27b8a0393620134b737ace699cfb29a234476dc9c19f006160ceda0cfb5c8e276a24d35271cf077f2ac1be4fb58acee813d50bc23dcd096b5db8741f6614ac2b56d60b31c966428bdd40424f694774a8a803cc0f8142dd18634207e9abb8dc18a80f407cb0b743028200fa47766ebcaa59c463ae2a5adec0421c4b08cb589ab9b8910a27f0fe12a0c1e2e2008fde3c0bf325b6e554dd8d6a212dfba83fb2389654550049075660e82b9897f533d9acb4443f1b461a797748816b7d96effe3d54af8eb59cea658d76b2cce92232c5e910a6a108aa35fb5e205a462f95a7ea0a863b94bcfba80d7748d715c714d4a9d791a120f8f14c540f571127d04a95b85c3c0fd4b32d1dca0ede44a7b942a8d4c1d6f6aebd90db638fda21d3b053e064f6a89a4b106347471bb2548338ee8951c2ecd5b9a35e826540e444f3caae7e36f20bc95345c0fe7025120481c3da21f2b5cbacb0aa2fa2f6ada1b772f4cf6c5dca59f069da8b1900d986a363ea613dfae065ca6821c8e7cf15321c322aeae4c5358e058ffe78bc4be88fb639b08d8d3249d924c4811a7e1a25897e6d0b58e7ec1caec7f942ece9f304525ee87a46eab34c4a620e88c34e8ac13f32807efec6a63de8c7216d9cb2d7b3ac9a07b288e5cc4b31028b2f0a07c630af095476abcd911885f2334069a0e3f27290a0b3fec0aa715fe65543fad0560aa219781aa76babd57d73df4c044012cbde74a9d60d98676169753bb33d31d82df5d73d4b034aa15bafa9f878998217fa5955d67ead3f07bf83e710ad7c4517e33f985fd46a9ba19ffd5bfb7473839333d961e313436806c9668f16ba12420e2261cf0b4137320a2a08f6a5ed9b7d73c38f7bc047668cdb8338b2d0f6918de0b3db6cb6625678ecb1ca7dc8c91618b55472490f182c82ccbb5357bfe9ef04e0c0db34ed63dff4d8ef80ab9f984dedbec3a0a2273d6b740cbd0c6589ffa4f8d3ecc122ced1bf94fb4ce9723397cdab0f1f85f2d406d0e8f75aec2ba09adca3040d782d75ddc3c04952f44f0d3ea07c9b5a6659bbb440ab1144bcfd9116a65260321e0ed008ef964bce2619e7069ceb697f8fb97681b3ed9e4b112d7761806bd3db03dc77fdd92d7623e143a802bfc89b5d98a8ee50a9cf4a1a3af32ea0466552bf3094dfd8a4eba2ceadc78189e25158fc84250f7439b8e0fa51e9cbd0c95d9f1cdf857cde473f636215b20a56dd1c612b7d36209861e1a71394dfea46c42370222ed35e4e851838546707512edb863bfc0e1fe252d0aa103e62e15330b9ee815f1e09c9e3360994a45f01437dcd3d6353c8639b53b150bd0f4e19e10d08f97b9f19d2b301a2607713c85f90e636e6ed73db1f8eb79bb9e245dd5ed666daf437a184da6ea13fccb49145f49c662686d158a4a5917505f27e79fe9adf58106efe0efb7ddbb9da616e2d98bdc2ca344273815cece6a2384a50a4ee85bc1dcf7715f77ec4593347d54900cea9272935edd02a553fd422442eaf1498e90d0fdaf0423d1b53df573154b44ce174c0d23ac086075441ebbd91eb27fc7ee0276aa9f4b76e1316a8c2474c8035dc249c3887ce3bfe5e2d15c5610bbce33991c3226b1660d13208951dffa1341d983bbaf8877c8aa8be38e5e2c3dc77241b775fda4b88437108440ca12cd98bb3bfc40f74edd67d65ffcc1cf762b7dc9eb5986fba01bc057e4057b69663066f5202a978ee924bef410029a328c826e1b1bef8cc92c801f1bd89f8e5b2090e556428f286b764048caf811204f8eb1de81d92b801977c420d149f4313bc4e8109c26ba4a8d1809bd62749181a4f9d2e1a97699164883a653e7629e7f172a576e571da1af5ed8fee532bc68f992ec236d00cee6ac9696c84c11b1e6bf09388c363e285d0d0d4a976a32962a3de3215231d416574ad2b0499ee67c8010d4f7243dc6f830e1c92d552452bdb91fc0968b49870f90f6483109db35afeba8fd398a8a4711c1792bab1d6ea88580f634a93be92e859b1224ce4e1c3231ea4531e29c005e7de15ac5272848c34fa191148f19be7e1c47e207ad10ae4548b077127a20b2df52a4eb2efc2b4ef9587f8b58e481880856fd1764f012e1e65fd19143213802a518077ad3ec9e8e51f83c6906fc7e8c1ec95fd845933a645209867221e936a96776250e7d508896d03d6b142a243828304196b2f39bcb3a6cd97ca12f0b2b8018cdd35b7b4b27f18b9849bb5663b51dd0a1ad23f805b23e832abb360630cb61f6d885ac173051fe4cb262fd8f0150b4791c6ca3061279dada114ed159eb57d7963b1959a1604b7ef710516592616568a1e5d619c54687b70f37bd18b4d8e05c017d4b162fd838e27ac7b96586b19dbed0ddd8cc3dee620cbcd95356578dde83c623262e08d4ad6bef004422f15215b11e5c50a4db1d1b6c2d4419ffabf96f35534483904a730e8d06ef92e77a349963c4bde9a4c65ce7fee5d3fe0f6023ea4bdd7ecbc07885577b9c0016003d0bf9af2ac3fffdeed79346afb73a68dc9052436a9e82776e8b1a7407337e16de8d7a0a545bad4f2df33af3a1dce9638c13de89c619e506645525d865c66d3db340f2ed7addfafff0a67014e5b20cba81bcd232637b4c65bff61ecf311b1c9f0657ab9a293e67acb50a36fc291a79c2bc55fef8e5967bcd833b8d432cb650ff5198ec2b53962b30385e02396835848d8df8c4263f937a80aebddf80482bdf5d2f71d2acfc02b8ba7dface7f75b3730a2a908e6124b1dae026073348ff84ad107be2bde15a198bb5128eab7181d45aad0c41dfd4b9b9b75a5553005b44d2416b6f5143ce0c1cd721ac370267c63900c5f52fe07511d11637d05ea85be6d9626f1c7216e3239e57cd106032452de4e140c6cea9de46a5a61ea2b7a82d240e346dd11afe271f709e87adf25899a86fa7158e270d68a2af81c01d8760d092c3a7d96c59b4a191658117247eadc14796ca3d921f94ff833a303156fb0079521a4823439a1f732b0a86774e30e654cb0b31330708207d3d75f374124c823e498041ae8345065230d97cb4cbbf8672b25d7254089af454555a476974f97cb390ee620a46a09039ad16d4a6fed38e92b748fc9f4dd9572bcc92408b5637f8860689053edc08c337ae14be910afa3e927b0f5fc41f2c04b67edd4624b2650da212b8b3f10e23ea8bfeb81517704e8e49734ce1c1032e7b481a11d0222d80632f3f5839903d8a94f53102b957a73224dfff7bada878dcb196227ce7d531fe29a38b9b52eda55530afe72f51d86a0601a5c70a4c54c1e09200dafc2203aa879fdcb508e9a1f28343b6ab2c2baec9ae6b871d09bccc958ba4d08784dca01533f0d02a2b5e1b017a758667abab52407a3fffbdc68f5fb4c0fe2c3a391607b307519828c0e91f6dbbd087609e4433cd5b4b875d1eb3b7a5f67c035e4eaf00e476c67340b40a1e063c20230daaf278946d539de8238d4d56979c62908530163ee4e5adca97be748517ee2bd6e0390a1f9628c14fa382ebabaa24977d1abe5c285a2a6145d49421d63f987e8afdc1ac91747e82e491dd12380a4d7e58da6619e753cfd125be41ac867184764a469070c61614c7b435fbbf1447be1967d04235a02c99f650201290919736b895e356e46d1b4ffb2bb82507e88c1d53495a456a85a39b02aa27866844712e1cc4dff000270a54288a29be5cda3866f775b554f4fa4692ad33fbb1931316898283a84bf99efa777146d0b7f02ccc0dbff89aae5050e0177114f669004187845c06bb57001b445075843b9f61194f2aebaf81a24781b9420ff4ceca49d2e7ab17e821bb2c918115d97832296f883aa8cb2df5ae1d1de62c8f5dbbc62f47fd9ebfe2236d38cf2b1891a135bd648ae883d4215b73b4af050486c9de331a9e1adac6460304e1ccfe64fe76b83d0a28810f7c88f3033c6259c06db782c8b21df98f0b2458b012ca7a54a75a48b0a6c5a248404f5ea01446e229258de3168cda004b2704930021e7a603b713885a665f9367454ba1c61cd7f1c965c376d7ce7ed3c24804429cc007c4740fe4a9cfa2717afa4db2f7160740b4b5ddf522f3f7ae0c62c09dfe2dde057068df2547c975481b7e7709b6c7a0fa54857382976e937eea04821a135153fd9b2e20134d43552d0fb731598c74f867622b35e16acf3968facc8309f8005731c805fcb73eb311e4de2a9be32c1edeccadd78679d87b327d237b6d5da55ea5bd6a621a7f9bd95a80f940d8b3d7cbc9caf312494b6a0d0af8137832f3ff04abd4a8601b2fed2db97d8521d3a7dcafc2424cb44973bc14f001d839baaec45fa94deae671677687a62307272b291c502de5fbd21e8d6f26c077d01d4ef89140c513e8858a2ac86440e7f3658c5047413f40a49fde606a21f090b7324eaa5b5c5fed28716259c60c8c161e0be34da64b7804234890aba854683c3e0706c5c265454a0d8a08187805d848239341f76f04d9e7750722a193f8d382c49f0a258dc95736635ff1790fdc26ee85dc6a400c7f84d55572d6b9e4ac5e8e40ec1eb8cd1bed8cd622d7bf022642a50a094876b7cef8bba029b12549f607f2555a4d0139e53fc999d695dad98bf9e1894550ac1f2ea0aadc829d74c9cdfc3ee43dccea8922c09a0667ddc75438e994a94590e8dcfca287ca70c6238c13ad9cbab5653f44c4ae239f92c3fea2c12fa7d9745e31d73a37048423c2f8911ec5d4c20718786675ff9ac3cf2135d91253773627e99cd1cb54243a0df0c5139e716542b886facc2b2a91651dac387d482b8b746950a6b74fc432d7880a96ebc958ec88f5444158c87f00b6164ce0991f4dd4628c33d1e573303648a18b413af622ac2fb3fee37a1aedd889b9553ef086108a2fac54a3514631f5e1578e923eea35e3d4b17d92dabcae8862ce6ab9f394a8a249922a3b0be899922b3d0d07eb3f541695929a2049514331a442768dbb528f354b6ce24b8cfdc2a27de1da5f15ea964661a87242d478250819c1fada81bea370ea405e770fa26a4405cf461df97cec688bb585c4af32fefd87961c0876b7ac7dab643b26dd0294c1ed94a384eb027595f045d4538fb9553ff1fe188b4c204bf56467936d04076e99061018c17cfd158df3218d007099af632d57ab0f0c7b09c01d37c8253e31b9069551791e2da8cb5cf9ee067081d5fd4a690570f1d4aa34b436328f6afa16244efb0d6ef2b4c91d8b90fa517de988934cca2f58be44df9a6f36ebc868cf7d0c3f11fec019647cb419784b1d54c4c3fed09009173ac9c566c634113b97547e271941a73423746a365887df2b1ad4f39573eb24b3831086dc47ad290d9c2a2decc4cb6ad62e36a3b4ea38629b46f92a273334bcc2cfee164749c71adc68420d14a8e5bc7753991f4c8889dd520589c8c49bce757adf937b42ca5d4f1a8c1236a69b543981a11c42431262f1dd0c89204abad2234ada69deac5a96be91044d309bcc89d6386aeb084e93b8a0ca0a3c92e9b563aa450d29eb7f65343abb727958681f9e381ecdd99ee1b68d6677998f67937a4ccd60b3a2d73b9073a5a30c02364f4109a7fbb817a430c9072eb67245ea5128e19786ed7ac66d2860220906369afe79e1c5404b8f54cbd7669e9e5a8f0c19496629e1dfa8578d73bf23885ba7faba2ffd0a830723eed1398b75f9e7fbf271e5dff7cd5bd13e0b990d9d3cbbd0371c86df430e91216dc6337cb033c07b23a6e1eee7aee2aeed51e8cd72495456a1773767538e4fecd2e7165992d8bc55658da708689957ba8d5c7a94ca5a76fe97fc59be376280648d1d56de192331d2aed42eb10c2a13e0bf918f3ecb587635fe0ab492e84255a78810c40b4800e602d8267a765175373ed6b11367e3fcb35ec055fad344d629195feae384ccafee9e29d1b233a7f770c5ba910bd7f51891d9d96120b56ee798fddacdbc7b3de383932a99776920d4fe2878c09f23f336753f0b3895309ea33b3a5012fd3f45dce74707b5d020e0a01f7a5c36ba15578dd2bc9e121dc97315cfe678eab78c777f7669c85a82ecce0489a559658a334abf2dc46362263b48228116710980e526949925e71eac62897b8d9989df50e128798e924a75e15791526834daae99c4a0e1c50a07ade5ded5bd68c1757bb7dd61151c325848b1bc8a371ca12d9bb56a5740577d38c5610307785ee8021487cbf0b68057e1c28d053002987bb96e02a7f872c329495bd5f9b18b7b702787406483fbe371bbf62b0c4cec496e8013fc6ba94123e9f5ee83c31e4b03a5037f848c4d6861b609ea7fe83e06974f51e8d02e71fd25a41998cc53971038b39a5d267c653e4eb23d3f3525a26dc662565668ece748acfe0c2ec8dfbf3efd0e9c3b88a6c7d34de6993a1cb248c5ee4e3080f2a11a9cf54aa29790adc83f6bf8821b99c1ad54a462d78c672e4eba2e0cbec7ec3da4e04ae1fb8632fa730a878161c69401ac044e80c53dfb2f7449644d2c7d1471e51e2732bc51541c3b81074235677c787444b0a9e38839cf2c666db89d8290d55cd101029e893714e340b365f46dda678f72ceedd0771d344ca2ee43c6c8b54295a31b204309dd3fca697130f8b203818fad1c82749fb9bbfe2f6e9d927d41714d880e1139c1e7aebfb90085bbe54f0968455bbd43d72a800073724d7718853b2615267be8a27a05cd5fcb11f2b25cd4e28a37df55646f68284d9ede33acd7074b3e264b933aadb68d47a0862bc47652504eff9ffd3e8504935955c6df2be8fa08a029535aba11f7926bedd13dfb63406e65cf6204702c3499b1d6239676656077b431a99904175935cb8db2064217537d39cb42037f6d18898e1572c6cbc9b592b8de67c0fed8d5aa140f7380a4a4e8817c29c6acb14b8631fbcc48d04d51c64dded9cb5179047c9142ea71da276a66164bb4d6ba1fb1a16b600192c586ceb15171305bd89a023b58618424dd07dd424e570e7fc6b29c4accb7a3a007f4841b80d68e972cad750bd35f4ad69b346c3405aeb00abddafa69e8a87ad6740a36b651fdac45210a45db53434849331dbbab2bb273295e9ec0656eff5e0f10498f529b088a8e32aa2bafdd0db24100bad4621bdf6cb0dc958d1cf63ae115df24af9e2e484593256747b8eb109f7163adf14e271d7679c739e7440fbe58bcd1fe15a9979595dc3d2bdd394b02bef93bf584b55cb334fa2a5a075a5810291c2b69ae663eaae9be30ae847dd61bd7648a3323975d5df5ba029c847352f9bdec89a69015a56d0c5da2ef19c5c8f6faa25d309ca6f9d5238db6910abe7647aeba82677e4aefde65b0e67f4055ae4d5f445d13abdb94897c15ca737a88b6c108f49986c90d2cab0f57dcfa20eac250aa255529fe5efb5f1420ea2d57118adf26f4de93b1e800ef9eba24b99ca437eac3645726bf9e13ed96035b60aae041463d1d8fc781c077fd3c9f28b0e5a4e66a7ef753adba96af6a36c568345905748e13e6a46a69b8441a7098204b07247c3546b917c02a5131fbbd359f8221a6aca19dcf495f9669ea3cd99723390f90bfc4dc37007cbb9e4c371f460be09289a4962021440dc79f1a31a2df59c4e18b2355782f284a34e258adb8039388687bc702044330433c4256561c06681ff80e478e1230d39dc4c35deea7cb81706c2a827a7d2aad5109f289aa631bcd94cfcffa3cfe9b595e28aff8781dbb830befcc602ee801c5605834fe5d742fd59120c19fc932709085b87d7117823df81e4bc37d8cbd0bf9977df71117f352f54c5246a44f72e4062377dc998c431e2b27d842e82342dded405695e241c370e38e665e5f78c263e05224bfa4211751fd8d38cf95d045b700520092c0652a30bd5f8ea1f415cb27b4c048281f5edd4b229af49c753e0c1a13f2082aac46b42679df8d342e3367d57c0ee836f9b017e32a023a0731251c79d273d3cd143dd27ab329ddca62db25200c378bc92e9ba4a77ef5cf3905cf56af857962c983cee98cf7baa707a2e68ce0499aaf7f2672dcc4b8e45e55cb54ba6adcc11a3d43ea45aa2be1c15568c7974313b655e7f7536be2482b6e44a663d501edc2936e1825713b39f8279b639cdc73e5dd6a0a321e474ee96caffc73285db5c5bedcb1d3798045ec81fe3cc162328aec60847e4127d9528683b88da31da9b44eb1b6e8249abd4a6a30008dfded9e6ea3b1d447b252bc534ee0b7fd1867765a6c7c61b57c3cb5c889428302a19f75c95088569629b23c1807b5ade54f24d28c19865fb68b7cc2a725c96b2858d7d9cfab6a3828738f363d10c4306eda16b0ac0319d8da34bf5e84e42f573b9a17ca5a0f4571c3ac7bc00164e01d380bd3ee7fda2a5758e6175ffcb056baec085777143445b7bb84f8132451422a400ec9d52b512e83cfe55d81af90bb7bc428a7bca1b230b2b814ae5d9e0f0fedb20d5f424bd11d713714a610644090b4640fdf5029632ab159739d9552a7d71528f67ee624edae33a2a2b9d7b2f3cfefe82162300a087082568f63f55c16b461fc823284a7a0f189062fc5afaf54da3fd9c86042d126a753568e1b1c6b102c90471e5417ec962e33fa596a21540da3a471f9d3c77df62f561cfff288d0715c50f98001442c541b79650bdc7178af29107519e975a8b371eaabf61238767919700345fec8c0e15af2435596c43713077c064271e1e08838ab440a425cc19090d07033ddb68f883a909ca3a0130def844f375750000b743a979edb4e8015e0d4f00e7f3607b19c54790e8e310babe6ca2b52ab984c68b95352838553f80b2c2c503694b213f4a1878f6a9da0a0e6eec76774f64d448280c7cf6d8b56a58eea27d627b34b63c391d6a13773c6a3ede157538d5f60c3e2640412d2d186ca77937252b2771196c0036103b6ae15e49f2f9626dac76d8e34b5eb6e29f141d63fe8e770a294fd58a973327acc3974eb724d6a3742bec7049652044395b8d9d4b7171457a9e38925bd7011bac4763fa77ec22c08c6a8b8a1b7942cfe7efe4bad8f49a376db558eda67558644ab331f18d64ca9b8918e0b70824174bd84db01a4930b4dec43f46afc35fde0bed6146941701740f532479210d6617dc621ec4f65516a88db800cb9dfcc8a11661610ad7d809489e673a3012ad3ec20f1d3ba39a43b2bbbed2bca87050b6e69e4afde1136632dd09a59144cd5e853d86daa04688a680c1d563838005e56534d32df9a5a51082ac8bed236d190b255d41c9ec8e0ee0161fff16101039ffd8724287195db38f2f00e6d9556e15ccd833b9993776b6d39902b88d459884a54e329db5764035c8f9010ae2daa0f112d95486d4bb9f1a068b90fb797e5393deb3a403a53b16866c38f231ce17012cb887220900fafdca27820610c8f3d4120b02385c5ff4d3d074ee76530d8cd10c36efc328a9b195536527df6ee468bbf520d78e5be0578dd26b73ad97836f2be2261b19b984d73c40f7f3bd08329132c5c86f4187c386c17b3ee56e471cb767d984578bb8a8ecc39404ff37f5c8d30a45a3d82c760a603724875fca5efc8ae8c3150ab10935398c16b0061a41d0f24c0b75f2f262ac992977f793f247edd25ff62226bb1b704b045c92f419e30dfa165ac22911c7c195f900dacde0f088d518a4ea6af5bf4008ccd2051e0b3ccc483446dd62cfb863e5bd84088753e09c592756f6992e16d26b68697a36c111cda07165a79611324051bd1c787842dabbd38537ab6fae0f5eab62133edce6fbf6021a6186431685f50c40879ab419ca7b645eb25d1462c4e706b0b37b841980cc806003faf7e5362fc12ea3a641b8948f979c0d37ab50d782cd9383d5dc62e6dc6bd6ba0f96b83b98fa783a3c8efdbd64672fc5f1d3cd89a36037f7ac027a80ef9cd25a6da32f5af95f16f5091704ce6f61cae9a18f4ce962bd5dbfe2c7a978130b5f43efff8335b8c9e35701841fa8387ae006150f98a72668b989e59a4f9cf2315a4f14798dce73f8459c7172cf5e26c6675010efdcab22a1894309ffe4f8baaa41dfc5e5b168c9d954b0059c2687551a8d652ed08151544e75d959cea7cbe40b067891d5cef74380b5218b564b4dbb14730b4a41bb340c4d3c8d51ac2aca63e6bb7486254b58b2c464383b1cf0c9e60f854cbdf9939c01c29363882d22c40a983ab3908320d8aaa965afeb81e7ddf6f31ae8e333d6a79bfd123e9a2731394fb949fd8dc6d4604ccd07917cec729180f2f150399cf8c164e4d903011c24a084467160e3566b650b6d63409c9f7586f54e1efda3348c80934eca6409e03e130a00afefbcbaa73cbd5aa102424d1ed06f4510d42b7f58e65ef5aa227b0ad5e3c6b81b3b0c8c40e941f4b2bca55e3fa4952c4f3d30120375bf8c8bc4653bdcccb5f7f1b9e1422e536a4670b587fcd871fdf5ba041ef64d402f5a535f17b12aee96cfaa2d5ad9816baf21542a6a860e23d300a5e5d89acb249d12c13b7fb0fa7a29b9938603e1b40a01f7a705894607f49d8fca43a8f3a93c83d5e65993072658d4836054a5d44c38a701ce1e26108a55a2aa53c8a49ae7a34b0c13c967fa8bc2aed683aa504d8db29beca2102e07672451876e0c9c1dc1dd77b82b6183b215ade1d416ae3561513cc8c619ff2c8fca514ebbd5ed27bb4ac94c22990ddce20d325a2d01ba20b4b6b3223dcfed05dcef785dc20c2e32ade294a420c07631ae8d73fd1b7bc6ce8ac2f7734b80368c22451e6681cd17bb62cefa35e763065ab4748d97e47b3b248e3562d369d3122d49ea720874e2f2729868cb72e8dd85b9ff3a2a5471034c54198943ad46cffd00d1ac4ee9b2d23d79273bd94651db42ea51a6f7a27f39dba7db2f76bbac0518620436c43f13d5e1a49974396d44561a1ad957fe99b1074b3188fa22843225e59ed9513f9e1ad6891a131c436b91124de42e6e16c54813206080e4d4b2cc9dcb513e5cb6098202048800804f0c31c3390308739c354ab6a20733af2f85d11fd7220ef2946e2f4072a901ac0e415c45bdc6b2bbafa624b61a8e38f570be7b4556d18b57881002f9c34d56e050a2e7f614415c3433170f25ec8b1852536d76c23ac4fb797b185bef0f53acda303913d24d008f8fc02b77d540f60c8283e81da384ba2e9528b22df5cf8d316084f8169f9762e9ed49a0c811bf355da6ffebaf7ff1ab74dfc64f34088b513f84ca3aaa9bfc9758b4cc9975f1fe0000fa5a1411ff29f6e5cf650e362afcbb71e1e8ab8fb40b204b2f24bed509be4f18a0226f348afdde53bccf742a43bfea02f03a527dfbf3c4014615774ce813e3e5afdcdc3ac252881fe983202bd7890d76fb8f2158623b80d3ccb944ad39edf17617e930c378ccc98ccac750191d1adc6633b7e172065e9cebf663579301bee0254cbcef1a46bc0080f172c60c5a8f93521575a8b48dcacaa828538f9358534b897b1a15a396ecec657ea7aac05792e1828f3e2e295847d10820e6a26ca0f24a426c418bb244172ce6ecc58f35c75829df734393d004f381fcc8a36ad0f4af68da11b86d847bf736df68e0bb92768b06e6ab9c677b17edf564ca093786470e175e0248d14e8ba57dd10c3c53a3bbdb9f56a1d613441e939f4fdc54a618b54558eb335e4293fff188d94892d32403cf67edd31d4186adf29d1306f12d8cc1bf616b285e4d63bd40474656983520395b22b42333490fa406b366f9a6b9bc5e83582584f570498df3b833cf558f19b5b85995c0f29a832c27ad7c29f1907f2b065408e3e82337b68e83642d285e10843d28666381810fc14e6341138966e202fe688818f896fd3d0b932e6573a3b29502d9b45ad17e7080058325421c3da8720c9443052fdfe0c1d0bea4016bd7e30121840dcfa3adf16606834ebc05cfd23fe653c739b649d9473436d407c0903c428952f48346bff55422489b71709653c647b3601709b89200936155a54d58edd3dc95d1dcf21625c5560328a22127d3042bd16ea9fcd6faf30293df8d4675143337394fd5d9c1c22170f5590c48999d752bbc404353ae4d93881672f0a1659e253bea82e02bb100910c753ae106b4017e8e96952cd92470ad8a7e3564f4436be07e698125b27e64cb4ce11a63fd79140a026d50e817e13d2f25791b8c748b685511dfb86e57fa46b93a38d51817612724f14e6c7fbbbda411d13e7a70d93a854b82406b09c174008712eca17b8e1c19e7077115099f3576b380c77b6a9c4c9d9bb2a6c1a4577f42a8cb10c29bfc6a181949f4fcaffd95f91d50ec74c18ad60856dab4e80d3a349119a1d34d0476d34f76feffd34a485dac9ea5f29e53a092b9e1fdbfb46028e0b7f3b1bb95d9b24b1f4c65ffdea0d7b0612d0c93f22ea66a0301c1838638d4237d66a50624a814f110347fc81c092c9a5eadde0834154660d4d53da50e732b444626839dfce3bf7036f18c9bc0b025b9dbd756b0f692630b6c1aa519ab1ddd30e660ca8fea27ccd191851c05158365d738aed2543e8ee8e0e8dd4a88e7aad864092c6374ad817aaf442a2489072cc25c9d4964c8643097a2a6d44daea4a30441e061a891b320c1c21fdad4a3073f07a514bd02d56f3105a99a254479655c92da4f240d664ca0b9b6eb39988fcc18d530ae567301014699817074a4bff6dc929ad4a6b47e1744a14924217819744f58b3270e9933c79080eb02cac44b7a8f71320283dac8363629daec116dd2d5b889a411fd6d06c18bf97afb0bb04215495521edf96090cbd7ea15e6b0c8574849dff3e2daee544b07b4fe4815241a0d15722db7600128feea15b26894422c4448fb713245693e53d8955a8159602941016d43c854af5a69aba65777c51f5904276813cdacb7ec05bb74e9b345ca4746eba5202bc128d448020cae1f8be95662f371a1ad09f570464b111fed151d13c50420b1e3b3bea2885ce3ef316703a95df458328b24807b6086fe9fda4527b29e0ecb6e8a3e46375f128cd5e77f2a783dacc4aa72574c2029ce7bb91e7ca9cadeb1e445849ac205f6406bd56edcf830304ff8a04578949b4eb5c8459a101bb12ffc3bcb9e4e384310550d44114b9f79af5c857626a45242492658e8a01660269844c068d95e16b457c0067b160db410f0a7b9d5a606d0ce8897fb77270169d00ecd64258bab990467fb4adc816437e5c9b835f3d49e415e9255ac8da8a3230039f2ceb1f4c21fb1eafb2a77a5953b86a55af525b3f6a9922afb4692588392df83e56db2f0a0659910300c946614085b8bcdad58e7c744da021a611ac9ffc72d4f37e9e829f4927da3451d7864a7add22961a2ed196f352f5223696dde87037c7426dda337ca3266249f9a90e8374524e752710ac62d9bc7a67c02136195e51a60b45d2abb4789ae8814528c1fe43b61a905e56e4a936ae6b2068bc8ca688dcd78058b004892a16ce2caf26707d2b0316660f18ff1ed501d2bc23b1c662aee8b230831dfcf0c88bf30a0837bc613c4746ec8d437b4eb7a73444a2be449158ea71d5479915adc6becd9c48f65c36cc19633aa42b38bdd9cadfd7b36c4688cb6b9acf58ef8ea87ea87ddc39be2a7abef99b22a89efa8cf43cf81accdc013aec511a393d9da71bc67cb9a1a26eecc34db22c346d5bba9a058473438f7f5445ebecfc880d7c8832316863d1c44bc23ac68ccb613d0344d133c7b0d7dbef73392025443a62a447934ebe1b05fab45fe0bd23bbaaddfd839076eee7209ff3dfb574654dd978d7e979c60e2cf625def8548293f6c7fc8d5e9b521f178716c2c602dc479695dc2b879eb75759431fced752564e62064c38fb78fe5e1865f1790353765b1e6e9923535efeb73ff7e0d67ea0aaa99095463050e729c85790f547db80688fa07dd45d138f86b8988469a3ccc1bdbbf871af870c8bd2e6ef7489b2559ca1c09659faefb09c2741f1632720b53f3fcd8e928e21d89bba6749c3f5b96639b7426597b46cf4bbdff976b5ccedab9166a990abb8e9e3cff978211d000ec3ca8c01381ca1a6e4b465c8d9d9e09845cb487fc6791dc2df60dcc19c979620c7624c2b8a87502760b6e0ca6666b3546da5b69aec95c666b0898899f7e78712ba377694c759e1c148b1c66ea9d91ad24523ea244ac86060ff9984b88cf51630ac5b4697159404ac7c98e70fd48ca5afee6fcd68adae7362c5c60a3770974b43c3f3dd6131b614190a7163606f0d115d3df9efad29ae4b96de49a4c47fe5032d784f882ce00df3034715f5bd76076782f1fc7e8ab78e5e984a9bb2c0e013d27be0646a533ae9cf009675f5bbe113a3eb19d62e42c2ffdb6f5463a301045a7b1b9f8f359c75f3d7b1928391b3cc7a0c02e7a61548969eed647bcd6b450d11dad377438241ebf0480cf29012d072c0d29079a5d49dde14383e83ddc98af7cb79c3ff815f3202708596a442e31171ab748e08b8c0056bcc5d6c9609f53a1756b72d49f78945c39d28c57b975e406bd9a46536c8b139c01e7646abb2a2b5c09bf139638e58e5d800c23d221c1ded8b0ccd91988e485cc62c2df855be30c5671627c79526b079d5fd7a7f019c58fa68463f7411ea3100a10f1d2391aac2fccdae9a3d11ed29e96900f20c2ee90d185cf46cd4835f5ac793cd0da50f1eb84802be652b8f14649e76dcdabb0ea1b021071682ad7f3d0d8605b1e7610e8750e04dc5456d0e7c3a0f4e640a500ebd05582e3418dacfd9c932218162831d78903471b9154ecdf778ce6761fd0238818417ec1aa8c0fbb08663ed1d5abe819a7c71d7a7a0fcc3f29aa8cd1a341774848b36fad296e47747b75fc4f2390105b293d63660dcac5b762c304c05ac57a8434255e2e20fa1265b064b28de92a240cee92c1db7470bcca800501a0cf02e9d568b0354c6fc331a121de99f3bf33a218c8cb0953124041e101d4de1614e0c47c943622a5d35dfa3d192a9cb8543da7af17649f52d338fc46fe183b4762e8f947dc421fa07bdba3c52cdcfcc85fb18cd499a28f4890e0789e03a146e0a001bbad612bdc23e78c6587dccf88c6fa1763915c286b40a6f06d0df61df804f195e7ab77ceaecc1ce8b851612e3adc016e86b190a60e65b461ad87ab931d191e6c76331be9a7464a7442e86b619a4afa76e3b5da5d086ca4195794e53d8897c6b54b8650482d145fde4fec9ab3a111eff157d7704a3eb9146d7aea5b92458c5de62f3800e6a52ca53aca9a5b3730134f506686480fffdcc68720f951ce73a7d4d146b30cc4d8210492aad47e22fc23b666e2401c8eb604a518e27677348e44873e6b0d3d64c97a5b6af71f6b63946bacba0a47fdac1aeb1cd40d8ecc64a26f72491e08811bc10acf9ba4b99e0dbf9b71fad236c3029a8acdf1192d4c9700d934b06f9bf9635036fcfc3f2bad521aae60f05e15fc71e64c550eb45804b3c37f91a449a78fe756c5b499fb1c264a5d43870df25830834425201442708a623e833a534b065ff69fa12cbe854e642ca30896e2871779cc504f01933b412b3f0cbce75773c17405e337a4860775ca0aadaca50c4e844b1e94fa6e80ba3b4f5050d562812b3e9d6847c854329e36828c63527a93d509668cfd407967ec3d01e568c9a2c91812ff1436de9aec322f2038940d7e0d2f74084804c6cd97d2993dbd0f380bd81add940830a5b5c60c50e00ad2aa6b6a49c3de98cb5cab67a460b47820a9d0c5df2adade3171d35aa2154548c90476701ef1949ae613a86ae161e11938674022606d6ce4d428baba8b770790da7da01c131d2104c2cde1053369715ad05f1e872331577b6d41e293ec557917ea1d6271a3c2df8cba83a003d7eb177bcee08203ce0df205ad037db808d809ac9557ff6ff30128545b1134226a9c542aba2dc32fb0a1e7aa894b9d99d41443a51821009b31d1cceabeed4ddc9298e14046b7b22e6154e514b5a1f6d3b385761cef855f762802fdc420aa14d79723b15ba4ad1bd12592b5f5310c440c52072c570deeedf588c46b448a6cdb203e2ff2462a2fa4578fbfac692e4024bb3e3d822500f886ad14540c3bd9b42ab9cf53a376306890e6bdaeeb04184007861535f42d0e33ff21efb934dd31a70aa915e7364074ea591c3249b3cf3737129d64cb40e5384ffb39aa5d554e7e1a533d2db914f937a823d22ac175b53ca4cc0a448fd45f6841487fc192eaa4d34929a72c1f51af1f463a2da50c9c14f9fe93c4b3253a165c538438d0484feebccf3b23563113a13b4dcd0e182a7efb050b2009d0ae44fdc650a709f02d248923a4804294f305e40623fab0fdb684465db30f91accd113389c020f60f491f5408c31719ec2c0c785bfd2adaae37eb7a3cfd68a26de1022ffb530e0a20017fc3caa929fe99642f2d92d53a95145a1d66723c7b00fe1b06f5cffa7bb79533e8ea7d538377bc2340c091ed5dd3275c68e0264b15d6c2c789de8bb190a43c37cf14356ee790c5f66937ae86be6a5e0efe01115709f1db4c07ef925059153ea8cc72b8b8138cbd0a746346e2b8f12a91991c3002e2ed009856e0c573842fc5be614d9dff867576d0faeab659cffbeedc20e167be3eece66a6218dcc9df349d080011d741449cdc7e0c6da923126e9735f0dc847c6bb9960820a0462b822109e3a352e1846e07bccc3634cba20b282a56910ef2236b8009ff1a1d6382bd941ca4baebf6699888b5641d9ee7ff87b330faadb7fbd292bc8258491d116c4c6e3f0b9c842ac2483937ff37c9e1d808e1f01e6e71583fa9230031ce4f5b3a8d5b1f3934242612d8445d52adb49afadf545497dee69c21fba32d4d4a6bc9274ab6ce55be2b594606b200f081993e41fc13c680954506070b7dd0a50833e4569e4fb5e5f5d8dbc22c41b6bb688e3ef46be5f14064a0bee1e34aa1d3e98f67700ba9bd66263da3fe5d246829e64b048b35f490b602c68afd48c2dcd828bd8cef9a9c2c7d20e20ff6c270f6f21020c5845567b0866563657713cdfe8ef6158d10405e8f303f86b194c9a127fbd574eb16cb8cf5eec679c6053cf366f22676b42513a4ac510690336d1fb7130e24e1d01c6b16c467f576032a87656343535824ea912672984854661c3fa5e7a130e9dc39f2fe5791b1e91e2742d21c22d1df0d28f76faa7d21e857891c3df9b8339bef021a0197e313922127ae92cdd451e5a037cb265da2643a003ebd3d61f7fd12281277dbfd4d8029efc37009dce32a6566e1ace22475e33f973d7388ebe75389034a8109732be61079a689884f34cc224f6c2b4e4581e6d7b16135e347b5090fd54ce15643ecd45d9f92e3590ca3c1c8c0ca3319968494490920d9e9f459900108264ea45c349852acaaf345bd4a73a844af538e35fe65a07fabd4a34c5e40af08c8b4fa4d4198b809fc09de3a79c8627a5b0feffe21dc4a855c693094d4487475052aa51b2ba32154082ef90ce77f2026e06f059dfaf8eecc561bb99a30667da381a3c99337b7ccf4b94c58ebed1ab66237d5e7765eceb4301512cb4b1ffda6e2811b449d926d8de87ab282ebd0bc20aa411407548e19db4b589dcd8bcb964cd554180f2f149fd0efcd99458b7c25591d4518c3e5acbe546696f5deb92a6be89f14519db8ab7742ba1a7ad9913955a691f8682dc6a0148c63e2218db73817eb1358fdc1829deb9c6c68928a06d9b4e3507a3e84cc527209676e1ddc80287ebb757ac13b71bdde2ef18a8f37f53054f5ae06db7daed5d789bd3247bdffb768d6c66fba8f90fb446807040f5c65f11110b8063d4c469f5161cc55303609ae003cb82440b9fe7bd1df69dc3f67b3ff87bbe0fac408b02ba26b43a0a8e5272dd087b2ccee4d88aa0c84ccc3ce79239ec77154868748d55e010580ac58b37b5df5b83bdfde77bf594ae4c3c16d0fa3feebb14d5467a2b6449ab23244d0690d58373439068e82168a863ddd01284ba8c0fd6aa0897660ee4e0e39ec81b81a0e711d85b45004aca8f82b09fe09fc277792048d590800ec872a686818369aa2b07ba89122342a17554a918a7c0f280da37ae79e44fa69dc136f418a6943f4b0f689c39756254d2061cb49b2d3dce528f571adb5b481844df533b641eb9f60840518898e45b8c719b03fa114cab3a94bf0ce7bfc421d67ba34b71ac494b65ddc663886e902fdaec42d0e53df239d68089d9372eaa22656f3ef6547332e1fea7077371f73dd4116c81997a96377694ea0deda1af02b07ebdbbff8b56cf19a0424396965cab75004fbdd5b369e6ec8358a4dc1f9a2873d69b9091e3d22f280972db80ff72ebb5ff4d08fc71f6adf58ad3aae47d5292ce2bb108f467ae21c2e0bafc51c050e5aaabfff2dc78f0e2f689ca7c00e8509a62b721aa6f717a251f084141ffef4015d07a1a93375c16f1cd5ce42aefc63e1adac52138b45f9ac74860f22694442142404acc89beada5b74c32dcc707c685eeb5668f37a5deef9aa9a2c61a27d0a724e7eefb7787f289acb96276035d387b7fcecdf85343227a3b0618809b7a2998f9937528f631a860c97fab64a136c0fde95aa0ad210a3c7e9f018c49d89112e75b8e0bf91cc909e1360bb9822a6dab8b121414961ca5d59b81903381d8ab47911db1e888391c88eec7e54f426bf6a59506894d6f9fae3b9aba13e17b636e1cab0483b92791d53f32db255cd40166056c70edcb22f11a0491e0a40e04891d12f2f9b27e16a8e482232f8a463399c8e8b17a17b4e1067d0e69fe225014deccf242179fdfe786e4a124a84eaea3830eb78918471bdf8cd32af3980982bcea53d40b7fd8a11fd6a32d36b96bd012bd17cfcfe576cefc863104d258198fc659839d45e834e233c15d6ac54044331d61a501c33d9f620c8887c19780c250e12cb57f146d24a636fd5125345336f78dbe4994ff63d9c8756d324e6b0e98f19df21810d5791adfcc8c84167419cbe0037bd3056d6f6819f21499c7532b7be110edb737b271943509665ad4e819ea92a84484acb33b693ed46da2ff21b0280daaf8aac0002d31d87a2aec217c83d19459b8a8d82c331fef03096c62e4f6346af932e99a6385cf8d468a6a14aa8d365fef348b04834eeede23ebb7457924fdfbef8ade90e66c41a29425dfd1ca8e2351bf1b6ed5ffc32f1a0415f98a4b1acda1f76acfaa727759027b3a1965b849658ab4e783cb336c32aa45a0bb20b957ca3fc1481edf02459c1f2a6317cdce40d3fc519b944be655be771c677cd8ca8ebed521f8465cb7ec386b10b28fe738175cf22318810d494af17426f022a94da3cd9249495a8c27319f3b1847b21c2e8bc750bf24e1b0684d742e54a4921a3f9b7c86b23fb031ca3fde254b63422c06bf1e819e8a20a2835fd63035d4e9dd0ddc7f1b8ef79eead091cf7ffcabb7fb80b8203eef45efd950a87dd6e027003332d729e30706ac0ad766f76b109473e929427b3ffac9fd176947c54ae24b269fdada9901ab676c45c84650b7d28174ea6ac3a4bb04b489ab7fb08a820300c28a119dfefe6145f498f74cc7471224e52414707a032fd225d859b0cc842e4b6c3269ac9c6b131e81549c691492385412a4f37136a3ef44bebd047dd162e13252094dad8429efe5afc3fcb5e2fdc151e1c84ce064be88d4ac577a75c6be275a7084150b046cb7bce9f096f168f0b9a61415b19daf0c4738ae21a56a649bcd96211dace7304b8525840b76784f176b574c46cdd494d949175af79d2154982e75b5030d62ffad7d5f40aa8439409346949624f89f376103123cc99d50777ca259ca0865f3e386fb3f08d46d6df4a130788afd3e2c0be7cc81db0f98116d378e4ae1c7a1921fd354fb9868b13b728f7f127be417dd7ef55262a56e8d6fd627f5664a724797aeadfe522389d29c8d0e1aa6fb97d3464048b7ce119285179eef24a10f3eff942bec656316ae5584142d217035758b4dff188306c54a70f2fef397e7dc62d00cddff5c335a6db6bf79464e830c1c249faf2af73382fdcaf4cc074080c0ae9cf24f82c783ba66cd85428f413ad0a363c1ab7798fda654eba8fa11c0ea850ebd4e65873699a9f8841e23e833749fe0a641869ff3678f630e112707865b0a7e7647b2a8d4cf19bc46b48fc2b191942173ce4bd439923ea2586c11620f21d3beaabbde1a170bb84f3bb337cc0fd68c9e6a800be861dc80ff59127dd488466ab47cefc6869ec7b8e691e27d24fdc90c0c088f8d1369032772687a11bd7e2ca927d19b7fa8634ebf0a3d859f5daf51ab1569e6a53cc9f4636b1f2eac5e95c34f8b600afecd0ac965bc9c418ff525a77de4efc1eab6a230409b84e838425fd4728b29d054ee83095272382b45541b38459956d2e3d5ee518b508a526e21a5d8959ca44192e7c9e28d6aee9fa38b138cc5428504a88c2f68c21d771bf50026aa82f01744c4431ec086d82129a8dce0f3b2d98b58c5469cf8001e7a072bb26e18bfac22fad5082ea0c7b8017eb34ef4a92132223a153b7ef4acb2a41e2db109f2504f9dd8160686726a1f0d8b48bce9115413948b45312064037203b1adff5df2f392b1ab18d033dc075e6963983d69d28bee2e8fe50d991ec4e6a2b625367eb860121735e55645037ca01738495f13bab0f8e647e59d21347d8e3e6964f1fed9f700e7605127ce6b5a31268b0cb30d24e2169c3a5bfe7c8adcf968b984ef4913f45740c7ce0b94756e1916cd108d79af3e4372d93520c531c4cd5d001bc32b8c1be12fab885e368a4b61c3511521ea958ce82f30adebf3b650f1c6005521b4b065185e9c32044ee8b11750194045f6a4f2ba488962a3626929e7027448ebdceb4623e8ea5342c49626ee418823df0df1d1a257936845639b477c92edd6eff51fb8790f63a0ddc551fe8f5ba7bb469f22b84eef9e12800db4a85ea2086d098224bf6a09dd5da0524b264793c36420686c90fc15311588ef00c66c20fead0ac4004a08ed161f715c3401cada02740ea88ade4481cd9d5e6858953008c121b2ede53a54e3ac760a3c3b3ede7950fb7520bac9f6473878036490231a65bf16806a276125832fc62f6c41684fffabe672c8ac7a5698515a6da952ca4d622a15ccdba24a5732df3beb0fbcde9dd245218d0a1e0462860c071a744e6e2e76669aee36ac6b6d6d2cee2400ca6396a09e6b9b90996e00eae9d5a2f7f362cf947eb30a1c5211100cf4a89a381ed6d9f8a65ff16cf4f3b7b93ae9f64242fc7bc488e835d3119d15d17d2c12ba9936b803d00607ef204e693e74a4aa1f774658feb0121ea2687c912c99009cab44976cc34ce85230eb9a70d7251fb729d9b622c133df14e64f8ff830499a0ecdb55efdadb4fb21b788b70fc0b27ba3b79f95b993e70d0a119ce1f9fc6944969d682cf801da2ec43024e7dbfa749cb1dd5594555344873fcbdf874247a011f631fe1dadf83adbcc46f07cd39f2d0288559a000a70a3658696ff792c6064faefa4a7fbbdabf50698a6db8579ba004b2f9c8de0d0ae3e7963bc209eb1371044f755b45718e8cf776773e9dacdfb23c8848d1d6db4c3829ef12e4e8b09b974d347e3ea76241c1a67aea5237e05a0928b531215419969ee38104ec3f1eb560f629ec42c628503b6c626579573121f5440170db2d6475fbddde46131153f059545270448da7af1e4fcafd26c239d4ad492189f1ef67f58ddc17626338bb987984d98a6ab4fd9706fc330fa465c1fad2ebcccba11bea65f212770e32a41a25b289f817eea11760422e19b630660b106e906943e5d6a39915f2d123e20e367da8ae888822883f50c31150c9b682cd17894e5823114a8455e2869c18e65c84b805291027e0995ccf52864099c462adcd0f09eae9603d90d490ef087119527a739ff7ef13959356916a0e6f18186d9fefb68f3f25d2d0fb0eb53effc7a458327ca6fb6159d12a1f4b804a70bfa184aa187752e824393e53bdcf2ed0521eeae2abc58bbd8f641bab667af748cf523eac6b214abf233f76869f478e6b271a725e62c670c93075e3ced1fd6516526cd6806519b72e30a8b9983121477fa245011bf8ed01b2ba820992f8fdd04c90fc07901e123a0854f2f8bcf5e34fc43de088ee1e8e8e2b239e4865a6c655babdc694284415fbf1b2dd249a4189bb17dc1f4c7c9cf7c13f4a78b1558e8f6fbac0f04c6e2b11599787c79a59d94f14f18b11bac9a581e4a5a042a8d143ff003b74800d300b87aa6e37bf3e567f149df065ec2de024cfcd64eba736f9815dd3f68d7bc21a30e10b49fe480547672be8f3a06663343c104eef09841ff9b070e8f2f5fa1c62ad38051dcb44becc0c59f7e3eaeb71fb75745d4d1a35d9266b1d8d49cf6057208552d4e630ddf2bb6d5a5d1542b678461fdd8b7dda0dcb36a55491eece02301ae772ac4f330a4da5a915882a81cce6c3cf47e1add5775d9a173df39d21084ef316482018ae09bb9e5ef70c6cc3d6a5acba99e41414c39648a47667388bc22e0430ae2d85c551b8fb094c03d1eed5694b8679cb79756fd5e6de8ba76a9d6c007be80bee83774bcfb84372e81d2d40398c086f873fa20e9a1f9dec8602acbfbbc0bec907b8290cd7b17e899d86b8dee523b96edb0fd2c3b0466c44fc91bc1a05c1df5697773067aa8a6bb932ab7d5bf45560e3c218d63ebdc8913984a108ca580161cc0b44fbc15478dd757e11b0ee5a8f061e542e5d8a4916416ae2bd2217040f592201448455fc0491b858d88ccb3bd60595f1ff55802457fb066e89ce8c80bb477190f780ae8194291586940ee5694852e497900650b11f38bc86ef764a2ed1805b469c26829ac3c3404f76e18dfdcf9821a5a074729edf01e7a6a46fe328eccde5045c4786d8a29e808dffecc1d028ad0295c5b40d855a5030544a1ea892df81726aab931ca4aeb4dae98d955a7418c8886b293be89120f6f93044a95a826ba59d56bbebc7d223a77377f0c95b52ff1ca802077817b469860bdd0d84be707692442245065237b4bb9a59452ca9464a4056005dd05d90c7037bf1f07083c1baf47c6e4b93d57794e43ec05fd3838f7de1499137087e5278373fd191618be3fdc6a38d7ef3224c1a6976bbf43adbae4fef780805c087419c1484e667e501c99a2756e3f10f84111051299e607451444ba9af383228a27b28e18bba34ff489d127c6ee23445398428c4b84107d8c1c893edd4c8e18e96672c448379111264758468eb08c449f23d12706922559ac6e966449162b3611830b2a500107ce4b1489c2164896480921922532168958c8224b962ce918a3498cddd127fac4e81363f71122264c625c2284e863e448f4e96672c44837932346ba898c3039c23272846524fa1c893e31902cc96275b3244bb2587107d1470b26478862ec3e4214a58f7442f42122228a314bdc2b9c8343f9c1560ea804aadeb3c0bdafbdc01d7ece75b791fba010f8c30377f2fb2827adb156e85d27adf5b2178b1806bdb10b7bf662d9288e46d07bf41e963d0c7bda1b119d9c604febe0ea699db7f27b9494989202bd5394e4f73f412ec483f88ac8778886827e94fc34ab83f4aaa8fd2981110ebda0ad7b40d9867687304639e39cd07bb64318a39493464aa1378d724e4a6bbd6cb4167a5b5aafcbda7bb12c6619f4ceecc5b02c1b8d4e70c4187ae31e617dd218638d92a2125554a0b78a0af6484febf05b61314593097a9b5a4bc15a537912096bad94d5d031b9ff96b0d63a5cc363892c2cd09ba58eea9379b554473342ee8dffb21da2ce77b1bbdbbbdbbbfd2109ee8be952a4d07b4f2691420ebb096e8227bd882e7bb16c7482515254482ea515ecfd6531614350d5d3ad56f622225ba4894e2d364eada4855aa8a5a5a505e56242b1bcacbca44abd450a7b5fe0ee228a3bef630a5e07419537504ba181a28edfa00805c174c2b6141a945f0b812affce7a3a0854f977902127567e4a594fb39ad5828a81e2f2823d29d4421203eb2638e6eae2227a7f317936bebabec83eb4f2150cd65ee0d75cd0c257aba1983bd9c2d557407878b2226dc3debff6a1777a67088767d972a205acbd7001e30006884aad0355fea4aca757fed7058e089d6fd013828238ecfd555d10c041c90424d0dad1425681a1ec7f5fc06434c0b91c32deb7ea003712a0ca6c9881419b16c06b1a9d24cafe5d4a7efdfeb58d6cb5cabff3fc6c34a1dbe64195cd9d6c791ef578324cd070a60c47cc708b02eebf33b0f602cb604972275bcf57ef22ba22fb3b8f6723857a30b9f716599114822e3443e49cff4c7683fc19097f26c381dbb58573dd454483260b232bfae2d95c44dd884776a1dce1055c3713c9457411e5d65c7269605d8469b0476311863d15aa8a6445d046b6644bb63a9ce1d699724b862366d9c2a275ca7a4c456860701862e8180080c927d8c8d653f94b2b9e4d56045559511659d15c82fbbbac28fb5f0060efd97079714959c118d3832a54c194c545e5eebdf7eccf4f4beede7bcfdf7b0f4258bde6bdf7a65ca15c4c08e17befd99fe6a945c02ff7c79da8f223274da19306d56b51b08b45099471078bf2cb70debf38ca308430068de2ca34c2e84f7ef307b85f669008c2884d08b13c21c417c5be9772a96056894ab24ad058b1d2ea15e5f7f6a71f0b07585428c3ef2a50a1fc7e25c379efefa3fc0e4208e5d04708219c50e07ef850497e0f63dc11756234613d84fbe3db1fd8d50ed3b49c502ded827241b5b88cde9379ef1b110b8e1001eb05473c1b0c6de0f63f04b7f708ec4fbff76eb827116594d1e76687fb62e4cff71ba8f2971aeaddf88d2d829ffb0df4e681ba21e6f731bfed3d99ee1805705f4c8761bb940d1d92404228a1ce112e743d468e9b11533117acf650597ee43e4cd22dd92fc45ef6b6ee0345f93d843f8180fbe18ea8f31e6a0d2184d1a313b1a1c7ae5a253aca1400c18e50620eaafc4dd069c0d192e146d352b464885513fc19dc10af8b566c8f9073e94819bbca496e630354f977b8c3b43a0e54ce11242846195ce534cef99fbec0dd0bcafe1d4df6b721016e43721c031c9f8c127ac751075b0ef5e363ef1fefbb5633de78bca7eaee6377cb61b4f5f080df070cfefa170606d4e37f598ec70f53a14afef61e0c2e8fb7ec0a2c3ff50fdac0e0f701b3f160baf1a1f278e3a1def858f99727bd7c897ecafb784f557af92668537af918daa4a45e5cb4ee21c9f4515af7a2c874d361fa9751be45d3404e363db6caefc99cb6ceb4f9637fc35934ec574a1a49f3a1b281c0fe00440e60d0821e1c0972b3c35322c40a3e925c71842b10b949d950b42e8795b1bfd877d763d763d006043d5e183da86249175f58e10a61dce0e7d1373e726e74e80ddeded6c5c7aedf70bd2be0faf830bb81668fdacbd87d779becfa134d9331c334a7018e8f42c07dffeb5ff313caf375e86c37d40d35d7df41b30e305fd5065764698355ee1bf9d86ba47cf3e7f6750799d3b5a600b9fd50e3015fc608ff65f81d1fc28dc7d65577a861dd54bc7f2af65ebf600cf78f64eaf0dc7a6cbc4fc01d0de4dadddd9f3f777f404db8a01dc2f06395118ae04a4ce600c7772abfb596ee3e76ff7aec6218766da413fba07189ff1fc3fdc3c2d2c8c0fffadbd7cf0ce7c279cec1878fbdc63e7c927c18e17bbbd9f7ec86ebfdaf7dcf706c77bea2f48ffe2f06c21e0d050c82e0c3db8f5f679be8aaf856b8c339de871fdff46ccc8fa755ee8d7e67ff6ea81260fbf5ef7b76837d78df339c9b809cab75b65bd462bd327c6f72ddced16baeeb1fc4aecc7068cc1c907383ddc65e06379273173b773f09f8ef0d2228df9befd3784dd7c09721dfae46be34e4fb9ec8f7bd86defb17529cebde1773371d4f08a6c08756ab094ee001caf6afe9b2dfa13cfbdd29b7fdf7f7dd70f7ee6ef951ce388573f6faa9dd6b9b5b83e19ca5703ea6f1c03ebb6abdb07f9906829c1bece763d8c6037ba9d5adab31624f1f935a926c2ddc72386737e09c95524af9b1bed422db8f1b51b66f25e09cedc1a3756e88c87745b6af95edcb2af0f5ee8e3de7e63f9cb36fffb61535d65863adaee39c7d7905be7ebe8ce2cc0c44fb57eb1a8c6c1f4a24ced99f6c5f6e2de49c7ddf3ac839fb970f1d64bbcf920adcd164fbaee3353b5e63df7f04c9f6dd07afa96fdf816c3ad93e86b5626cadfde7353176d3e140273ea8a20a2baef09d5cff5ef9a1e4777af7dd7017e2895cc3f570c3a8c0b18d70ae66381a7bba35128ae5b072277f470e2b5ff1338d47f6a37a030b796e3972b7e30965ffb206829c9b4c9b9f651b8feca136b70fac72672f3f9355c69ae15cdb1fc0b9ea19429821007ebf1049728cdc9902a6026afdda170f2c5bb8beeddbb1a3c7510bf19ab8f514ced5af7fbb09f8df649aadc4b9fad209987efcfad07e6b1988f531ad6b24b9fe05df6a0dc4b9fab009b8739d5c1f6e1dc4391e15e0faad047e8512fe05bfe3b2a40277ada495784d7ffd9ec26b1e500d8872fd6e798d7fd562abff62eaa6c377a48f967073c021dfff0447fcfe1bdbe18ba3acbd9cd3fd00163c085f8c73efafebba36281f4a58238d33c6186b226d080473957f9e588cdfddf0a3b6e3b630ac45d4e4226957a8d138cf7d41c0a7fb9c46862e47eee0efc891fbfda5c6437ec540a654870e30f77800043937393eeecb16b0c98f1addbada8560c5236705146447a7c70844b0baf19173f39c8b99913f46572208af7ef59741f869adb59aea57ee3977ebd55dd31040eb3c1afe9d0c110b432f251cb2fcdebfc7b66e478dfcfe3e8dfe80df5fd75f5ee0ae75b2bfd6e91fcef944bd0dc7fbd9bd7f5ad4e91f38409b1a50e55f2bc64f835d1f66385d5beb7c3957b50cc8bad5ae816b6838d0bf5a28a7a724d31906f07ef0a2784fa693c1b9b082f4214f00e11699e2f0834c3f8ad61186e21288503966bde1785b179fc8f4af84fda4cb39d258a480bb98e94ade00fdce37fcb264817b74ea0d7c19bcc6bf421b1cb87f93097bf924e31f68731a3a110165af94d229e79c92d229e79c92d229e79c92d229e79c92d229e79cd2e6f40e33475e36ec781042d9ff2a1142bd264c57072adf5d2097ef6e507edfdd9ffc3e08138ff31f2f82e93ba9647e275ba7efe410e93b4954fa4e16ad7c37572caf9b3b442e8b75a6948ec3f7994c39c618574a58cc52eb3949266a0a0a3eb994523a4f78a512f460f09f9ad5c2a1d66fc33d2ea89d582dae67983cc12aca8976dd400d621aeee11c9e596ed894c5ca54e1c41a46c2482412e93d089ce13c181d981f8ff44f7b92447a2b64a5c0b41210e74a3c303a403facec6fa540c20a794dea8820283b3c29251e3bc43362e11eaef2ce7d62b9735613962fade71444c809af82ea45611ec4094607a6872ead682cda48cbfebe60fff222ff25c391ff925dcf80eb86b49d5650f54390a9f5645a4a8482e469e7c26b3bd9a19190d7c8af90a80583daa86a3d88f493e06feacbcbcb4b0a06060606c6a22caa67e84e3f4e2b1fa80c798d7c53c571255fb012cf4b7cc970de5602924abd4f65386feba99fcaa4cac4a2c4d3303dc0e8047162a8c413698907060606e6354dd3b48bf37db9b41e179722e750afe6e48b0eaf6ccb39265e8db5c20ec1fc70d5ff3fad24e49ccfb7d92449e06ddb329cfe2d937483d1d1c1dd69755a411b2b04557e5a9d7cf1fe27455e93726275621965a5edb4d27aea9f7ec01d8c4e108c8ea9f560f0279d4c41d91f6e5d0a28fb5b2becd6a59eb02daf4939c1eae15c45c1272727b0158a98158245980b81bb128f4dc997d6d3f227ad0711f824f89b135eb9a45ad95f4549f647a584b23f2a49aa083cd99eb4530f9fe1a493fd4ba71d164dcb36b89d56ce3961b7eef493fdf423453bf1647f94ad3b05c94eb7ee2424fb9f6cdd8995af9b96edb4fa01777608f74f1a941bb6727f57e2c9fe264d83b9b41e953f69a99696124a053d197f169855f65f4911657f526a28bbca4b265248e48fd7a48220777277fa01cafe2ada6975c2b453d54e29da8907aafc51b413d64e27da8935b79492d312b8c3ab55f63adac9fd59e479f98c6e78e59c07813bbcca3319ddec100bd8bd5043e5cb61506bc1e69c325e6badbdb22c9332bed471ce57582da242910683da05430cf4ca00fbc9f45bed2fad676ea9542af502a303a36363ec504c4c8cd522468653e2b143259e120f5e711ce7820c193264bc80573373a68773fe32198e1d7a32fe2d2ac0dd69b5cafea71dafe9ec50f6558f93cee987d7d821ac9e5a502e2f2998d76c5495cd9bd00ea378e118e164054b4e84e878452120fc55441326987152a40824a61892151501e484073cca0fa808149c0051040f298868b222220ab9c3af041154e4eeff2b11475810230698a4840b30560411be418e20a2052920828724e07b051ef8ca0f680817b9fb1f62c50a50450c9162081196c0363fa0213fc0439830010b1843585e0c01e287f87002121386e88c4480b32f464ac0388b132398250911cea0c802ab20913511228447c80c8210b2f3427c3cf7311c9316141917b8f6e084072615d1040dc0e851c1c91078253fa021bec8dd3f3104167954049ce507344412720c376308217ccc1046861842ee9ee6240a8f1e3d7af4b8753b68381717171717d497505a972347cbc796d8125b62cb779f5bbee3728e7ca27534342eefe2e2e2e28242d5bf01515a876972d06028cc9a6d88441dfdb26a44ec4bcdbeddba9c7c61df61e4b875778688590d7e67f23db3cf3bbb1fb70edbba9a83b5f561f54b43d51c739d8fcd374df99d75a04097932f40f306608e9f935f56438c5fe5bb89da03b241517ed98b999b8eef1c875188bcd7b51c74d07ff97e17b71efc78db81c8f544b097d5ef626e1cc7511f763b9ed0c9dffb398c361d4e99e53bfffb7774471fffb28cba1745765fc14fe54f7a1fa5f74f71f82fbf3cf8272fbf9c54afd1f1a6676131fd15ca2d1f3b8c7cfa7baf632f83230de6e8397af6ebda46a32dc6adbb7ff22ee35b779fe56fc0abe5b0f289d63d211c1d461ef9e83ed4de93b95be78f7d74ec73a8df71209b361d4e79f4a79c437d0de8b86f7a1df75d832aecbd199d6f39d4efe2e3c73ecb11033a3744e6b433caebbaa63522caab8b2891108ab28b48a994510a45d9851233902cc96275b3244bb258b409eaa3858c4808456984a45446a1288da07506d58708f531c20823e40e9e236300aae0e30394676ebabbbb8afb7af078dc83f0bbbb5bdbc1e56efff66d87d92fc389cf216c28474ddc17d391564be02eebeeeb30c618a59452ce396706ff524a6906ffd6cb5e2c1b9d609414155269a545a665868c9728a64c9bda4be0f3cb4cea05b875770a4605f3dc6b2eb42054da62b0d048b2224a544df84d5c0de83e4d4489281125ea1625e248198e9bbbf564fcb323c4887955e4ae5b314a9e129bb3a227931921460b5bc46035b9686d8473578890468265efc583f5676922eb9f1f17b887b14571e0d6ad1953eb7adf4f78cfbfbb901b8e16f268e51c10d50b48480909590bc40b325e64cccce8ee9e81929953eb9d99191d9e223c33333424071e0d1a3468d0985e329cd83430c410c30c00babbbd0800e6ce9c29c85d4f0d0315987ea302c1c88a66a8c98a5e2b0b0372d1d2fc6786468d00a0d0c010001b988b1862b01f4386633f0058fff55dacc1bec3ee0cd1a18f7a7b4fa6d6fbfdd8c70ce7dabac93083006668e8a076a29f7836dd48b217b9401a5b08da740bf62694bd5b5af4900d1b36e2dbc870e2dbc8e0e3294832774d23dcba05b910000d987c42eefad1962dea3f34cc0c401221774d5b5249830110a001399cf387000e4a2660239340ab003337126043d08d3880030c20bb0194676eba5bce2dc1cdc87a5ec68d8ce1604972d72dda26ec3e9212112103838e56d9b3220318c000d300190efc1a5843bfc3ba055544e82b5a3871eb16916b8003d05413feae616f80a8af563c23c33173b78c9091f5608c83c371d4d035ccd8d0dd91880d333466482b186dc460525a6416913480de59dc17d35d56f6bf2d8526e4de4f0d57f9241737393403785930c7bdb148228918312cb4e08269c98d000f0d82386287c562b17a60f5c062cdeba3d73c0c934c2e1f139d8b4e8a19beaacb33c9e03431dc11b6e110f7c5da49f9f25b878cd02af15e440f3df46b17b0668473fea09f8cf41d6277370a5873e7fa94b89b833635b81a3c60870e230d21c0a43925c771d0e600ae721a1632dc6e542335589073ba9c7880b9196e43430d216a1c31279d42b307d008ad268c8004932e9450638a175031040f90149d200b209b21801cbe17fb41081d07bf0197df3f0821b43db8df9f7c33ca37eb0f9b9f9a00f7cb6a332bd68feb93e98736f6c66dbee18f83f764209c715514fc603fd57bb8751042282377efbda092dc74a152254d5a983b10c62b899a25c1fd9285dc4108e103caa4c0fd302646eede7b0f8b526ed8a4efff48ce759f5f0c945aee20847068d68a6384f24798ed8139495d14fc9282f3fe751042a8422acd95f79e8965e5d4633ab54c1ed4dc81aa262a72ce143994df8b0b4aea05f79389270f83450861041a1561f9c37b0f422f324904612b3ff903ee8f8f6d98fc21460c0b2db8a082ef49bce7e40f5285c91f649c31be805921ec8f0fc1fd72bef7264d8d19325c6646cf7460c2263219cefb07219c919921e30515e7420b2cc4c4d8b48749bdccd4d8221ae1dccb2f46efb901218470076c21904aa9168a5a61319da4bd7a10c13e7e47bad3c5bdc648e3d47a50bf9d7ab80ad52a97528a6aa19456128974ef55d17ee449efa57485d24be98ad57a56fed24a405c35698b44addcab85b6b4d0ab67e5add6835a59d1b08c6aa112d37a48a43fe99c20762f4ae7fc97ccaea450b4455e24885d3b6789524a27a594ce9bba52d774b22ca415146d9157cade3927a5294a3f95cd6b7f05455ba4dde62d514ae9a494d27953f5baa6534dd93badd653fa4bfba89528a574524ae9bca9eb9a4e29cbb2915650b4455e8da5ec9d97d57a5c1ed37a5e1ea5a15e4aa4540ab5c272c2ee957a39fdf0c162d745d216abf5901ed37a4a8f6a912713cbca15351e182891b4d34a3be938e78373f4de3badd643299d72ce29ad954da69c734a49e99cf40ab87fca2653ce39e5b5d7bdf6b25a0ffd4b7b4157c0dd69751294e7a8944a5d2a7a18aa1a190000001317002020100a08056369922689be0714800e7284444e4e369487847118474214444108c400088210438c42ca38c494222b00008c68f1479a83754fb87b6a119301b12527d4825e18df1d146112ef6e7e27a856bfa37d87f6120a94806ee05fe4e08db18cf88b324792f332e8d28f141673d641889a110489209eee8ea2a8b55c71b30afdcee0a56e0a5150353a0b25a37b3c34bd3b5cbc68686134c204b072a73824d38be3bbb98e846045b3d6de1157e689a8b75d05d0073ba093333edec29022267c5697e39ffc66fbedd44edc417d396f0de81dbb155a02c04df1d3fdc00e803ff27131125efa17467e7853d042da240ea4ce53038c6a30747ac4c3c567127469753ad7f1e6bca14986454bb03879c049c58df491c7565680643a1283e6287389b8056aa71d005475113982d76fec93f1c6fe1b5910ed948949686ea126c68e9042c9558360820d445966d2b488185ed5d9e5ff3870c6277bdc146684444b140753fab2e6ac80e2e321f22139cf320dfa738cf48380f3f75ad5c73bafaf38501c88148377454c4af62dd87d3e6b8082823a8fc03d3100c3a05d1f8f722ca52171cabbd321fd572a1d37df41ce3555227f220aa94a11ac8a419682c526a8a4c86b949133a4d37de1b691eb06319c464db9250a3a7b030078d89bb5f668a7297c346859e43ebcc84af22473230a13642ff98e75056589c92c308e449e9ca09bd9f8d2495cc6f698f56eb2c7506b1c8027101f2501e7bb26f0bda71daf025e88490b324381d80593cffaa029b3ef0e838b3cdfe3587b7357a6af3220057df560f002cd1b6fe89df9e20a0e0fc3e7a68e277bf729f4ca15401c19da45f4ecdb07e2431214f449188ddf81dddb7ffaac16c68b715a5e429fdafcc75673d10703e2d2ee7013f7cb0492c01371e7f59071acbaa6fc505a3e4d04ba4585d3b324b757c12f2ea0e09c94ea19cd9171a1a877332231344e69402e323101b9e4341404ea875f124d66333a0e9e0fbdc774baf63bf18e11ceec44b7824a04a71a2e8e4fc90a5791a5a8a7bb02f9b9227e4884cfa19b7b812c11667b7a559faf41f0f94e7e5b3064f539609416faba0b2e69c9cc3437f4bec66674184bd8aeea647618157cf99eca13131fc52e3ab5236da20c09348db277aa4508f8304491e69f415794a17a47d4b7f44ba46f8c5452cb308e964395f3f22dc3b15fb6114d7319dafb202a639429745809606fd5a323d13c140b85e512b8a1ac942209bf2a62707e66940d8fbab6245c56f239fbb738b810d6e4c6a4e6c6d922a078fb8621eec7c25ccfbeacfaa4be7e5c253234e5577364e30bdb410d955971be4698a81feb64f25944c2e068ad6cf06994a63497af1764c35217b554574d4449ca4f1bb0e4c24c05cae01f17ee12091732628065a612a5443996267f898ca475639222d0585a1c6e5e0e1b7394606267c7b1760ced621cfaacdee5a3f0a31ccb1e5f3f2b565fee47db09c3f23404729032a1e02242216dc22dd3de73f9e9973aac86c0f7c5a1133e1546be43c1bf302c8ec2f617746b85180f4fa059f9a00c246b6708a2408d5ab148fee230422fdabec101405fe8d1d98c98151cc505588c6163e2d5ff47210b45383869bf70d305a87cab3ed9f8dc98ffc3309d0759d840184cb49d0f3e3f0d837d43788575423ee06a6008eaa53be24a6f159c64b654e1a1ad0b13e78537ca3e7b302b83443bef0ec9bf666478be37a60a4240cd5fe5383409d4319c01f5215911d434828966a070bd92641fb21e356b113b6428465508e22240c381f2724995e6b735bc9b123ebd9f5445a0520c6f5ed11d5dfd0acbdb38dae9322ba47516ab6ab35011b637885e67081664433dc712c34b78082b868d7feba25b5be1c75c8097d59482086fa903bc8584a1bef009d8877fe25a5818fe65f2787e21df63184ef70ac7c0bb17106de4ec4e49a1851c3e98fa54553067bcb35afa131840f35ae1ffd84f9214793c52abbd4996aec80efbdfc050ab7ba37fe6b6e748127c2e0417bd1403e255a8c9b71710aa027e418522412f546678d6ad8ee5fd4367d709a61b6821208fa0c65a71d0ad9723ffe4b16df1e1081259e9bf88811efa671c3bda94b7a248ab062282b4e93896b051e26d98d11ac4dbe28c33712cdc31d927a45ece7695f92cf4501ea41be203d0f7a33ce2ad9702cfac44dc1f4e5690f574a610052f69ba3212923f270b5d3f093d6f4662dea0fd98c54d00a0ebd15859264c2636b6d50bae81ce7accde3556f5558123935288b749b1347918b90117c0654f10b4771886d386ddcb0277e5e1ba55e0de98bc13446051499329260b1b2553a7c67a936e9b5c855ed5e66c3c0b8c768848a792a372f8a1dc3a7874d035652dc6e68c4b2947337740daad9844403cb84cce932544a1d1b0c12f49fc946b1abe74710653c7888a850ac164c1a420441422768366925c4bd5e6522fb4e6219760ec0717f406f12e08c39102a586c7c3b9737ee8bc2270b90c9326a4ff7a63d6d40f8342ff0a59af122e0dd7995d4b0f1a08b9a4ff8333c934c240acf50950b2cd164dd5ae2239b47d325897329fbc24899b4639cc231fc71cb0708176a8c2b26b425a326f3194d0e6e3a157a80cd93d4a4eeb1034bf502221b9888198109df54084ad6f9b23dafaad1c4f064f1695da43ba86b6a5a73ae74dadf6bd4fee0928fde71bd64e6fc0f346871f110d68b874a3c8e699313889f54677a911c6da8b3f36f4ede1d98925e593702f3d7cfb22817e9150577e3cb0f2d847f2e288ff22d3fd99e7544aa3a198337b563a0bfb8e7d6187ea5fb433e34ac66c15d515556bef5fe4eed581bd512c31b324aac6335866f7a8b90182b5502be38da16b1142c39a3bbca8f5acb874c1f7b6c6715bb7a7de45247df5acb21b42d179ad141ff257cf72cae2a3829e7d238388bdeb12e4ebea4346d4efbd68745ab7708acfcd6c992217420623aa7c53e2b6bf43074f585d4989acf1f8b9393158104d1140e193a5dcdf2cc07679477af4ef81897bfe7f3034a9dd8ba4346e35daef10eee248727fb004d0d179f9117783114cc3972ad80647b069f9c081cb86a30077cf2d33cf15b07a66fe849b3a2d9804070b3476cab960f33802b10daa4804856bd20855083bb57cb3c9ac7408a991e710406bdddb8694a7bb85ef8673bab4f5a5b189c66ff5534a2b554ec65bfb921d57baff9f9ecf64196f125f63f38719260f84683d5aa7119a7c2d4c9ea134c8ff116dbcaf4eeb2e19291f1fa1b356c7608b4110ef385f8c82b44d643a5fc532196241692cc58601b84c494200629387b280de3e18d82ff1fc5f9912546af272932bb492521ceb1c43d405aa1f286c26fa8bb26173b179e9609f0836be21f1bb94ffe3e467b366d21cf535ed3bd7333035cbbe3e81e603f8a107e365231b6ab79fb51e1bd90c8038dcc68a31609219a6205470d4cae1a1e368885c13d3dc084d1ebf216b405c0bd930875b06c90922187f031588e783ceb331c2d908da95184e943eb720508fee951887cb00ad3d1040850d6078dfb3e9138f7b149712af6c1b23f5f1f9d635a50767b3af984e55ba5e32405823967e4a39bbbc04084b35672ee4c50be5b3693cccc49a3082feecef365df0dc73230e8465f002dd090dc270ed166f81730188eabb6436683a2e586b0595506bffafacb33d4b51bccbb8601fca1128d710e9dc32ee78b386d383cbd82797d84b8718f147a5d15307a79a5d825434e080202e884d3957d59da7fcebf0eea6e7abc50cb2bac656b3d544310cd819f2cd62ddb42c94981ca141fd16a856c966266b0dddbc27df8bafb1c860daf03453386b38d669c2e3cda0f84adb57f4149ced36f60b216f9db5dec3036f07d740ef7e9b19736a47834ab68b533e3effe30958ad72fb5063e3c49960c9ec9c4631c8927255301cc34716f422a61643d8511ccf2380454582a1ad9a49d75c14e0bba34a3d4bdc37137de631179a85ab7421c65b3169d5702997536f75be3813e148bdfb3c2c278f279d768ee4f1937ed56f693f939932aa41e310503afb6c2bece38efbbb8c3583ae54b36fa2e7b6be10df2f833f8b0e53e60a0541cca27734338ccfabe5342821f7b34a202821761c2c1e424e8a2110bf3bfa801c7dfe764ac82b1530b34b56bf5baf37dc9fe2faa355e33542d18d49bc11587c1d49d5541ee454723f851736637888e5b904842318f0882d9e6e94f2b85f35d877ce42541df98909579647ad3d604c72e2fc38f0e0c13d333793d40646c6c182f34755249f90443893a4fa1fa420aa249100cd81d71d5eceb313fcb1bfa35252d651964807b8205c92b9fdcb121658b3e7b5b30434348cea8fe019211ef46c0dd45184153d79c37cf4df28af1d9ee470f4da86351006b6f01bf923457b556090b8bb838c68aeef67c75af2d9f64667ee0dc78b11de9ee414ec6400dad8bc21e7c1dc31fb7a0b3737cc457c042208d8ebcedede945188c78699803d47c9deaf8dad39028710ff57a29c77112ded1c98fa17eeed044dc006411a80093e6db1b0009e3f0c2ec8ca9c5c68359d8b7b4fc8a9946dc201ba802511c157a12dbff494f9a085beb7fb5a9ec5855dda7000c312bd3f56e539239700ff97519c541925cc5c8cfa959eadb40282c9fbeb2425e1b858d244058224077823f00541577c5f89510cc6f406dd961def12b93a8bedb155dc0a477341dc4d0b9e949c10a77cec0302bddb8b6cafe7aedcf1724225a80534fcbaf90c50a27df054aee717ff4dfadc4894153fb1e4443d98c53fb0bd3e775fd012b482be838e88dae85a162299466f8436e345b752dea9ae02bb7373e8612844a0ccc6929b132461493ecbeaf4a69684de0ac6eb6d8188d699bfb2a18e2627d55028d2bf5b93f0ac14301648caba272ca3223b2591daee6091b6770bf4d89695dc46dfa53fa21a7ccd05c56bb703e2da5e2cab8c39b0c38d48728b80f3346a20a6d0d646e63d0688a64125395b4c6aee281d339eb9733e2fb906647e0af5465c0b78232b7bcd87d9426812516080d7ddd2617061932bdbb058f0b18caae742254277de07507080f7dd22a35f6c72655b1658a9b47dafa0c60f6dc3663ba34d6034b630597c9ce78dfd8e108a3ce14b2c0e0f35be2d5e1ba4cdec0f91d0b31a0e0414b33cbda0445f847ab00f7ed0c6be126352b13a65c8ce5f99bdcbeb59ce3e03206c12d1374fddeb73b4437bb87a2945f08688094487de44892ee67c5e12f41eebf17c3ce7e0d465cf137b469f7be1e3e6d953cc631d86e69851ecd8b6a5c8d17dfa82acf1e5fe435352c04e3238100c2de8011599b7841eda462e2461d8b8f04c2171a2a463644bc65b79d7def79edb5ab4ea66b96db2bcd3bc241a50189141684159ee1bf54909f736eb9fa9931c5cd841fb882b72b487a7af6d5b1f5d75e7eece511df457639e7daab627ab8144aa6357a0deb2664081c23ec62ed4006daa9e637377cfb995ca84ce9294f9e90a32f50cbf367929f5562dc136594990dbb916e3a6c5bb3c51c0e55ccbb5ab0c5c419e8efa993fb6402ed983778d377ca9696cf8508fca19e961445b7e6a149fd53ae350a09b847e7ce034fcb6a0bbe18315be079a7452a9fba2ae3f2b43c04b91331e4689d3fd12b3b4b5d6ceff19bb196b451fa9e44d307776af08a9f8f8913917f37f8ef12e4c6158e0794b1acaf7830c1fdaf5421c224f094b7b2601e6491308c80156e07bf8b84475d7eb8d2a0931f704ab38cdbde2c01170646c447d726883377be919d0020b320110b5066e40552a69406e2790754b00f9409eaa2eb48690d58431629743b897e63a4a86e6574b47511425a979f6e7ef7e6e96f5d00056f59a51f0ecec87e8e9183c462b7012b607710f573863fe55dcb80d33f44bafe6263fc5e1f7bd444e4febbf6b369875583761502997424ae76c5f39aa6a4ef6a633510c7a6c375e43593680d706e3d5ba89357064ce50651b5ee8e7c1fa4fe35ee4be80c61a049cd7b1b2f419a0c1725606d994eea6f8178dd41efa9afdc1f320bdb3820220482c1fa692da6507690669a126804c5ea478e26f721d863ee26e1d22bf9da9e40ea9252b5c527400fdb7e229a216169b0bb7b5fe7bb4a8ffcaa5830f6a8b4808437358689cbbebc28a2d0f868be9613e810b893d70d5b30456223c450e1d4e93eee18949f7bd6dfe8dd99689e9523997b4179c939ce0e7655fa009e272354bba3ae950824e3f482e9767baaea2eda0232303d94a38b95db1ef5b3fa66bc6521369215037e55e304f621bf75b1109eecad58f0681fa458569bdcad963d8be11fae95deae248f342795daa2b9b8f83e291264f01623fd33c5e7e9d77604eb72d1709f871b67b7b2264cd39f7ff2d3dadad4713732add49c5b6f4bb1d867d819265d678d84572792b243652ebb122ebad56e38721fd1e7f594cedcdc1e434f793129fb1061e6d24ea06746dc4d9e3c388d5a1024014d62c9b657374b8f804ed7a476bfcf333fb5460650474cddcb23908469920302f7d655afa0ca3aba6828655e4e0c21085f396913c5d350e67c6ec809a35a5efcac6d4ca42d6abe136b89336d4cad10f7ac5a18881ca070db01a3ab52466b638d97c4fb8dafc40a222e2cf683a121f7628acb6f2bbac324e999572398c6ae90170bed940146b0ebef3bbd5d9a8e93f67b7b06fe89aea823d1c5911df0353e5094bf7d9a2a9d70effb373fe49aa7f9edb88677e70acbd4983e7be79c6e90691ab2797749076029c998ae03c412ee55263f40be92d40db3e8e4caf2637bbe67748f2b7e97f761b6c288c7228c9a8a09318b7384d8f25e00384a01efa45f65380ea004bc53e48f83d7b971fe4ed57f38ec525b22b1b7f4358537a9804f8e236d17e5b281e6f400cca2541288b015d066904ec5c52fe8f4c6733f562df16abfcd45bb7145851f91f579841b05dc7f77ce6c288711e6d9cddf9124f622eb6fba8681df0ab6c9734097b9e96bd358af6907ea3f153c2ab22ed1ae53ae8746f861a22fea0aba0c99de40f65fe5c98ba41f791dfb6e09c9565372683cf6857a1874841acc93100d1e71a534e623dbf66bb6defc92d93986cdce0f1a221f603a333821283ddfa3de4887a3c297670b7c1a8c23e0f86dddf715cfccc39e3bfd7e51d15139fa98ae7dd4b4180611c5530f2774a2ab8fee4485f7fd0c5e058ae0a506caa4b4dcdafb58dd58f2e76361620015375b5e22ac6612b18e45f0598ca09a3f3196414c6d07141bd41d0ce30e2e97f0069e95cd27f3c1e714a77632062504583415eda3ff452e9163d53f495b9d8d428b6e0f1e6c0967f165c8835ae1d8b173a2698858de084266e600c5251cf594b03e30570d64573cd54e51093483b6badf3302b892afa7abf87a9133941b45166f69244694804695524d18cab7e7c42a0aa386604f664be8b012bc5c2869498c2a1fd45e214d1a768ee0a893ee561603890a24375164213fa97967c5219b6f995c3bb80c4e703e58f781500f6110d02388661af058b8bd7721ef88be0e87a9e3a04311d31eff8ada4ce7b4f7a2ac711c76a3c4e2ebcf7264bb1ad5996f71a1c31fc0be902626ac9d4e8738fa944e370ad4741b00c0563d2ffa307b74292eb4df258f7a690fc837fdcfafc1c8431b76c17991b782eb38eaecd4fd787ba2dee765697b8df6f4fad3584a42b0dea5dd734f5b7e1141ff2d9a9f06d8938bb45df04910b97c654b0223301d9240b699b2b27ed3f78511aff43754efdf77569362d20dc61b3e67013c40ddf60e36c5acde68260629500013a0bc2df95cabcaa2036e26cfe2cce65eb86e891eec792f716e764971460c6c4c9ad326f6b8f31e44901797f20599dad551b237ed4c1c0d151a4dbeb254cef1da266df9be4653721a7d449090ace19ff521e19fad6becf93f81da0c998fce844a135b31580b60cb545ba3c023266bc1a07bc96f31527cf3ed69e93d241ca2fc593243faa672fd7bdbd9a25e6f06e08b2b3b2a683410ede3fa71fe70e019106ae8e20148835256f4763c61b687c441bcfb8b681c5a3f0008a6d6c0fe53ba53c0dd7ce73289b1314c7c3347c2766a52db8dd1aad643f05767d5aafbb116c5a3683dae2932e057cc23cac0045071b5f3db3777c0f0896c48186e8ab91add397e9a3c63f38d3c3efcaffc12826cefcf2743b817f938968a4058182efbabb8026f1965405499507bae8c1bac8c9fd033ec565d213f30a43f0fd18bdca1c5e6eeffdc817d51e9bbdff133bed9d619e5ffaf1b84ad11eb466e640f6b59b3a566287ef8c554e60fbf140d5d2e45a6ede902da73728496cd5f33c5ad1136144c5ec3f2a7475b93af53227f8a984b380012e30cf90949b203d48b534d7a9bcae7427d0c6fe49a3f3df576eecceada3dafc95c16ece3d65d085285bf2f39621e7c0753facb1171c1fc083caf7c12ed5104d8c296c20eb8c1266519e3cf17937a077ca421def207bab36316f833359a61f190cac9b0af439437f80763996363a6fdaf01abbc80eb09937e51dc608d7144c0a5d8538079a5374d447ea2f296cf9a911068e4a94cbe21f6f0a489a6905067b85926ea99029c385264ae4aa7ed210344426548bffec59666c616042822820ef53d81aa900f1d3c5c1930876f7d7bd6fb500f3c7271d19cf4b30c0250a08dd3c4b9519733d606c40b7d525c006148bb04682a1fe25c8a2e06b965a61979bd5a010c3aa3c4dc0242b26ddb39d354dfd57cc6aceb8296b0f6b7641200200a168a3e409f85f2435e058d3a50e82ed07e0b41cff7529237bd3d72ac60fdb9e485bd405e0fa9086e210e9b1eb083210c8e76f0a8dae8ea8e879815847a2a6e0e78b06e45af62485b5dc782552f45e6a73a839848efc6ebf39a46c06c3abb11fa354d45076082da215486869fc5f04cf203345ab22dd0749513828d5c3f0384a48294694687aa67141ed9e5a2faee8bdfb95bcf7fd431c46e8d47cf08b2703e64a078103984ab4e0873d066dc6ea5698f595c166b0e5d8c8e1e21854e6883485601dc2faa615aa346dc14dd56c15cc7bed552110bb25ef8ff508fcb466588a187949c93a2441b6d315076bf579518166e52d1975d9536b9279409325d9baf3e1e0ec8a3f4293e2ecac044539cb2874d97e13bae971a1827f03f9bb300810f14a3ef86cedb9bb8582e3aac7070bc8b3d1199f83295096ad98970e5af7f1c684fee53d76c2edb6e28aeb998dae30f83e556806364153df0b6e62e28926f72dfb57bbadae12e6bbf331664321a5ddcb8a1de41c60c5303ebd38730f28833b5272b95aa9c826e2eeab42a942f9c01c4cb516343307af50987f608d6460ccdd88ceff70ccd6839e64e6d64d0079958f06653f34710ffc339a9cdf0fb3423d3ffa9e27d0dad609c819e9ff9ce16f667bc81f3dc34fa58569bbaa76b83d848788aecaf862fabc4a797f5f05b2daa47b45ffa9445d21c91e43776acadc66757477aec48d07115763e594ab0d27317e6045890a31914982a252e2a48e21c0606e3e8a4504624f3ee58d0e66ff17c895f9e3b6884fbdab7845c066a7479cd2a187621b356434d0420fd0366484904d34402b796f413eba44de9d0ad1a32adebde1dde51f2e686bdc7c81b80204a07294ea29aa04ea8d191410820f987df576c47296a15bc6c8ac7a2f9b4555b79fdcd52713bc9586d0314991d1a563dae49b52c6aa551ecf23d5d301d1e2c62d183e60c2572f1547324307551902d79310ec1947535458cbb97a506d66fc20db5050bc80ad1daf4fbc0ad9800dd91565958f496e731aff66f7df68ff21fdb6942898fd438e279f21cb8ac93756a59326dfd095a82549bac2bc448078a66f285e96646310af50da2b9d3251461c60742ce15a0678108615dcec784ee1129ce176ae1f78c518a3e33c138d0761e1ab769ca3ddedb82f1776361ffee8925947dad6aa3a1e87cbc1a3b0e3a5f821d25f151424e65659f931482e58afe32c0feb3713de0c4e83f1358db303ac284a622848c6512a651d4e108f0998ac4c680c10f3cf3a305ef7b3504b21c0f0cfa1e599ad7840d5b4bd0d6e586785e29221ee70f058a74ba3b8cf1a86a5b67a631cb6188d38c3e4a428e822f579a68322702de803ffbafffecf68a91c684c0124de90e2964f8c93daf7eba677835068c9908b70380e0c0b02d6a35811db4ade8077913ab38a98aeec44029a2df8431a944895502a0cb4a821158c45a57830b7160a5e14e8cfa15493c6622a493c0f8ecbc2bf404be3cdea8d2720199cb7b6c0837cb59dc735af89792c7b87a5371ef547683fb09e32a51013db7d7db286943441abcc5e8a25b194649da69e314e3d06551847a35960448f8b78bfc529c7b9f55b16df5040ae2e18808a42985f3e935ee004a7cebdd0503cedfd8e74831daa9eeb5036b4661e0a222fbdc35211723fd6cea2e5782a430ebd57844ce2c2e6259f4f46f19b53e462718d10b07544170d08fed863aea589624c8480b708bb97885104d66cfdaa7af703cf42bc6eb8fc854744b8025326cca9685e05f82d4f3bb69a2a414368954bab1ab0b30c16b2547cac00147e0881d22a9f2452698db2c83beb594ecc86c13b5f4ae0aacd994e8c7aa6f3f42265274e4e64ed537efb19c79112685cb5fe79f13d2f11a630b14f221634d6e6e82957816ed71f0a2bf72470dec24e72d9d1c040c701728db3c0c4321ea0133310889a06ef2f1c42153fffbe59642ed5ae3fbd838b5cae93973dfdaf96a5a4cb3d1dd7a1602dd2444fdaf02674c0a082bb8b53f35da046c44298aa33233f50bc2af9e60561cc701f88745f07a22698c905981774a1d6eaa5c3c8a23801090aae1818262d0fccf4a51b5f074a4c2b44b5a1fe6562c3721790b2e4ed41930f728678f0a112c05a5b82cbba89bd9a2277ee30b9b57e90babe200828a808cde3fc55d7aa5e2622433717a327a36f84615bbd5e6d8a5850bf8cbb5cae89807e971025598c163fed33c30fd77989c1502a36cb4f9aa6770ecc36c48d726e316fe9dfe8521b2be7975f08613b69d00c3a0b049c593b7a2721898bf0c527c4e35324710a82e8db781ae12517a63c4b847c20bd932d499768c2e334edaf55adb5697a457bfc8e87069efa8687bf8db4d3b4187826fcaa48af8b65a3c6d45ac142ad526ee76453a5aa6e7626f15a2da9476a1a76b2388010ddb0703d1751d6450dd581f9d97b1cc443210d1f9788000208763b9017114d9f32a6749ceda32d3c6c37bc85159c06baa91507083c51c8bc8f43e512b1766f1819e29819f4a78ee0833f011fdea4446550a11406372c2349164b1e64d5b2a854e1a55ea8d31801829575e6d13df5c862464e5e685fb28d4cbf8614c931ca0cb5017867fb543dd4dba02360b3a8192adcf49189c35eaea131039e737798e0f2a9267ff87f7c07dbffa264751c69ea53eaff7cb79a04cd869ee460b70914459de5eb9c376b9eeac4ea5cbd8be836d684543a8e96c480a716e1bae83d13136191a195a6bd71bc25e0d27863e115e425026ce350843aebc085862984e68de3d54a2f51a33aaa96d575a84e0441f6cee7fa94619b37ab3601c074b9de0087a2447537754a848c82fac290051529655c003515bea409b382d54265073a24553ae8ea51057a2ba3bec610bc1873848b5d03a15b5405539e5ab9b30be1dd074136e87888ae016c8c9e56b80394ee3cd39cfca1debceff91d3c074e1ed224328e739b5d856c054d0ae9ed4e842c9f2e60a7914fa0d9a7d9e9d539172191d903aace5df89d4cc9b91448ad64d17e1012c4bc6e0766c45f188dce66747cf74a632ecbcb89110d91b828e4cd27737403ca20e83c508a973ef8b2ad709a6f16fe833d54dddc5c31f06ff67eb955457186042310fb12b87be6aadd17b600967e6d29a6a081084fb8353a73d3e46d86073cada0a701c70354e965ad939988c3bd2cb540b1a9e8107515380598e370fc0ee341e1205c66fcd277153f858dfa4ff1ff458828b59c09423ccc8d240a17675024403da1968f57011171830339ec382848e7fc0634486c1933b91d217c00a11841cfe27f08fb580a0292ae29f9003f591796c72e06a4ec093d7968a2b4291d5dd54459a73920b0426957561cb9c54d2767026a88d627b1cae9584b9198a847b323ea4d0e22cbf171bbd788b9567b6de566604966f7a409d17846bc0b30254a79632c23ae1faaae190c089fb543f26f54ff882b6302c4c75e972fa815296b53edd8c64010a8e9f99b636b46d3546f67597f7b5ad2a6b2ae93e0572b7128340b3e725879a78068752819c06d2239324746d355f723b79fc7318ae1ec950c0ff0afeb02e5316340dadc400f1e5d94dad2d11d1a28f334070654a8e8cac693b7b9e924d9808c787d50ab1c31de72148bba343ba0b891c364397e6e7a0511d7babfb5dc1c50c2ec5faa101b87117f01f3a20c6f18cbb050a48b70fedcec480e9e8f82812f9146a47406f8ca4185dd4271f5a06480fb24cc895ae9a9b42b7d4897c2a49c8c6e89c2821fcaedcedf20c2050929a95ad7d6186b6ab8375592a6562bd4555a41bc5dec4ee3d056bf5f4d44a261909f1f73cd15fa7fbe1d0dc2eb4f52ba4198a8411468c84a4897a2da0446eb77f00def597e3f295a177c4d9fc03ccb3d15757b43abaa517a9adeb0ffd554e5e497300ed21e5b89945aaace86f7f267f8a4a789f1555fbfa6716f32cfb7bb6710e2ae9db54bca6ae82797bc3e80a9f96f4687b98f067be81e457296d0f3dcc0c20f7a8f04d45d2a6e97bce1ebf7ec213b0ae5cd7d796ee72141496b408598aa4b784b67d32366a8263df2cb22b205d045d792a4fc832961fded7d77d94da2c24e790e09211b7e127e50f046b931f168ca2218c44d640c56bcb6cab2982d2eb03927ef141d4008572a264c2aefa7f8b89a8b631f2d18baae34eeb1463aabe5369ba1ae8a77a5f0678128c48856fafd847492752006be3ec3fdcfbb0f2c758244fdf7daaea0cf7c08e4355cdb269eab631e7908b45e55ca2e087f271f963aff5e451aac0c7067d693f6f636abd51f534d4e472702214a559eb080c33616b73dc1d50f8918570a9d08000d6d8bb9213fce5e5444de063f72091b7609459e8307bd8b8f4188238ff52b1e65d00c5e2765ffaec44d5c4bfe8c31d4fdac5b46a8a954d4e6c25fb18aeefebad8458a77cdcdab38a4e3e182ed2ada750909f8c92ff07738ca075600e3e936977d61cf64bade8823b46ada1c398e5edd508c236f7bd15bead1f9f6fac4a3afc599286bc389cf2039fb4dc608976789a226326238545ea6c44b27a6e80c4d113b856619215a2ccbb6930fe836ec1a8e62700dbb4a3b2859d72ef377004eb13dc3eea5b0117fd0adf01110ddb6b5fdf2e8c902a74a13f7caa71678ba222718cd00839da69a1f762799688b55c4a9eab1e6f119e247330859e40676db809e84714699b6c8eae06c2ce7b4c8fc5a61256556de396361717400406ba2a84515cce2b943e06f13ecfbca602dfd45404517272c6dacb1958fafac87f61b1660dd600bc07af433d4b934726885bf2e5d6b2fe40bdbaa59efb58eab8cf5f982efcd2e61a655a2822b01dfad07704f1f52f04995a5494d6a31fac897eed0f230486fc1fb1b0e680e6824c39b1991044303cdc22f266f278cea62ec11242e7d2a9b5c936dcd92c5751d8a1646a17a5804546926ac56d08ec98638fad3a419246c0a7471f4641e2b59623365f47e8e0ed3d634db6bba36b6de2e8d6e19d80a4b0b502523d2886cec1ca6830407c1d0afd4712d5169d4ec691810767615cc0a8633e867a30562a40f76480e8d1c16c49f95e4505af75e6f8b8520dba385260089b5ebf1baca34e2b43401c3ba97f085ee53a6376eb3bf7b86f160b18b2c495e8178712e57d221cf18ead3f623addca04bc71e0c359941c35b99b2da025e85e47aa53860280358472b03eb40a00d7dff085e91fd9ad5e75cf5db2ef738f8d654577c33244191a66743f105568501ea97c81e5cbc8e4f4199465096e1ef4b7603c73689a48ef4634dcf6e26f7ef833999fab8405a189aadf4e160118d145536b49e732b719fdee3cbae2d97573fdba02d1951a37028861f8fb4253522af28a75552379ec06b9ff90704fe00d32458bf9d1432172346892ce0a2d0f005963bce49e89399c07667020f1a1b355cb3f004077ab1482f1bbda83cfefdca3397c44fc86b240bd020bc1cdc8c66d76b1033196291496c861f48efb66754a5793187332509ab7f0eb96776ebf0f03fc40cb37716b8d26733f9af3940202e1f43a0fc1a28a03f95966277505464bf4b660270c9c43fb614d64c8b7ec03a9081909ae98dfcec3337ed2614c6551d14c7594f48696ca101c56b5f07e478c0ee1486425de3bd8c00872c61911bef0f10e64b9862b85562dc342813d15238e86d49bb05c912010dfe264ea8cf388588d66739d3c5b57ee71ec8873ec2d21b55e062ee461263b001bfa0c768bd5458a98bc6dd067091c6ed074e51143a413640e6500d5ffb4766c46cde5676fd184b3ff0e6c28159d9bc0ac7194f40687d320ab16360b0a9e2ada91f27aeddd936d54a59a92fae9d5bda8d840faf02298323a3ca971b21b39e84d009045721db6343577b656a424dc9e3c366c30a41e54e846570b311342f8236b54bbd0a60c780eb0006375def128d67ee0c793f4e14d67db33700bdef9a689ed32107964621b3216dd2398adc87d5de2f3340eb4547fc936f9661abae666d8f9e5ed72073a77bcfcd07b55d8dd5ed4305481ff2101616101a8c2b88f1451f654f3015b0e0139b48806761001b36223381cc12bb36381bd09ed1198c24c9f10ae9f2a1a4aceb7e41ed067fd761898066cd0d344e457e7d748611876da64deccc72f8ea009c1ef118de6f85510dc2e8971c5459bc84ce5c1e16d2e978d6302bec2af4180d9782c7d24e6d0672ca168cf3800573ac18674c9203479fa1c36da85484865b954df84a0fb85dc37a34a79a506cab2cbe71b4f07d82f14733f035bf81d090881377fd2f6e4c67eb5c8205a931258f36ba0b468b87f3a8b701acb945bb6190625836ccd84e0875bcd84470043e34dbbcc3342c5a8ebb4f893fcd2d88e23271b0cb64d6949ab1b80c0d6d1647c39e7b060c647af3fd2bdf22b6434e88aec9502bb07985e252d6ce96bec92b070dd34027c3400f98708846332c9a369e4ea3392e5dbb459cc1873fa59e5d4d5bdc7407203e16b842153440b14ab632241db3201266c4344a52a62f1048354ed27d14e35f183cfe5405a2f94fd23bce51a4b25430cad7eefa9e70ec905cf841ebb5e5292a73d12c6be9af412679543a7249946a6c27624b4af977e2164bd4bd285cb4c7896b17d132e262e0cc40a7ea1e38d681537f46f8db290ee78d23ac91903e999338a4e11359d0617e4d81c7e541d899f6e613eab6147ef284a81048142b7af4e8ceebd1b6b40ae660211d8ee3f70057a41f0746409e83a7cf1a7eafb9677e8244e8b70a54953c7528ecf5a1d674e717067a0ea583d0b63c8a518c50c66cca4f93f2b98ae17744ec71cf5029434b96815d916380927eaa05718d2f5471f50c343472e49b6f1a3d95dfefb936074fbf20fe26fae57a2945d1245a65b0b7cb111f5d0c38045a5a7aab8487d0963f58103b91ef31db2cd6321ed222ee0b31230a919a741de745a1800132e41a9091c2e2e16124e443f2c4b1a9086f0712027f570cc84e283ccd08cf723e1878639391142764c26258b4f10ce824243d8a98bcf21ea31a0fab249fd48fd1e24f3802d070b6fcf2186166d0e2bfc33b9f85e96a13e0eeb11905611c53c276614b7b17b356629551230d3064dd00d4a5983cd375530acad579d17d89cea3e0f31cbe1b1b4297b2c200e068f7eec5ede4d964d2133c461991472ceee34fbf668e5a322fcae4ca639266c6490084d4c80ce4c88d5ebdd9e0b29db7e17064e036b061ecf980d71af2864d199707afa2cca23c07564c7833bb84ed4f4cb7d72c05bba8db0993f7439664a908c629d23876f28744cfce50754ee5eac84798141f6414c429fb01f4bf6330215c562a63b32e4ea9ac7076096d4b71e68b5bd2a100d89cbbe0e04fb2388ae81543cd1f215698a2ffca967d7bdc3b03fad992935a20d03a4e078e231c8c290f5641c94eb2d5e39ce12f4f6e17ce69c51cfc816092de623291d2b0584e7a95f5db5400a4820300058068bea747ef7107b8ab0f4e1882c956521dcd0cf172217723ad09dd42dade6d4dee2da54c49064b09e6084c091e5bef3d2d6b1e0f65f3b8f42aeb1ea902939c2713e0616105241bdcb296754ff32419350e2bd830dec65ffe9b6f5bb46a5118160953f72f6cb698c650d6a7f429185c60fadc11e69ea6c1f944061898be9f9030fda63d340de76e86e967ae03922dfadb0eb6034ac59bae81fb2905b03f3dd978d39f2b80dde694e0308b643384a9cf6a6898e2acc22e4cbb9998106481731413021f9c8760fab3dd2dd2918d0bdcddbd8c182d731569fab9bb7bbe67f7f9fed1bfadb5eed6bdbbbd1bc7451b0a5214a3fe7ed772fb3ef1b00612367b35a32ab529feca110d964f6a88ac6d19f5221c9293a4ba73efdcb7e0af23725eecf6fdf15b685992b1ca2fdddc5f72c203bbbf4ec9e365e2f2507ee8a1fc09e6f060fd5366d89c24524a29651561e06ce534d33f8972b4c0f16de29c3d3b1ce2a13f93bc7d8e874cfa3726fedaaed7bac596ff769978e8efc53e8e3847da0ab83f2709931c26d7157ae891967198f82b1749928b4c918b60ff223538d2aa2002730233a60d868eb0ea694345d8632dc079085ea18bbe85cded1384fb71c0208e342578c04359700f1c692948c2606a900503004730658cb01268709635fc8a34256a645c1a1d11845b9ac1fd318261b3cc5eabb52c4ba26cb7d492a3301e46fc38fbec08f709f2694d92f1b06551cd67b89febc266599345b2e641fea2770ed9ec420daef93e92b00622b6fa89c04774d2123210e7fee981c2e70a2cb85ff083fbfb27def4d71dd8dc3f3fd148c45be64bf0dba7af386c5e27e3adf68e75f4ebb56d661f67e9353442d88b3ade9216bb8726619396bd86fbd1e0f859d6a6941c0e4541598a70019a221c70a469118473c5530b1e26579a5b6c8eb4cf49c2e437d37f38739ac013383dfcba1a69a9f45e2a3d0be7897730c7adb66b71aec1b5aee50a362d4d848b40ca6608e811f23ddc86e03c558d457f352b0f41e22df93d9798304ebc914074b54357a97aff87612caaae773f8e6cc9cf710ed8ed08690bc35f3926c9e6cbaf124ba70d897a5e74f4232b47a4d217b6eb554b1cbbf4e0f82a4cc06135d29288381ee770faf81067ad8b13384ee9f1e3dbf803c78fffa38f348ef1597dc43f92c1201dc33ecee10d1cc1749902c72e3887383a9813e000c78834830b6759432003315a80bd769491e5a1bf2bb6fc6b6797bb5633d895131e583ebd2dc87f1c71c459de5593c7fd8ffbe689fb863f717e96c378629b17356091032cb117e10c4cdc8267d77c14a20ec6700108742822e3cb0862f3be6cddc0392eff864b07167ae822a34117646466b2f0c2c434ba0202209ed842348b58603f634516475b92e8210328e65734010220b48c2183a0313ec025568b12e61fbff6914692831e7a622b28fef2e5b532001e5accef4eac8bed1e4b27222e78421683c1bb015d5785cd2c67525b7a315e0fa28bac2c9d0a5b04cf207000020dee9fd1f6d838c6873070e9394ece49bfa391d2d722b8eb4a1e0b5276dbcdf2733cfa577f9ba54fa79b7809941205fff6f533818526f3fbf65d614c1059706f200b1e7a18d1a8ef0a1ed62ba5316d4c6fe3e0e1779c1b39190f738c61b32361f733fecadc9bdedf93fce5ef48d148f7fe4e463452dfbfaba01414d8818deb6742fdd277d182c0c6313ea0c1a5afdfb9576bd7571acb4f061a73b7b37848bb7d3cf4cf81868d1ed61a2c7f057f6d94523a5f822ac868144b42d87e2933119174860cfbd6d4b3f19a5a73c375774723ad2ef480fc3842da7cb6baf9f8a0b50f712ead85362561ff6067e2b8b96d5b57e3d3999b8d07d678d8db76da5ac27ee3b84f7d3a13c57117046b77e0b0c9a16a158e897bc70d1078f8c85663c1fd79c35225ed867a8a3ad601f9d683db632c7e3a4f8e90dea346f4888641062d839ee1b8ef4ca0c41b78668264e04ee2904cdc9bb89f1c47c3e0b251eada1edd993a9627632de93fd5073a6c4cefddf781cddb7cf0aa88bd1ad004fa28f2ba547fae51632aa235e998f3146eef28e729c69b0ea80bea84e28d114dc49f03bb99879e8916d580742d7f653bbff5e970e157c20eee6eacaa69ec87e04d221dd010174e8d95ababba482b5d1cc71d50070476403e64e60373f7274871d775c1fe350d6b81897307845d9eb63b6536d74c50e26d6e6007d40579cb6b22dd4ef34a978bd00eeb8064cbbb59a9033677403729198e3d9a03c016d4286ecfad68b09d914361b7e72abd5be6f794df75ac1ac9bec2cc189df494e7a18d87424870c61b9b838d60a6f8c9912e7446730611c64c742e3e33235b79d0f90a5438b0fd5e171fb43fd838c60b0e63bc64c1718c171c70ae63bcf0e03886893338cf314c808153388e61a20837ad450d74174166075dbb9c74e36ac9d479df298a34b0ca7e31a2ac083a3156e5a53e774fd508e2a961a3868da02b1d11366a6cd46c445f969a1b35376815a70fdcc07103c796058e1c387204bd6088ce6ae98c1caa1caac9139484fdbda0a3202eec2786ca33994c26d50740152583b621850eeffbbe4fc7aa468995cdcac6081b58b5345f2364546b94a8089b1d363b84cc089d99a9542ab50304a06e04366cd8b001028fa17ae3c68d1b3c3e8d1c3972e478b0a403b08617b7c7ed6144b559f4f056abd5aa07cba84e2b583e583eb61a260b7c78ede587c783070f1e3f80802008028958260a88feaeb482b4827099795a369fa0a1f9538308092264c8081dea878690214284ccda1683bdc1909b2143383231d89b106e4208fae1e2c50cb60881480821cc5a9225e2224204890badba3c2142840871b984866a0f227837373737228820c4533f9f96210045021080128fc745073572504400458ae0489bf1601b4602780940005e0f9ec59136f34015aaa5f1c19817ecf57530afc1784496190c268e30b32e3322bc3a01c381c184b0481c69425766ed6b8ac0190107a78833387b91c6c506441b8e342e314c1f46f07a07c7910555f02065e238c2089f0e8d37fe0dce7833443b420438261c6947f4b866493324abc386c0469a114634238ab0cf6844be3f8d46a2ca880783c160468ce0406384114618e1c8911b3538f2e9e0c8822a7eb0cac3239f94898f7c3af1c6ffc8b704c9872388c48ac0469a1144b3148dd468e43a49a34b97cd143469c98c49bbd4263563721ca593d6263543891ee88cce66dd333aa3b319a702235e28c74d8ea374721ca593e3289d1c47698dabdb068212c0dd7d6b68d4d0a8a15143a3bb4b0f5a5ca1c4a425d3464ddb6cb66da61b6869bb61ce289d4ad07903c7713cf41014c4d59a049704c72541398ecedaa441471cc1d5ca8d8186194a4cdaa536a9199376a94d6a06000010aebbe5e6898021dcdcd310107c21e07f1090c7b740103e04c10702feff0079bc0f10846781e0f700ff2fc8e32d08c2a740f09e661ef6a3c0bfa72e3cee6908847b6ac2c3fe1de0f736a0f72bf07b1da0f727f0bb279a774f44b2d57f0334e10fc0ed051c88e20c56819c143d304002e700a50e1c20c201c6016e378041a201a6b9e00a8e347ce3fbc63d01794800ec345c63038c58e52f4e8afd140af552a29638b69f722ce54b0ecb4f8152aa602eef600e054ae130f7fdd6828e7214ea4e71cc711c90bf4e3309f3be08f76908f79f68b84f44a719accb70c4d903a21f18ca56bf07ce34363ff666b2d56ff268352b1d3381310ada0c7c64b112286315dc3cf0e3016d44d8c7f93da01e6ebb2c6ff5cffb3ea44105f68d07fb687080fd666f08b707b46386630f18b81d56efa060983b9d9ca718139decc1f6470e080bfdae468d0b836a4a5661293b2965150f8d3ed6dad520e947f939c43cfe7237723772378ae221fd0dace221fd746457c543cf6bb2ca263f8a043921ec140fe9cb5ba5cbd8a7a9cf2ad3677bda6eb6b7cb7818d2af71453d993e45bb8cbfc834918479edbd16bf4d0b9b9b2847182b3f7bcd08739fdda88fe20dfd0692b026922dfa37db0fd8f9d98d307d79732c72233347fef29ebe23754fbfa72f65fe323dfdee034bef81a5df4c5ba9c97839c4b9893095d5b4029b5bd632ea92baa42e298dd1afe22fca85a47dde9540053d4d24794983638d4e8665d1d75444f108a75aab9232d992cf0216c7d2d3a38a4731092755191eb2b99352d0466570964958caaf8dc5d423611548b6e4cb8894d4b2eee16844a59f229983cd1c0d0b116fe43758022212913f9e179b4b3f587e0592dddd37c723202c3f7367482fb692fa48472268c4579af8a22f935ae2b8713adc1338b352803dd6c94a267019ee50b4bb7a386936ced9b91f5c35993219c513c7af49cb648ee3d724d2a4e7afbe755bc25a7f1315d6ffb38107cbe9311664ab25cc6531976109c45632d192896ef3574ea4e46ce04c255a2b97024a671b1969c54b081af68eae0fb8c98b628c22614c7a8963f95d0e9605099331fa2b27b305ec45feeade0cb6c0c45e84c2dd6d1aad7ddaa7b68f4faedd4413f1f7263007561fc242fb21c2eeb5592c83734f518489c00ee321ad893ed26022117f5f41bcf1cffd85050993b1f89d74e9a72da5ef2f858e95ae151862f53a906cf97f4a781850e1292a2c3d2625a6d9360a4e9adca1722bd359c06538d6c0882752956f72fcfdd850c25e28bd7cd37fec052128a597ff02f777655ac5bb4261fbfa29fc668a496f8a6173c5f24340c7b855fc298d57de43e1bef4df37aee02afe0b42ddbf507aee5f10fa2e94f9f55f58c5bbf22e94d27377156d77a1d49fffc2bcab78df43d3c7ea9b766057dd731f63ab8efb144acfdd1784a0c8afffc2fcd2a7509f7beebe20bf7e8c99b89730ee4d1f6b4544a31b33365b8a2d7a4094c6872466b159c64b0df0c4f35512f6f910f4c31564d890829fd8dc02cf0fbd8881e70f714998044ae0c310134368e10197d87c1c7f4518e0f93964e0f949fc55c2201046e8a0e50668d45083d8fc27fe52e10c3cff01fea2781a8538d2bc7cf11214032b582f61932693c9b4c454aa5a5938c657ffe98797c11721bf3686f22b58634b4ab1a2a4622955f0ac957ee7948b18c7398a51d2479c9081f6a50c237aa23140ff0ed63f36fdfe159a1e558f2b4ca0bf7dfc95c5617d85d6efc21233dc32dc1d917047344bc0f89975c93b1d34c31d0647dc1fdb7cac5f7b6b2ca369928bee028d5348d10cf69f000b1c08ea6c5e0d568a471e3407b07fd780bde25513fafdbd832d109f7e448a4438e2ac62e128a690c4e8bee8ef0f058f394f0c1efac6a2b4293db58c2fa34f29a594d77d49bb9f6abcf12e8c875d6c26ba1d0d8d8dfe435d8dde208d31bac8e289a126c618638c71ce49279d74d6e07927d78505ab8051d2d8dc32598cb228bb2b1aec8f18d48a8ac387234d0562d49e15c37f97cbe5125333da82bac1068e608ab0c224064c1138cca735721cc7d55ab94ae76f9556bad1597ba202147f457f2d3812d6efff850b5946c7196cac55f97ddde5f596758fbbf3e86cfedde32cace0a17ffbd02162e0cab88a6a8d2be3ba41caae2ea90232604e2005ce951669b853f2dbf5341c552a6ba5a4e13afb7bde48fb72d49208e9793558301126bf87a0c0f20a2c5f80e50fe11e9a80bfeae71d1cfb88cb4ab6717ff5fdd3b1c0b66d31c8bc68d99680a185f65013fe8ab42586b0fc3ef2d71987f50c3fcd84192c651761d9b43172892c641361f93137192cbdbbcd8b7da493de7edabe74f35790035d4040c7a8aa6bd0b6a47008c7d52f4538ab7295617401c51929a5ec963d5b764b292547b4e108860834f3888e26a3097b3b7413d8bf9b5841c23a963b087b77511da1364cc3e01c8f82bad019a678051f3bcb685a0d1ee3cd1b372937b96dd2f8ef1a3264c8109a0ace707f69d4f547d65683e590a2e4d4a8f68bf15920b248f26bed1f09abdeea9f290eb3f1fe51fc55f3fe5d8d4f3dad01a37868e34ef11a377a2be56f6faea8d3cdcd83fdbf9badb7ba7ba3b7e2cd7d05fb6f3f96db906cfc5c673b579a5bd43881114c3ccb558452ca954a714a7a71ded5fdfd341775b9523a9831cb78a9542a95be4d2653a964bab9f4a6ef4a2693a9d42da594b2069bcc261ce20a433c2482afb281cd61566db2301ad7e022aef7df92b0d9e52ae2b09a98cc7107dfc35006b63fd65adbccec87f68325e9a8e7c88836abcd807a92b02421d56846b421a31a528dc8a886542332aa21d5888c6a483522a31a528dc8a88654234a3aea3932a2cd6a33a09e242c4948359a116d084b0f504fcb8c6a4835a276975235e9b6715cada6cef3beef7452e1a85438aa5085a3b241781567954ac2aa7fe173e567d36267b5354a2973dd6c5324245bab576db51bd08665f3d97eb20acbed074bf9db8fbf7205f2d72bdb202cb72c5b167f458a44a1307a96ab48c409dfe5e19330449d2465864b052228fe8a65b410b5132728a20b2323e2c729a0ab4895183f4a04abc4cf1129fe140f65ecfe2855ba4ab7fcbeb97e9528e09488147f8abfe2cb8fd2318a84752cc618bf63475ba6b7de7e90a58f3c34b5e48b294cb796c0196cbfdf271e4ad9b2651e768f87b2c75f7e6565cc30840d3cc8e0882d8e9cd0e1c8ec809be0632949074424d1832b484fe4f085498c97baa891a25434df4ba5927337d7ed664b6f6c56ea52ccce0f53c000892d4040c31960c41ae0acaaa935aaa589115aec600c1b76e8811934e8be7841c2463ced0f463b3378e04a0b84d881192f9c30e3b1205b898477fa610362476602256800860c8d25d2f0827ed9c195f036b085180e74d9018d2b347481ba610bc246fd120315a26b3ce18205416690f982c5082e8ac4338cb407615537be3ec246dcd4075b3381227408010eb0f0a1c6c5a8089634c0eed253d0a5624682a7cb7fb9bb10ddcde0082da8a8c1182d65d46640ad20c39dcb9368a4f11431573cb3adc15985270c786084c50b145e948087587e3c658073113c3f89c3a2e348589c3f5dfe2ac24176ce5a858d55ab7a59ebc07419eed8828fc02bf6978da6abfe8af16b6fdfd3d2da51b0d2ef9d0983bb93a08b31c2acbb048768f75b238ebed3de2de8aabffa6b84712fbfe3b87770be9cbfdda98a373d6fbd30f40e85a16577316d6a32921faca922c1a69fa669fa3cbf4a93c9f4f53b13583d34999ede68bad264bab974ef87d33571e6cf49bd50991f9f7efc28a594dc6bfb1c7f8b925e29af4cf979ce397bfe9c93075a9cdb28b7d128b7f62d5d8cc5d9ed9861cfdbb275d7789efd1c47a1dedda5d7165ca8c1a977a106735bb6af5ff0c4804a93264d9ad8765b483df72814ea66175cc0a9ef2528d4f7a783ca32053646bd045b909f0223eee2cd5d93021bfb2751b2d34414ea66ffe92d70df18f540ddf4e29742fd550af105faf163ec03bd9fdf09f2c6116ca48109eafb96ebc14a25f2aada3f04c4f7be630fe9c5364e7dfc746cbcb1dff5e7a4b3d223efa01ed5b5d703c8296209c47ad6d3c03481e5b3be938d5b098bf291a007e4cd3cdc7cb62b4b36af069b3da00d47231b4e851266f3abefef66de150f83b01e96ebf9782865d6e6b3e793faf0e5b358dfeff94898958ed9dc15ea63ccc606ac33d8c767d4f090150d96f5a8f7f1ddf6e9f8f8d457c20e667dfc4cf8e1036c3c3f27f259ac67dd167ed0945cb150de0fee5fb1ae12c72cf95e4dc278e42bd29c40d3f80322afc82b038b5a441a1834b8df1b62a1a4f7e3afecf968f1578c01ee6fcfa7e5f3a9a0e357df5f0aa65fdded47b62edeb0aca07cbffad5b5b261f99d986cc2bef0bd8ecf1e50eaf3e6d3df40398e0281e800572978fffd775fa0ef7d8cc94f89634fe66177b1b096352d098c0f3ec2dcdfa71561f95d121a14d8257d3af276683c94a54f475e0fe8062b5bbee96b3265320b4cb118f51d894c2ba6af89c43fbe6b29320d1a2cbffb9ab80c4b911660fd948a3bde380cdda7f3e33bcff3cfc9ed18a29e7e4d26fef1de77c2c4acefa427c10c38667d17a54779f2f139eee34a91987563bc417d97e4afdc1d1a7ffdb87907a340205ffa74e47f608c75b42345a1f197bcfd5d92bf803c2b0808b892df58fe095cc9bbf9c4b62bdeeadf82ac37bb1e9087d2061bded4670f68c69b20374f6cbf3b791ef8759ef78131d69ffdce035729c4b752df7bef5af95e56fa17e8f7a4e4ef81f1bff8f1937281c6f26bf440a17af4b8d9c3a8af1ea23e7e3d3efaabc7453dea76481eb61289379f78d3efd1605bc8670fa8b1b0ee017941feb2f7e7f75f7f8f5130c684802d30573c04ccf66b40fcfaf2d39157e561ac93efa012eee545bd47e1debfe6e5a3c0ef4f09f736fe0474e3fbfdc6670f888ccd158f0cbda2638a1ddf3785bf320efeca61887678359b9ecfc7fb88fc95b79c6afeca5be8a9c75f79cbeae53dd13cf4f157dea2e3fd9e8880a07cbf179480eff76608f87eaf0b0adfef0d0df97eaf0921dfefd1827cbf17a6f5fd5e91bf72009ec9f77b65fc9501f04bbeb3923f1599f157d6f95319efe8c9f77b619cf0fd1ed201bedf3b63c2f77b491e1a944cf238167fe5027c922cfecaf8633f395a0800544290939901ba34196235d1e3fb3fdafdfe2f8cfdfeafc85f79004fc2f77f65fc958f3c92efff8c8c7cff676684efff8efc95717cff1786bf728eefff90fc9555dfff9df157fee0fbbf247f65f0c3efffd0f82bff03f9fe93cc5f99c7fff8fe138fbf3208efe3fb4f58fc95618ff3fda72cfeca02f8d7f79f7efc955d2fc2f79fb4f82b87f044beff5493302b3356e4fb4f4037c088bbf8ddb87907dff820e01009bc045310f80bb21cf03fc010a4c231e085806f23e4756c985c2a3508142f01374b1b96909b13f0416c98fc8f4bc5dae8fc6045e0da7c4b0242765c1ea11cfe924ba566d62b5182809b83a0d05272a9d8e7110a72f3122e0137a3f04a2e95cae46625d586c9920f621324c8cd43be657365e0110ad27a1e21146ede62f34c2e15abe4669d1d4a749e4768c9cd411e01436e6e3d0a2f24023b5875c78f9bfa213b845c2ade8e2142b6ecf81f1160edf81b01264b9e472875f39087226f16f209781488f208907f7c924ba57aace7117ae29d7033eaa59792a1f7e4591e014e607dea01a89b532fbd77085c197884503fc35f1ea19ce463974acde1e7e4c84799e01efb9c4bc57ecf23246f4ef27ea970d20ff039974a7d72734ef54c48f2d293f2e6fbee497f1ea103dcbcc57b13be1cef0970a9d81c02c83f80bf098f7ac08ddd9ce44ff8c2fac57eb0bed4cdf7515f0af53c42496edef27dec01e1f73f1e9024f63c42ac9b53ffe4de8cfa13be07d8835e1ea1ac803cc29370a9541a3e8f50094e6eeec1ba24d0116e4ee1433a8293f0590de87133eb2f7deb801ea7cb23944bc8482a0906b8f97e8f26d6927033096f04c9f308dd9b9b700a7823974a4de16623951aa0c95f7aafa5d73e8f90026ede42df002723f447381919e179849adc7cdf00f69b7c8f0694f04e4e24d4939313ebe61fdfe3c4eaf13c4225dcbce5f44e1a40c2e99134800424cf2314deccfa126eeef14e1ec88d0e22974aad0ac001f24347f8630138978ad5a18202c24b85c33787ff43c7fb00a970780b0380ac2e8f504e21bf5e844ba5e28800e4031f3e705e37e3bc08978a5dbd7edcace216f01fe820f22a5c2a55c707aaffa1e3c70f1f3a7ef8781ea105dcbc45c78bb022a2e33f588940e479845437ff78958fffe0815c2a1cde729323858ab7ac70ea4a01383ec72abc39c703b9543c7c65e0110a813c8f500a376f59bd026e706eded2f3b1d5bf2e158b65e011c2793d8f108e9bc3c791e3662050128000149e9c7000134a706280264cc000fc121000af0474bd0e18c227010b100371c78a7c0ee87a0280213c09e0009080473c56e48d80ae1f010ce17140d80b148015192b72798402704f350f1b00f7240218d28793dfac8b2e73e8501ddd6527d1a9ac87ae272edcdded2eb8700fc49c200b929850ad364e3dee3f8c6ffbce2fe6d060fdabac1852153e8634d20760d964aa54d6c65e41a6a06b1b619b45715bff9cf3fd731c9cfec577468b1af0c59186021e3906e884fb2d19b8bfc65f2a2c615eeb57d95481460b2bb0e8b07c6b450cb2a8cd065013238b2b68587e117fe120c9f14161f94c7ec016cb7fc205f7c0f25578c00a58e01b583e0b587c81e5bb10a5082c02965f258b3258be157f4d2cbf65f29bc75fb1270b231cfc15c9f890e972c55fd105586ec032fef82bd25ae00596df5afc75c2403846233d51d0d872a9e12f6977964063045886bc4441c3171d9c2c0fbec43a55fe6adb5f7aaa204a03cc17517c7126e513cb48b09c024d0e0e9bb11ab17f8db01caf517e2186071a8e6274386384a3981d8cb02345237d44bce19e7b0e47c2a484f9f8f8ac1e9fff7762bfa2f7090ff8f777623cde0718ddc7fbfb906176e1ec48407236e448591655b063591e612e4b2447628156b6b8ef01760ee69e757439731ba88f17742450ca401e60942dd5ec4248168b31a1b6a1021b63f79fc816f76f45088a3ff85684a0d4f7b72204e5bf72cfe35a1182c2e3efb592235bdcf378f05a1182023e8f6bc5255bdc83d7af957aadbc6c71ff37ca16d27f74d58f311edcaf7a6c8cf9c7188fbb02e1aee021f71f9290164c78f191c9622cc816c73d15c33e8726079c2d0673022e383b5215d9e2be82dc6f47316905c5bfbe7fbddcf7d7b378c37d0d1a2cf71348c2bc074df8703b6e9e3f98b359dd3c7d30a7e3e6d983b91ed9e2fe839ba70c73ef481256a3560233b2181763dc4b99ea22e5e0cef4c08c2964311c37cb9f1b374ba09a9be5ccc6cd72a8c6cd9296ba391ae1ec4898138345143eb298bd5916610e75b334c2dc1564b008238b9d6e964747a8408216fb6e9648decd320973df3d12e648b2c57d77f39b6e56956eb6b272b1890386dee989dad2c91cecfc50a8dbefc4b66bc5f4f4ad94befecec67d7c1358028b70054bbf13b342b4f2bb42a13e7d28955e2b427ea1d02f5dd7cb967c2bf2eb95bf13db8989b1dc5d12712adec88fb118dbae4b553462e38da468d8798d5a32d1554eaa6a95744a221a070adbefb43b8735c650b6e29b88b0fd2ea594d565fda2ab9cfb46e7cbdddd638c305bdbbfca56fc76ffae5faa2895a8d4ca95a8d48dcea753362dc556aa0214ecd8aa930af6ef2f7d4d50a652efdc090537e99c5e2a957660f4025ba38ad3072cca890ed894115498e0a4026bc29146050f3ad889238d8a12f850e143450fc76d743e6b0a2c38bf2b8b6a1f5757d5618833d22081184e7c51410e70b648d0e060fe29d6a21883fdbf5a514822113ea3e67d3a1306510c0162ff3805f6efacaa8406162084d8010c62900128568629aaba76958ce69b3e9d49b4050cf6af4447554091028c03fb971ec7152e3a70b10514445590d1a1e9b162f3c04facdf25611eebc791b0ae351ed3057616e878c5e224349f6b26c26a801e72208a010469684b20d53821031d730311ec3fa90d3694b068d42b3002cb9ff2ab0c58be27df867901965f436301172cdf46fecb8065182c6962a0c04f60e9fa910107199860891e5e101b43da08e59732fa8fb08bc176ce8006592031039f349a88e58aa54a4679b30bd6b365d81c86ae1560ffe92f1f12566335636a7deed3913f4b73f28060c630b4e5200229a34f986006ef00d28c45f4151e2c32d72794c0219a6c8cc148b6a2554de15367ac51d9d81a294a80b3fd5ac241885538c630b35402271916c48f7d38828a2f8e00a306246438db3701122500c3aa70a42111f4b65493c9643294c2e0d29558697ec9718955baa10782bc15bbd4266c186f450f4cf1217e09b3ef92c964314c248af01c416cf68fb7225085e96099792be26c754aa8256c0d1c6948889153054db69e150e91224b8378e248f32206573bd0455656cd307daf06cb61d1af9679c87de6b4c0f439241ad65c1167842977c445c13d81e9c7e698c0f439fadc11d148d78fdf327f713d1dd2e780fa48e67a642ad516affcbc8fed9d40ef3b0f6ca3ee363e81a00ab2452bf8811daba0630f04631c90acf58987f44b4936c6e66f49b518d723612db3aa2d49c2a2e42da9f6235bf44d6d32958280c09f2772f0b19823c21469c8f4440e6370e6ca5853a97dfc952312167fe5fee170f0d70e31b81e4cc3c099cbd20448cea29188e96f48f3a92bb6e8d77aa94bf5b24583b64f47d2ff286ce680382049370e08530e8803f2d7c4f4b9227f9971589482eb42ab01658e9b1165ae0c3784e9576ee37afcd513e4af78032c4b60e5ab669b7f5bbaad88876e2dc76486e32158c4c352ed5c92e62a521fac493cf4fab95e26372776cc5d0a8b8262b72fe2ae0d4ce22d174e0db2a51bdef7307eae98e37e761454591258f9d9d2377d079a6ef6e414ccbde082b7657b6afd3561db2de22d7f9fd83584a3a291fec945e72f7bb06ec3c685a1268987b2d69f739a4c3aa6d357d0851a3c4d395f44d3cd159bb1f1b612d6f45365832dbdf71d96aebd5abb131877b0a7b71f4fd7c375e27a73eaa387d79eb0fdfc4e7ab1119b2caef11577ee758df7af09fd796b70637c3addcf326cb6b834ef6a7ec42b25b8b1d77913e46afd69a391886bad41d85ae7cf68e4d39978b630c1dcd8a6c1523a0d3ed9d35d3531fde997986adc9cba2ed4607bf3fc0c343e3dea77f0e9271831eae4f9d77d67fa2e983befe0fa26304e0f6b5dcd8f1e524cc1d5bc4fac6c398e9f92fef8b024de929fe3a1b4d27a890760a8f8c13d700433c50c77a8c81343f46ee03fe9cff01d18db42fffcbe2d94bede164abfc9d8efddecc2145cda66edfefa136ca17fdbba9b486c2279031688f126e2067309d7effe25a5377d9f3e85fa6c53a9ef2d8ebfca9593a46798f9db9cc1fe3fc43ff5a19accf9b58f443cbf7e9f3dfc7d3ffdd5fdf77d39d8efbbd5c9f7a82df625b83a35b83a5d259d27ee9f1f653741bd1499268d9427a02eeed3e9fd1473dc03d6fb6cedf7e70475b7f43cb8427d63d477351eae505b90555d2b6fb0fdde676b8214d9fd0746d9f2148fac578a374d64be04b72652bbf439b204d6ef298db094abf9b3ef9489b9f76f09b772d2ab79636cfac4db10f6310f7860b10986de91f48402678113354073658610081dd9a0c6094c1dc0eeaec55f269375c0db228b0972b822a48411183b45049c27a8040c98c9829e7588211200000010007315002028180e89c52291481ae781de0f14800e82964a60521808a4418ea3308a628c22c40042103000206288323425b40100e1638228db63aa5828a5fe3b8083c17488ac9950ff66c505743773eddfd4c54076044ed45a5057ba48ebb6bae44a4abd1c2cbd98868bad9e06366fcb2ecd42000b3b665508106917a58a38018a3274ff234de6f4aa6834cfb95c8877dd18e4caf9722195b46e8d6d77ec0c72319851a760edc3b5569acb460304a1ec80d39332d70e9f6586b36a1d76c6467cde6b866070b06aa8c67cab3aa4ed1870a50117a01114c99cf59827a9c88e7b29cf9e0964ac0f0cab1a6dd4c594e8378fdce657fe1bfb15086387d2987febb7e834bb1e49001817e1412b7b38f5ef13da62a61eb1872dbff148c7f8afaa2c86ed446a6d3e83f82eacbfa0a6ebdb93f0492301a51471fe26ce80b1f0da184fe877570c752452a63e64f7caef4889ea5d7efa36c3339814bf9375c16500b21db6216d483cbc0f175306827a209d752c7f5dd7bbaa71550e787ac615e7e007b0de0f5206c2d68c21a4bbcb6b6384c42abc170c0099eb2b96b7433a132d329fc62981cc9fe776c8ea9c65234e4b4188e99963ffbdb05100e23f3e91a31ff2205fa292457af44d57f3c687f4274b8293ccab65f8513f7d8e0f68764269535b0450442d15f930df154996cef663e1e2c67e2d6a8884dc1f9bddb7cd22051890456442e85b20a22aea09a154f3f0fd2381ebc6f110f9591445c54b6e38a2a860cb157405f0d5d8dd928c90d1a1ee79cd8bc3ec09d92639071f15bfd3d30bf08f9642bc9127e6ca205a353759a3c0c638c10b52f6ccce1e565e3d29eb98becca16c7ab168161eba02da9d49dabc4a914deb2270d265074528ada6deb6063d54126d9126b57c7a145d7088ca875e2a919c567a2867c59c19f8806d625079b3564a7cd6d17a25e4863c350546167bf04edd2bbb5a3913d3872abc3105013e59b79697bc45496f836f6074e406bf4f29fb6b6d81642ea776d230af85d1227a76e3df7eb04c5ca69dec298b73e0e637d166a29716a91e08672843c0bc8f363919ce2d75b3c613fd1f4ae2586c7479bbb862cbfc8245a6cf1ccc32f879c35465b72a067be839c72c413e274da67a50e59724660c7a2ecd31150866d85cbdc16ab727687b06a779cd03167b7b9a95363a9bd1ecbd024bda5453391a46e83dc28410221298e40fe5ce13d2a2b9fac6a9d9798627713801104d4745e46f2b6c70b607b5ebb5e90a1c3dcc014cb3d3af1e35f70122a0ba83f4d1f49fa89491568f0483a5dc9794af09ee4e1d1694b652d03a550d7bd3704a1b46029da034842859fb2ef261cc6e7668ff4af77bfd7c75b9565cc2e55c3a39031987ba97cae077a4318795826e8f8b2f40ab21ccc1aa1f611fa5840765140933c0ce6ab49ab6d172009a5459a086520394b154b597aa0791d3064607e85020ab7c2521a0b6e1b3c46dcf52fca9d5cc4a188ea10442e646ec30171791c27a2db4fc686fbf4f22565680c068ade2926a0d22a0fe3f4e7ca46b4e7a8c45f7075b7e031199de3b6e30bccb55abfd32151d8059f618e9bfd65e4fee90f1688b3333e2efcba81b9ebb2ecc23203a36672165703a9f02e3d1803a382d1a650c22b92271baec7997eda50f5a1d68bba1827a5e12f468e53263f6bb50100b10ec1cd2efbe4d386884254a6001aca8b7fd2c6ec8c5121d1cf1e96fcd029e1c676bb01b016d4aea06f46c9de74235e5e369b1417c6487bc47041764f03b78328ca5cc4516e22370f22f2c1e1cbc9b8a1066009ef0a1ee05f5f898789ef94d02a28b304ef034885d377f249f3501c851c7900dd9c423f2b2070f076041ce926574829fcf3c803b1d1230327c19ef6d2a77e1526606768dd06d5d20dac19c6f88209288439365a07330cb8981dad962dd12cf680da65d9d8827ce7f6a80aa170e04c5114d7f19944c8070a9b88593c57e1cfa91e911b50a84d1f05f042f8245aad487be62189fbb19dee1c201749961ad54f51ebc635fec06fe1c98e8f0094c2da8c4256a348adc78844e3f5aa9e184e975f38825fcc1a382dd3a22aeb1088348715f33c90f95d6c4c1b1a7cfc703ca870067723c4e250bec4766dac3af560af9ff2bf617340ea706fc6e46a3bc19683c90025e032401301153625b1180d3ea64791817e8259490d933096fe7979af729a3654a9a4d7cf152eda7a10b11aaba6fdef46f24ef5867db48d827426eb3db00309888088dc2bcac27c521c674ead2ed26d974c2d6d5638aff435b3a28fc204c43f20a7b532c00d1139136145ed94197970cfd8569f67f1add390a252830f557a97f673d94cbdd4c141ae1dee36c9e6b6e9e91277f758e46372d912328a7110515634b7998b25469ee0621eec522bbc509ea86963dca7913a937e80a51643c5300d206352995b4bf96e7fc6286745cb0eff02a9112aefb47bf2db285c192f5c72445427fa074085e3184e464193328527ae89a9652d9847212b92a26a559094578b960bb51362d4a8e0a56fb3ced0d9325c2fe37262f9e509f36ef09ac0e5598fa3d9a4e7ee79db583c6dcb8eebe87c81ec72dbee01f339fc458f76a54ddc2bee4b44ed88c1eaebadb0edb28d06e3f4b2f315df2aa7fb3f7023c7cb5092f07c6de58a29cc752072515657bacc64eeab355eec7e50a1a59daf03f5e07b228f1e22100cf3a6237158671017307c0631a1985d76666ece99851712134b18cc40044594b970ba54e910bfeb63709cbd7cd92611b5ffeb533a68c017778e7a254ed62fa5dcde569501177c0cb1d61f01bf5c4f7b2d260ae63f5f1a6158b8cac78433a55cbb593abcc3bfb18838b38e6557588ca6a6d5dd8149ed2dd8e61d0aac5127db2b92989eecaa38a3a5d400556c48bb8ea8b39c2a0f592d0cb6bf22273c31f0820481442796ac101ed46f0833ba59df0c61271e9c3cb8193453602ceb8bb9a7286f180575565b2da3e58bb05f03f7419c12a11451ce16adaa7b2b9cae31f94c5376d760d604f03c8911a16e9d769652be485eb8a292459881206e5d7e0f57eea3531dea2b900d9f4937d5c5093e20943050a23adf78dc0be78ba47ec0b6d37a8e35b201505ed4c1e84a84568b6289b05a9b4b0cf364991f45b7409f080862ad38a138e01b61108f265a05bca5fb69355fc511b2a91719a781e5d2007902c361820cdfa50853533d15158653018781ef7bf6008c704f4c26f077590c5d7e92d262b84c846a0e12f49ac0f06287fffabb7f869663f0b46b818d38569f853c93e9d6a288addd3814d58019c9b2c069e9789dac069c3ae0324698cc2d0553ed85ca6161602f15d09acec00a9ae5e82b49b8029b10457ee394b142264ff5b668f31b48bf557187329ffdff89691865d86ba089aabd1434871c2b258f6dd8a5f45fe3e13abb898a2dd485da0ebcc14caf7eaf34b2632e0273ec0d5a2303578be84b1a05730fd48e73dc6c4a54cb2ebf2d0a7e1e254bbc413c5b364ed4b9625b0d82f9b965e9f8196d04b759e60d9d4645aa6adb9652ffb6a7a42e3a217a42f2c319bf86c0a9a8f9d543bdd31de01b7b2041bf908a5cf154b3cbb242c8a304b028f66096eb1783869f6ec045ee312c9fc99d5ac5c2ade67a37a5525b80764c5423d36509a5481084afe2ace02885146b69d07ca05320fda1ab78081b37632eb224502daeb52c402d6a471a03abdb2e1c95f620133e6358caace7d80b1ee7f9c87d8a6d8bed9aa56fd1356deaaddb9563c5aca71da80421d4d3ad6a852273b7dc4a4b35b2d22cfa9df794456542179dad1bfc12aa48fce1621b2e3a1761b2904ea0cfa5bcc22d36e4b0b3a52019add6c2f289b9ddca8091e1b9397ec14be8f876c9341bfda701d46ceefa333cf72a11867a72b44f41d79defc4a0f5014c700e2792b578efeb59ca5bcea3e359ac0faa0bfba121d3fadc18b9cbeb25fe04fac530960e3026ab8dc6d6e10df89eabfab6bd7ed5b0fed05c573bfaf0d228c62d4ecc080ce5cca8ccd36c7b1464cd8e32ce6944fe64e544a4f688d490ccb04aab170cb0ed19dde3d3d4aabd0170fa075c17ff48339971a1a66ac9ed6b3d25fa16c8f086d44d9e7afbb3f2863a64221261be4b9337ee39645546fceaa147a95d96475363ee62243670358363c40fa4dea74c95e403c1cdbf8c126ff324abe26928dd58a2f279dd2d0bdb2095b48daa17bed9f673d96d81a78291afc52dcf090cfdf76f62f9adf31b76e4d535f0ced8a8034053a50f271f28baa02e68d6d2bbb41e56d62a36f44e408382a0845a894b171bf5759c07c6491cb4e919083410e2b48045e403b1be8a82dd839793733cdefd3a1e60b8efcfb06c3b56c1ffb0da377c173a3b5a9c68c5f10bdd64cb1f8c8e59e4d315007825aee4405f0c0d710b804d20e2e8c489393a6292ac5b790881f35bf7707693a50c38281d4c17b7d8181c700dca38d1063e4f01ba682da01d1240bb3c9033cdc9f2227e34f491ee76f91b84de5e67094c5b9de11c3d941d80e81c9cc254650d3ac7df5f524dcea483795217881e7c520c19bb3e43eb9c0cf03266619b665959fa22df5c467df983949dbd427081f023801c682a1aaf7a0efda589b31ac2a9ba01a57e5537807379435a956f39037164f6310fefaa09e1b739468674f05001694919a3f53e3b1f230368d90af84d0917b90c2ce9ca0fbbfbc9e80d20a82237e22c23a07c90341de97a8fcac3e2654c8992b548fc620efc9e6e6989806d753fd0acc12595904a48b3dabd46cd988414b40da37b4deb6090bc744ef6bf525104b666464498fcc0ce454fed738d3e7cf1edd740da0cc1bcc333a8006ddd63d2cf8573a69ae2fdf4872b144a8c9d34aab2e9a6b2ee19bbf6983252ea3f7b5a19da05f41e9610646e109c05b73cc3903559769d986108816ebf5f754c72857dc9f892d8dd8ecca7f32a57d8c79f0322c9b4253f47f926bd9a577d64ed25e3cda635bfb2a9f57df26e1ca2539bec4097bbdf09d363408c089ec9f148a3db27200f0298108d189df8fdb246e714819a66a0835b561595c62c0754db94aa6cb86ddba76943c7a08bcac1fb88ae97a88853f8803857306a763df01c771389cec8c0f263923aef8e986041c5044b08034ac5568e61600f2d72611a6f72f98bd580d5a520024c3755cb546f45ea87a084a094074908130fd3ed227cb01435db8afc71c3c907d61998388e09e1a9e01b4cd325d878ff144c363d1d2e49165724a4c3c9269f7467e7aba1fdd8d8ad770b84c2afe37cc2afe1301c2edd4f87abb36a5e20f69b1f673c804cd9325604bdbe7aa53e278dee3d484bc46f44234c8a40a9b75bb44161e06bb66558c7d18614df6c934c23590ce3883fbfbd8240a0db49e639745bf67c76465b9e14c2ab270f8b5cd17582c02d19a706567a6560c6b04249f0242438d1d6c6ac7239afa08794d31824f72c4fc0308c6290d5a0bfa498bb87d130965b02784cab163654c4092c2c1910d7e8ec772b4d0482933d200d4429d6068a2c95394867b8c0c7203c21fc9e4dfd80e52f87f70697dcc610d74cde99758b6414583aaba469d07b45b689b5bc480f0fa5349a69133cde86f679ee438cdb12d91f61e78446a882d742288a04cba6271cc6bbf834cf4e28c472baae9593e8387ecf81cb9e0ecd143adb95c004e93548b9d9eca8b3b0b8c1ce00e2ef70f29b35bf492b6ea54f358a2d741a12e3c75ead8b27b81006384a68034a1f76e94c187ca0fbd2976c317cefa4ca84c25c61a5dc71a6f304add0071641db8b14107260660d9cdfb5ef61f3cefb46835d0fb34b1e23bac26748498a28148c8a6802cbf5d06144c548e8d6fd417ec464c7d027b74590662d9a003d23f7b18e5fbaa7b788b052a5d6581e4501bb410835a928e856009c43aa431d019ab7740a37f20b6c9ec67823736c1827c43368b780c3cc1138628941ec2b96681b052a51b07c915303b940bf59b7cd5fb649aecfd3189fa8a221de286932a80ae7da2ee7d23a5f11ff39648146cbbb9986c3d402447dc91c106283953bd680e83a4ba027c30b17e83e666c2fc77ef59b18e1f58711c015d7d61fafa5c0d9e51e874c4a7600d9e4bbd7739860605a649f7e84b21de5d3d9d743f9dcc47058207c8dc88f026adc4acccedba7025459600434fbc2ecd864dd00e5e07d37d1eafe20c61caf33934f762175e34432db98aaeac062ab0e68a08885c31d981447f184c4b868592782c9a09692cdfe04be9a34681fb5f5e728d0a764db2d3d51abb28e544b5c98bbf94df04647cb2743a05da533727eeef49bb90728c19e41e672f14d35f35b48a7601afc7780442842cb8381d06ce1063907184af8e5a78f180873f6381ab991d91ab94815b03836a491e7b357ce2fc6110cf28dfe9f20b491797bc78fc75596a031651fd32bb6223ddb431c024c08386e291e29efe015e94303ffbc1c87f8d423d59c00f8c5a12870610b1f3339334073fc7e63ba1a7ed825fcb99611ee454b1502208264ab39d84ca349b325ecbc25b3bd8f2c3f94366d3cf0c74b06a8acf1dc1c3e175cd892750a86d0ae3b8a0bde2ea3d18daff3b7d8d290b03bba6c719c1474c948f9bd0412f70ed0e6d3df2758b0017665d5946c8d5ad7e1c0abb79897d45b02392b8f47205bdb9809c695f4c3ff217007443fac325b9e903da8386758429ce904373c145788cfe0797e25a100111055fc12c68ed89f3b198cdb138f7fcd58dbf131d2c3ca50a5a5e9134a2a9f422a209e17087e7363d57da01cead59ff759fdc65a3cb9d999bf5cf81b35811b4f3e8d378e4fe676ef0d0e66ae1173423b63ce9c72d1a98af04636626191a3b67fd060e00ea6550f3f11271740147627187c430ad4daad6d1144aeea93facc13a13b87b0b7d44a974d4dfc7cd4ee159d7709d5bbfc9d3598e19a199ff140329bfc6cd226d1d4508d38dfbe05690a33d2383b8de6030caeaadd7565dab8dc81da483eaf09496c80a514f476c4dbe1a1defd5f5725186a9ab50d7d2be73e1eb6d091d874cbf956b4410019b4f5100dfaed5758c5fcd4573d26e1a5899f75f821f69fd6810b2cb5b83fc3f27fdcc574a4c60a0c5d40a4a7a610a060c434b3c59361dda287b1ad445d583c2b134eb77dc068d72666d562feae245089c067c815fff52a15734b2a0597e2162d4ff2446c2d688c28b64abf8bfb0148a4a4ab18413ddeac0162046a0dfbce9a2df8841e5f495d244096494ab6147e548d097396472b812236e80c328094bbd347e6d1db0da6de575306be64e644fed38ff48f1b5b718d9cb1de710008d6665ab245bc12687f0480f353511f441c1ca017b1c589e2b2921f6ac4dc15d94da226ba107bdf5468bb55691f866265728997167d6a40fbf86402ea17e768f81c8a3dda07a69d47c75fc4416ceb926931b2ff94f381026a1a697a49d3646c70ac5aeec220913429844890fb9661da555f4b3cd95b1b39e2fde8f5a62b0dbcd22cf09f12a4719fc2d96297dda37a34d9fd9d2c2bc7f2098c96e8707804d1178bdf9169ad73a127341dfb4dda09072e872bf85defaf3bcaa698c7851e8d05ee6831964472f15f578f7898a59ed78749281171855b0306459a464cf32fffaaf1ed3f8d73680f585e5580b28904589ba8d2357d51030a3ea184435d5166bff6a8e222a2d510a72a83981207f754c86a4046a3b3600ed81c5f1abddfe131e595e8842ca542332812395241e42e44da37a43d0604bfa537d5925394fd89dd0ccb91011da7c7b8a1c3da7714a235b8603093764ea1797bc2a122ea6f9119fbe9adced533648b84792fadf1ddb7f8d78e9f852653aa82d497fd25484dff99838fd51e16f5fa0ac0a79e6dcf3e8e5808e5baf7580e335107a95e013b42432838a31e3ba5933894a5c45892b6e9ec358bd704a7801a5319b92784fe9b19bdb4f66aca7f9b03d451839369b2f55354a61687fcc347819255128c332d4f9f43021c20fdf292a9ef5091a8295f791e87a394f0714049b8be5027b467e0891bb5785e97b7089cb5a3b056cdfa9f4ff4f59dbb7b91bb8f59a173d343068b4706a2db20a7972702c32949b498382150166d3e438a65508de1cdd4acc46a862593a8b204784bd00a3fcdbd72a6d29b9d88208498accae59cc46b3e36dc06289da541f6d19683e4049919abbb2a5147e707bab2a75b150869bdbc3fd3e48420b4fde4ff4bb671d31995ac4598e48d65cb00a9ad7c90d2c04d32a47701015c6ad2e88869885b1f2f7cb0aeaf0d3c3c72284d36194d9dfc9604d2dc3ab898b59a0ccd39e8140d3b285b7506bcc7c870cb859fd5a1463fa22d7d2ed17b0b6124dd7ad109b627497ff0167de732e7fdc1d96d9887bc71da640acdd9ee2f21c3b11c05778d0865b49169b4fe387088ba9e2e79e13d337380100e468f9ae0738727ae63562359f6765ec2f6d0b980dc70ec70067998dee61486a2203b602199bfee5f9a9c7a806626e940f6069cdbb5898c007587fb65b804168af9b127efebc40c7c3e0f74983e7f949c4cf27707ade4d228c7f16ba9843e17ad7e2b5e817214e5bbdb5615c0c81cee453cdcf5afbab9fae41b5c05be643b04b37b3c4ce968dca15d409ce4b79a9aa030095b4737ac25428f900e80e6029debc7fafc01a298742adff652bb1a9ec85ca3292b2bded4fda16d9c358cc0c7cac58181fe621fda726f4dad390c898f52f753c0178b1fc2550fa0018a539f808c1fdee250f1465a439f01fa1c91db79e40be1c4dbf92c48ec6027384cab9d0e238ae003478043f344e695738e0743db8b6e6e65e50fb40289b6321e6dfab901febd126e6c8fa1ea55a3ed57ce5dd024706756c5ec4789c08f0da5c40d413402ff37c62d7abd26bc1844683222422d169ae6a2360f520de175cba89c042f9bc840cef159a4f811b269fb89313a24ee3abf5499a526781c50aac94a5f7dbe651461af59b686c15978e58b8fecb3045ea121c2ccf75225fa09b46651c4efeb3ae018b3e6ea5f5ff7e40400892838f539d85c9547666a2e38c93d9cfac28eea961f6e99b33cdacee5cbe630ec8148c7a96013040f7a2aad180826531c1a7e696d7b4f4c6f717dfef958a62342a9642a9f221d5a34e5f94b22bfd4a3e16f51b6dba450dd438a505e7500e920d879540eb430a277a7ae6ccb9bf64975e319d5c777691e93239d936d28d721f70d1b29516112a7285f43630523954087da111a134a11ea7e28db32dd3c6237c061af28143dda6b53742c470a183d0c89df1f2406b50e8a3dc58a10176a07413e9ea150fcf5e6cc784da3d799ba63f93b01b2f68cd519a34c8fcb6abedcc4ae9b7fefd99790bc1cc57b904abcc12e3d377a9b75d34998da4b5002738883d23ebfbdae68cfd338ec14d032822f3e3f74efa6e0b2522ef3063a5e227b1c23e03d6ea589aa56ec8d488dd6d7a731b1331323ad0313536892a53da5ed4c2c3249cc9ad602dc74c3356b480c113cf796e2b9daebfdc6d9782053cbfbec8c739ac52fb821cc856a7777e93b0fa2c316ffd7620771cf7473e92885ce0886a3a3a57bd0e1cc3cc76446579cf4c6457b6b7ad39933740e2a94ff70b1ef67e1b8264d3bc7fd65092b60d0e3f04e7c36e0cb347a28fa4d86ed6d4680ba0b9c3e147f84336cb503ae19d3b71525c0eb9f59270df580e688097181516397032b70de8c9c42f4d0edbd8649fd21aab5d91b83303419f28e916fff8cb426b8ba0d77bc50a465174bc337cddf75dcb45cf9079b8ac79a3fffe1b34786d8b4a78309c82f82232ed996c1b44a5f132dd7587a3337989a6100677a93667abf6a2171e0a47f49a5a79717390db273b36a74ad6e74d93823b5e4cd593c909b98a916e692f73552c8f026efc124e45508b1b390564ccabbd87988069e8a2f41d1d392a3c7cd65876431c751f88c1e91e437ccf7f372020fa7a25dfa95d03c9e39374d1205fc11501ba4f02cefa6869ce0ddbbf98c66ca053a3f11a35b067f524584c0a5cd6195d135087ab978eead8cb328ba7d758c86117937bd01caca818fa283d9d2a83b8332f13ff6eee4318ffff0bf007f19a89cd06b9eb06782e738e29310f3443aaf190c3b9ade2ac293deb84f1e14cf4685ef00bfd36013528300ebabe5c3280f6dca945e1a2f6201666c11b498546db1df65a87fb88fa7153abbe7bc4998fc0b506d2fb972f8246bba9cf69b7fa3171cfb25411706071b1fccba36fc154e100e2ec9baea012d42de5eb2666dbecda8d9bcaf49dc7c5a983faf42d651aa95476895a2b480d714486c19d083e897a9c405993100607bceef9a69cc72d07502584cc51f46501f825bf2dd6fdb97239d30a69ff04bcb62087caf9b906616c01b14a1174a825cc7a518c1d4ef051151e6aa51de41ffe3ccc6f64628591d0164ef403a34503a020822bafa3be32852f9d1ced4603f276548d1e0f92c043e217fefb4e6443e10b082e3cf8bf0e2af49086a8a5c1559a3ec50a70b479b6a8917699710e5f39d2ee2122ecd99800ed1d12848d8d21a296d0698b8d8fa209e1d74201c8074af80386938000cc63012bfa338184392b126217210b1caf01c32c976fc722babb25e9e549179cd3673630f892ca01f992706ddd25406033681e6be2e9c1fef815c5f4c3a2fee0256a5ba8f7df9045460e124a7b0d4f6ad1d5d3efa4d24e1e046b8cc7493ab4dd42dd228d298760175aa7a462b1deed2bf43aaed9823993e74cb7114b064f2717f455d7add6db24ec7a017a51c0e2c62bdc404616ae7b0b121d5b6cffde4446f1d560d1825158a3c009eb82e0ac922a6f49034c7757f0ccefd1ecd10db02c364d0c5c4afa41f4139f59f40a45b3e9ef38f2d71139bd1a7bd1d9aff8cf8ea32ff551110b2c2858831f360a56747fb8cf1b53236ae922ec22980d89550bdb0cb83b5ec512719c0e30ce7a9294806642a411d7c17af39d7dd0b17eb0f3886c8efc873f2ea1c4bd0b9fd7d73dc838fe0fbc82e69745f248c003abc567207041cef4890d54553e6c49f440da33f4d50a0dc888235a67b687128cab029ac92704c8bc804cf0f29b25cdf304f470002fcf24a13c270a74031394c49c775f92538d798c28546481d5c1fa437d12b4ea7d07a0a90862071d51bccb67e596102ba38592d126c72f332fd7824d1d347a377e12d282dceae3ddab1054bca815d613ef615d68b48a8214209d8c394de385332d4cd3a4f9091ef5f5bb69a1f70d235ccfec7c6f56933a45affaf333cbdb0b70aa3e999d65e874d915913d3395ddcf8fbc964cdf0f31dac4927d64bf751b8e5055e8963dcecf78328ed2c21e0b44995a3e25a5baa93d13785e0438db3a192439dc46439c94aff141269a586b0e4ab974a0c3a0088429b192d5a9f5dd9ec3223fe264658991359e7c81cf505314e1c48a1eee06c70e433eba95256c061a9cb5bf860f7b996c9b1c44de70ef749720c18f6a3171871a64ce2c946a994a7ce97f0ed441e4633f93225c7af88fe0c4649d355ce9b87881a6c680c730f75139f04d2ac4c6bd2508bf3b4bee32da3363765ca8e78142f7ae87baf0c55c542241ed137dd7ddd60cd82d031723dea0c8ec43b909b5c02262307f3e92fde02a06732d5d0f47c2b89f6b08bf2b0905502aa495d13640588abe59c2fd5982901351b132dc416bea2a0e063afb1e0c346060e89510de2e1508bda7f58d5f2fd24d0967252b8af299807ae861720e7b5f726316fbdfc077ffc42ad44a518a70340222ccbeeb974143cd518a8aa67bf4582565434bd15f2e5c3a62ac5c93dca20dbcea0b4e191c51ef4c90c4005b35ca3c031779747b1c16a00dc46a0a2b6a595cac535c2cc15da53237cca7ad5a67ac1e72cc7f576421bdcc28435f71e5e98f69fb0f0cb5648e65630701bde8877b1dab4a613a8bd3b1c276aa7b6e802025109fff8d9a61ade05003a231d2ad169c500bff187ccaf90a9db3f14fce710fbaf04a50349fe6f2ada6d22a8c26ca959c23ca56b977abe856608069af6868f9296a7479608fd14baa30353d01769c4b816f9e291f32a0634ba7270d3fab976583d86422ba2a55eb0eb4375903b0ce894b6610333d80c8c8a0768161e860e89563c9ab6985cefd9a3c019786bcbfcfcb82fe9126f7458d30d2f4a631a9acb84bf9e440e9128716e2ab7cd4a7e1ac0c983285602ddbee392fd74b07042f70ef47947b8bbdab959704f325232745c2f3a1ef85a5daceff943a80840e16051271a3a01d0a6332b5877adfdec9be442b426afb3dfa20edd57ea4e8ef51e171cf0dcd2114a2704405d8fed9b22dc938291deff9404c9911a1dfd79de48c3453c3a3ded00dc2f21e67c0bb25bb7c24735fbee7d8403510a3e571d1ecfbdf7cc319abd115c1a835dfa98e9ca12614fc4c33fde3beed7c6e71a09d8b2734d062e366d6f9097e316ea0a863d9a8afa718f9c7f892a4ef1be5ea9cf1082032f384f02b826f045a1aba71289732e7c45948eb0f5dfb35577c888a32bb454566a300a4e77c473510e95f9eced25d8c1b4b9b823327ab17fb074cbe328a0ddbca4e3745140a04dd4f974c5e853113d6de40937ca918887d8232d86aab0f7cd472211c028eb64f519dd0f68c52bdbdcf6b2c16f553bb7a589916d66ca36937eaa98922adde23c7a5e13f760fbaa86f7cea5940e63784d8dcbaec597c031c8607fbb6ca904ef6f5555ee9ca8e6a498ae25c87c7da0e83c40d44e8316de0df0f1c45f60a3f1a6953e1c7b304414ccdb4d7f124b83b8c136c7089dce6c89805473f28ade0396bcf625c70277aaf8725a99cb9e370ccccf885a92f09f50fbf94d0a9f6f8c3cbd440efe7025fedbbdbcf2fb7ff0f3bbeb86c6e431f3c64ed872867c54aca682240bef7fe3df7c0bdb9fe5023b2e433aaaa760d6830da1cd8e2b8e7c3845d5fc44ef5db1c730c5954381994781ffda95b1d862d04daaf1c0734e608443207b06d34f648dd7f9c6327882b479ce2e8c1d71fb5d5e7852d46d4442e6aad072792eca3b83838318e8381b6ed2446cb2905eeca170fb3e43d37e180f674988b9b9b0e83d667880d7b754574d9fc385d22b64cb7a84852a4ef5b81a4f88de3a28e9875551634fa55a782be130c740758e0b47507eb5130fcbdebf82d64c724407a2b860bbe89b7f29fbd7e2495044b77974fc6b5b56991172ba3ed57c57d23e83863b345e4c4742d86e1c0ab5f33a397d407bd1059555e0b595bd9274dcd09be702470984bd537593d7d1d52fc8844acf7701cdad16f0f63adafdbc50c9d5470662026f71d4a92c41bdf50d670bdac4da2551b0518bd4ae06499d890901a112ffd31ffee6563b0585563be9c093736babcd85b2ab0c692bfa84381520c3d15c0e08cb06397ae7eded311c7b1c2a6e9629619edb8c1b50ee618230376fbfd61521df5b54bb03ec0daa06db5d8a307d78ef71c5e9af6f63ea3440daf36d986a1195955f3a65c79046002ea3b898957f17a25071b94325102134cf206ca5394ee068af00690bccd8f677973d9c239fde484be1de2d68902b5a70959d75116cccfcebba0cf232b767df82bdaf0109866ddd2c33b268e76f511d2cb1bd532ef091fbc6bde986228a37c2070f63ede118104e791cbcfd0b91e85b3ddb92575a6214e74ec2680a02bdd92f45c037a8f265ca3b5271facd73e134f21cb2fee1b7c0143188de346037fad014ce87ca850fd18b4948616c89c7e1d635a7f73f354bd1019ce57fe8ecf98372f056e720779897f08aa0469207c5689ea0b8e4ce10fa8f5671b6f3f593124b85a86c0de8337dc85b6f140da3947ece95d85cde87e33da8e235c3dcd53f4048fddaf4cb948189f0a630fb1e755524ee5f32b69b626a6ac15f6624e5d8bf9a88c775f2efcae00916919768dfe440f2515e7689877b14cb6306d1da47035ec1c570be13a2703500e6c117b3cb9f1539ffc0c6e020ec1d5d22bd05f6e9848b4a47f2f1a87903a9e7dee161fb0e42507e22e2adc6a482e9e0475df82b584cfd1558d935a913c41e8ec01dede4ba7d7f284f4e8729874c7f51710664c2a26c3274553a28fab4b1ee0c78495aa1538405cd61fc7163b476c50fb8de854c73605b695db56e5e88221b2d928c776b574d5c4d540f954315655986de3ac856fd0db65a567ee68d82208c41e8351d4d6ce3db53ba7a60acd06170cc0a13206caa9c2f406e8668b329f1806f5e07d90d92476bc5e14e7965ca0c9987af339fabae8b926062c3d009091f6806fa074cfd086790217a808ff7d27a0bce4b389ca0afede990295f76ed77a42a152e98c48e79ef75d098926c90e5b59b523342f83b71f2666ff0a862c320a13be9c57c0590250d418a03dced71fd932bb079d22a2c65eea1f399bd42f9208b432e061d63a58b704ac3914e56c33bd6a470bc75b502100b7c10ae854586a9e402d525118828d2a0a607c0ac34648a7deb16979eeb8ffc03123a7598b92464f588e8d0efa3d3fa5766bddd7bce850a2f99ac7f717a298f0b5409f1ce47b61c70e0e2b3c1c5ae31251ca3d53426caf40e4aa55e19d6b192458bc84aef9489846069183c34dd8d61fc220d6348fc4746b08c506a69f2c81728d7f028560cb7d8542fc3134dd030e6dcc79751c61392eb0a2f55e0c53ed046d72693b0db788411c10bd696ae49699bc1f9900fa52cd81280b2132fcd20fffc8ff6b60b63488dde3b24cf5e22955b1ec94c3bc1e60f496722f013c4d5432a72c45946390ebf4984a5c8351a1373c31317c8e447fcf9c1b6ef97a1956f90e0c31bbb213e935990d040724c4817b73218a6de44babe3288db27ac4d1e1df10a8bf68eadab6d8f7ab302a5687177f5108f268865939681f918e903a6be71f22531fa75fcec9049dcedcb25acca4358c22caece2997fa3c2eb6f1cd74ffc7fa64173c30e19048ec5cf0d1bb3bcc32ba4cd1436db01cb700a8cd58de66d35d329cfab733931caf2a9689f93546547246491916800f4070bad8c120d0868346c078d9ef09029b8c657bf95234fb6c13d0963e5ad6968b0c9226a068b26dd37038416c48c86fbd340f9b21b79773420923aaab5a53d15046c57d51f80b4bffa9c57b45acd041c6e8a1deee6d204f2b42de3934c10ac203f9b585517b8714528f35c03718c7f692432e109e36b05a1167ed7cdb639a6a1a369c71548c670e3a00ab86655fde9c64219bc66f350d6f67374e102f622474f8718f5d2e547a96cdabad727dddb3ca8faf4cacb6da4b0e41c79e3c73eb28810d60b2866a71d46fb56031257fd976acf0fe683ff4e3cd76760ed201e947280407697ef78f4495a2e13559afa439f31e0de8a91f3b80c68641d02c8ce4780477873bb2a138cf089c93b864653959ed555ccc07e0cf731d73bc29870a4fa4f90dcf228b97bdac6a067fa6633ddcf6eabeb1827f6f94de2beae8ad80479eac3c97e3090f97428e6ccdda8f70b4a952d21aca8247b01a4ff2fbc6306833a053737d44179d35627391ba4cbd0dc6b58e97ca460315bc0c9841f612dc18bba4a7786f65091c7b31e47162171e2c18ad44940f93432a1d7a231cb34d3d060d33031e55ae23820b636885e538753e424ea6d6c491dd425b60c92511c8fb77f2e6e33ffa375aa4ef373835f60610f1891b871e2bc1de48c64ef42371bfb8c6c0c2544f63af45f709f3b21eea49ba8bbac93899f340d94216333810737e596a871f2ce3196c9fef4439b0288e8d2f4059c97c0d62ca516bfe3a36e969dc574b0e613c56f1d0c9e59cb75200fbb46557846001e9185da6d52b2bd6501aa3a21c866bf5975553e41559c8c3220f71d6b88bd0485bc2cc0fdcfcaa469a9c7f5e99a2307998e676b02e584e9fdb64ec09c35393ed60bca4126e7dfaae072d2a45b34f143420b1c47787c803e477582df7edb747e592b600e9848e6266848f8e2a388bcdbe763a827f2e3b74dc39fa9dc8e6ee8d08b131eb1e53d541b285574777182df7edb147c19f26d55430c5934e1115d93e65a4a53d77779e2fdfebb26d0d32c69436d28e151415c34165ae6efb5e9fba37738c5fcfe37a6b3c77a0173c8e4f85a23500e9b3e2f2b02cfa9c9eb6bb50039317d7fd61290d3e6bba2a0e40b1f098f282b2ae86a448f718c5146028c9896d25ca31ec8186596953ed7b972a40017c9d02f47a2962e3ce208164ad1ccff9c41d883017198d9f642a06fc7a2967c78c411164cd19b7e9c51d8a416e28059ec926eccde6c138f7270b6bdac7b676556c523ea940872107789879ea5d9178fe4aff124f3522730de8c47354aca92c29b5cd01feb1e1ec5f734099845f00a9f2cbff64e8dfb96dba02110826e6fa1bbc574d490965fb952395732c004ddfc94c242dcd4f9022394c227d0c5260d15d8b6777c900257a8042aee43a9c105c7953304b85f0dc175e63087435a114c5763f1b98d2bb59450ac9230278f84399147e336429e482a8ea9d942b03c15ed82e53908fe30bb26da5af102707bfaaa93ba6c8fe2b20fe6e29b84a7e41ee111d82f1b720e012e9bba32b820fed759204ce4ea8a6027dce55acc2e384b9c2b9a7c12fda5bc125d9f8840bef0595fe3c35fa4f8fe40b6aed8519b9246c02c4a410c3d78d35389de34146a5e56a2987fd2f633082a8cb02ef92e7e469c2f06d1339daaf462fbf17f405aa1131467905f353ddd1b63f06a80d0d3e3aecdcc587fccab1f392fd76538e781e8970a6b29bf78b37238c0208dc2c665315b946a00262606d32b24c45b02a5f33a058de800d0736e1f796d0e568cf07ba74f007938570235b73f6145b95c93a99154c78ce9f9ccfc564562d7a8cd0744202a56e30699726925f4c82f7016e6efff40639068c69e17478c9ad668181ee6b0ea13af01db4a2e8c7b118faf0ea58146b00423f1924541298ef624ae1d1386635f34c5b0c4a1a95c49e830bb2a55661e863942179553b6e183cd944a0aef45a931a636b46b10829edd182b93dc5139d8f82896fc21520f639090df8d867ce9acef2d193c1988f3174d128c322fb45a1e5263e5c942644649b0e2f617a61df1726c47a9781d5c3e1fbce54b90d5a1842b2e9a893ecf4019b624136ba21db6f6b32f335d08615f7d8e9c84c821944a642461101e27077e6a6ef08afea3c6c929dafe6faceebdca50c0eb36f77fcffa73e3ba90c26b32e838ae0dfeed9a06d9283ceb73abd5e92c0e32c997703dc61a36d4f21ce12b0913059ca37c537f83e685128b861d0b44f7a7a28accf4d65eac7981448cfb39b647d68b62683d02990f356e6d3b9ac413f7ed44ca54fdb79ee2e3e344beda7d09d8d8c0c604a806467112695b97007d5e5241b79db194b3ebb0df0d2f726e360ae73c564f2d392e49933c969267409f5857bdf0536cae9377752227ca550f0324c886bad9a859c2cda9bdb48307508e8b795889b92a9548b3cf92366e75f97fe81dd995d2c6b1fd8d13dbfbcde16c67c5c08c3bdd53e554cfc4d4207cb13cc3f87eedfd9a0ebcc100fd88d0c17e7a95506c4a20edbaba14d7b5c049224713eda850efb9bab8083c44027a800e648563fe8e389672fb001d16501ec3078f939991e898c8f4257ae7d866e773b170f26761eb2cd396e230b6e9d9ac032c5cbb6b05e86aac3e3d67f368c670d8c617b6f517a318e209c75d4115592349b3d12ae0615ce975d7a6be8898884c75daf6614f2dfcfd878dd0bf8082c2b69615b9e027a16dab368974c667d79992e97fe9868b5ace4ea850ce0298a45e952c37a1b6d46ea929101acb9ecb6167c4e6c68f6649a5fb25dd66fcbf912b6181dbc2038e8e896a1756066964a39e63ac6750fc46720ec5bee0f4d58c35f07eb7ffd787230aba722e9a9911e1c5be4aa703ac4a09448c25838efc079133bf88cf4a89d8bc79b14e8fd663c2458c8507a90a88c5145b011e908db912a3932f25b084be6544237b65deff427d702a46953a3fdfa7498f53b26546f8ab1962c94938bde296cb9cafa9e3eca7a3d3bd23b8a19ed05add9a5ac2f7186eafba6c86c2da31b6246dbf06079571b4985a8aa8abf4d614d5c9133ecbd2a1c7aeffa4e7718cc50f4e5def9eef591a43b424453c3b321885264740623ffa680b83887505201b73d8e393fdae0656e1c8678ec499e9aa122b841189ece3e7eee2a95671abd48da11b0106f18aaa7cf7dccf667f10121ace4ce3912632cb9eeadf9989e44f4352aea8352485e2619994703ffd87985694e138dc6dd21a3900e62f3c7460dc66d2c57ec5fe88475bf8fe62c7d0a4beb9feec63428e119a16c0a16fd4cc4248877f3280d83e903596cb188e38614c45f90e6fa47627675d26438667be74a8130b316e6c524341c59695e14a29daf8ab9cd3af58a6725369f4a8cdd1ea7441d387e8b02b72bad4f86727f8dd88816660fe28d3da8d9b6bf8406f820bc9e6158c113a472e16edb7d799e5f2700518661593dac5b5e26d1198d335f3c263996b7aae3867aa9b20f236e25d91587a1e345ab3d238a5a78440829d8df37015cce330a0f2a5eb04882263642a3ca0a042e81fc09b1887c1b6301224cbe5e652c04aa4f88be5768c6e6c570e6be2ac6c9950692b641d8ca89711f15ea4eff2562a6fc16c126493300405ef3de1829698a595978be75ab60808bd78d00142d74fa56528df0a6ee4bab3f3e56e7ca9f9b0aceb0b5afc4911acaf8d7a967babf259f42a6f09038873efe89d72a69e4000b3d2745c109e3dc18b8a27548d4bb5db627ecb62ffad35dac33179847fbe44f147354f9c6f5efc85cd3df1a740acef782451ba136be927ce2e05b692c79308f4c896439c30310fdcea411256eabc7088c42ecf845a0070f3bc1329f68190ecbe66fb928fbf1237835682638749861bb84d786c44905840e6add2af61745e535dfd5b70f069c1785985633efc62a16e46f2d9796fa066abc40afc512ad1acf624559eaf3344d59035e375bf25112988a4cbd379f399caa157dce22dfc8addd7a9f101dd91bc5c324b9ef21d8e098ddca52f49ba588d96801c323a799e9dd79ab8774f31dd901a33769e6f3546785752b526b18940ae49fbfa597608b97d99bb67128f5b7f782fc6313d8f826f4bc344b40649a95d3d994f0949d03e6f302401008020cc88084ede545b036c0d54285ea03c6c664e1b2f80414e749c365a9e7c864817fc8ea2f96aaaebae4f79402622c99339392489c2681fbea9d944320344c29511f64453661a2789db5168d949bca67ee2a4d6b34089e692599e5148c15a205ef20285386e8a5b20d1753aa235875aa77abae9bac99684b4049a8eb15aca6a3e98b365355a78c5d1c1ac5b965dab0ab86b60a2a67ec1aa2a2a11edfa6e6c9691cde74ebdbfc62a90538376e6f3f93dff51821092434c214b0aab3f5fa2440671844c6104f0af92ab7dc42a4e97f3510fd246d4f211936607468ebe1d968e0ec1c13b0ba01ab780abbc14139db467f141931a5dc5eb440fbe312c865c38b945fba8c2856b5ecf4824ad815247ce62f3d262fc5e702984ee011ce0769569d949a740e26d8a3053204fb9d904a5088d6a013fa59093944ad47d308e55f142e341c578d3fc809eafacbea139e3c6bf6ed656feb8a1fc04be49f3a869ced72bd9a434e540633ed2810cff176e37b046caf6bf283e24c5ec9782a6c9e78677a0c7b32b24cc789b09b265fe9bf7f441ec7d9245cdb9220b917f0a46c8ec777b8ac8334a3f9e47c27c6579a992a8370eae0512f550a477b43074d7376eaea2441c204f161bd86e9318e0ee12537900ac227510212aa0f61a514732a5200287ae307096fa1d48c92fe7479677a7faf83e7c08bb86d2b74ede46316ea64098c5efac904594524d496a7d6d7857fa6f4bc62926914da4d8a567de0c6dcb12c2a72185df81f009323d4a8de1dd66537ad1be39ad4d90bfe759a5247da251e194d55914fa8a5dbfea78a4d6bf395d7a0fac822d87dd1434d483b71d9c1d552512225869b35f6c98af0364d3e8578fe03aab0fdcf0127e6da132913a506f8a4ca840dd7d02d9984d31de7756cce81930f01e5151a0e2fde78fcc298276d60044620d382e398dbd3e713e5427bbe8dae56326be7911e02640b712186a4d8ceef2fd4777b02abf076a5e40fc5bf76d6f23db9d5ad5037d7b3c852750d73ec6b80f5b81c21f158049e2cf1b7817178112cb6cfc53006ce5b7473efe8ef1780466f7d8e2337819152f8bceadcaddade54d03c21f88dde2a0752d603f0396a1b46b3cf1de5ccd64ec458f29e7a637066b841be1cfae0efe818d2fb01354d75e42bf9b82c3d261a53ce7335480dc036561fed3d3347a86883fbb15283acda11240dfb1cba8db9ae3bc49ff3bc5f4500e917b41a780694373599574b604a2ec55ddb300144e9ce5a472a1156a5984e19ea97e89ce4562da35c504e635c5db734da14bac79583d98f6239167330203ac5d7915bbb286bec9b493a9b613990a3b3f622e8b23f885ebfedb042de4f09758abf47861305243bc1996901c050f56bbeb48db01766a8e87e9b93017368383266b5520f8212a2199e4ae1d10f1d948e0f10b20f97e7848b8708bc04d4ef14e4bac33791581c37e2e2d1afb0570fb595b21905563a5cc1859755748e45ac08a10798ad6ac05b461978dc8c7d58dc8242ef8197bf82389516e8f8afc4acadc92dc6e3000c46a0152200e52aaa86583c01fe5dc6f66d3831f497b6245e545e7ad3bc06ae1c82ba48d7f0bd1ccbd2172b524e3274e0344c9274575b1448350d0917645c83ddf8a364cc4cba40b6d889ac32d177e138bf7cd45071b5606f841150f551c6f6894805f4340f52666c7e7b907d2df5522f0e288a52759b401c0eabfd0b4d0ea11917768313b039023322e9ea4f54afae987ac5606c4c4890e5e386a7915462663f79a2d67ca957e323ff60e38b126c2147df926900cff1be6f4f6ba0719e91119341d01dd311feeacf873be5b12d57de1b1a614bf168af20663a5fca68b2e909828ca1a28031df0c5a0ed12d7f5e9c040a6bfa041045cc83ecd4764d2b7b05b414af1764cb19d170a45396ad12c5385196984ed06bc894319b1a3ec804c9b90277f3c75f76443b75045e3d84a59009ec359c0f01d3e669ccd8a07d13d4e874d8500040e69c46b0e1e2b8da3abc59aa9335c7692e0d32327be74017c60b055b8b1849b3e5cf4cbcfbab99cc7c7cd33e66208673946a5a1af0ac503cd60ff2e47b56594fe5d03a38ea7cafc918b5dc7a2c5ec2c68b1ce87456bb3a7983e5d6c8d0e864970dc2653f85c9fa6b0a86d9ffe75bdc8fd795ee15d584c2f718bc1cad55467bfbbc75475bb9700db1e47a4358d44f22affb1b6e199bdd35e2e5d5b70c53f9131c3d2f334447caa4bfbceb3532aac3531e96d35470ac5b8604bcbbd39f51c570e6734c0647a5b28ee2368ef090873c139ebae4d96586ca3f0fc225a3c21fb71be6fcf275847dc87549053d17b19804b06ced245c731e8e670b244a6185017fc502836aeaa52a9a19beaa98153189e2a221be23a4fedebe55fd4272b5104248eaf8e952c4a2c8946d4afc51285eefaa528d586f7258954134bb40867415707bce40dcec21826571a0d1caf37d303be069debeffbf71d13ee8d1b179be2622c4013b9a74a94bb6a3204aff751ee900306a0444956d1b70526ca5db38bb181f7059f20fbf8c7c5cb38e76472fa34b389379151dc5bfc8f251e156498541b9faa57c9d8b04a8762505031d9f8757f735fd681ab87324b94d2f49993f5ba8eff197d9383d7d1cb00db1abc3def2a4dccc8404a26ac18265b38bd0f22cbb847767386f0f5f903bdb3482cc412c570316873f11e99b1c729bac45936cc135c9bbe7882b5c20611cb8b25de99432e1775e30b14cdf861e444973224c3c4053a77463d05e64d24fab88177e1761965299528dd27020f1fb171722715dd018e0fa5912a4feb6bf62ccf1728822a15f245fa8f09e06744863a75f48a644b0306fbc58041f096c7480d51d95ba672710912a784d6b964eea0d0cde5643b7b1319e3b72cf9d785a99759c626b3ea4189ab33dc23bf9a6111019c70e11ed863a99aee7bd88e41fe1d080f8da3710037c4ddfb51e31e194096e0b98b6429076e3a51a256076ba885487ad4b9f47e22f1422568f2f899bd492ce049f1364ce797c229cab135008ecb47e5d0983b4e19efcf51df27ab5415511a40e2c40a5113708addce811b658d07fab9f2f0f3e98f36e81df9e6e72d82869be261fd8e783c200db00cd2606f322580d6f57149e7203d7acb487a6ef914de20cfcc9859c45d28df8015d55e4224653343e920bafc58389961c27ace7871601241ce32d36890105b6b7aee88c534456611fc722714aed58586bbd658b71aee4203dd6ba0bbd448f45557a01afc547894cbf7ff0ca92880c09b48b698c6d5b6987a75f48bb7c3397581bd0f7043e2c89360a672f3c1469f3a002c3541c93ccc2f53899b7edf39a578976796da7d88b574fd683631251c943d1f02343cf1eded32af3c980b0ac850fbbbde2b0ff9820428b83ddde7956fb6e09148fab4fbb400e8ba27e7d92545e50db5ed29cb7babc73aaca4825eea7aaf5cce4e0e7ac598cba76ce3266bcd7faeebce3246728aef1d7cc5d058de5aeff48b5203fb12d52f94d35a83f704bc62d428cf1fa20310aa57651dc669fc3e6c97c626f831dc88fa42aac0bdca487e13cae84cf673a149d88a8e742ee103d8355ecc9fef2aa009d3233828de7f5222cc56549ee7fb499eb1240450c5d0c03c1ab6aec7d3056a73f01ee8b5cd1e9120f14ce29b7fb4069e6f049edf161ffe342444d1240cae377090270f344b88d55d1b71ae49f4fa414ccd43f29cdd932fa641224e72d498517fcdbd53dae03ae8ad5d026f8f8d7c74eb080b6fd7c52338e6d49419aeffb6410e6b5a37d33a6c73c28906fe2af7e2b320951199ab82c406561818671ebefceb378b11d9e5a8d11a5e13a3b24003798b0a34a12611f575c451e7c1f228a0bec869cb867416c0f3bbe62df9693a374037cd41ef1b890b3f8a2287797511decd37f826f9440a2b8ad8dec2cb9064dc6c730678b878ef5dcfafa86ab037f0e0e2e9bf518cd5fc2e96249f8881ce121e933370c29b904478af1f65d6cf664a5750343303973b940944d2d037f80fd957fe6fc7e8fb82f602d8ed7d47d49f25ff1da40653d194e375d3821d8120c2b8bac039259f910bf8a4cd934d4d04fef1b47a26fcb96bd06b39e8c4bf98f2f6a20f195f86bd9023a7f89f8b4805ea43ceb6b1a997239fd78fbbbe4f080afc2e80dfbe3944cb67c89e7a4de9892eeff3c2307cc9f0c78acfea99b5cdd7a09dbae69b8205fe17e1da26e8995540b68a5f211943b3b3bbdd564e4e0b32135eaed471995482446387169c279a37e34d124e861a7d026f6799f9a43c40edea5e3f110a7a130e2a1490b235c4e1df1b238ddf3f16f9a4418e0f6171b1a4c3a1f82b139e0d2a817ab02ccbd849e78611b93ca9282f77a3ce2644e79693b3a682ec22331075d2855ea13ac7c7c9994aa0e21ce7ce8020d2a1b50920509c92ba301d09409581f122ada8440261209f5f07bec8d93dfeba6bb9c043f80a9077ae9770ffe99af9182db597f61121635062ca83288619b974cf19f794aa1cc0dee4c28a6661c34687cb0903f297e12604541c8672f7825c9b7cceccc87bd35f84e1d80c95e1177f7d77632c192645611c6bfe31ddd40a50d301fb2d3402da9a7f96292f6b623239045017ce64a17180b234a14677369e3fa03b70254cd87fc3ef1501696d94434cdf7120e47cc4d67d4b330d51d63eab3f90d3184b948d8e0179909c903c31b67eacc3a5e0e1118006f50673cecd3577a25496c9c0bab54fabf859dbf4faa32e9470be3cee1d367593d0dc25bfa0bb22eae89611e0a67af08acb00b9dd3afc81c48d66bf4e8b5bd08a8aebecf6b25b3b30819f1c560bdabf943ac70006e816ae45497e967f5b2623e22012f889640ab472703b5e39aa6f6d291e6a71049a8c67773ed6faf5ce6fdc41e735b18fdc619f846d980fae0758910c9609af3541321d0658578b77a2d148751e00583adcfdc179efd72a7e328276211c71046adcbfbb54206015251d595ddd86dc3ade8f2e4246c3c6d82f2c8fcab464d319dec72fabac9c003a001038082714722c6003dbc0a1b7af610702d40f8ceacdf3ebb3bab6eec5f0207ed16fad35b2092184104236d97bcbbd03310b720be90bdab3086b285543b9a28f6cada27b696fc46a73e8d4371186818ea1a0581da31ef55c6633067080ee76cd661759ee8d339b43b7c2403663da332d7bcd18c0a06756c776e139168cdd3a67b31418bbdbcd5260ec2f86056b7fb907c620be6ed9dc0348ee818301ca87c7f272041414234427a494a1d78de5ddc92ccb6af61aba9cdc6c9691d38c524a19311923ca28572ea38c32bb7ca2e22856b24986e4e8caee9cc055d22c048ab1a2c9e884e6a2228f250b96524a96ab58aa5b4617c98be4b52b232b8c6f7feda27218f6758b0a7542ca4827247765773212258676aec7c27d64b30c1e3df42ae555157af59beb4157f62bcbba6a475c267a8a95dde5771de6d61bba15672b1b838decc3209b63a6ecaaaa64758c541dab9e62554f7975ed66188dbd6ad22bd0555edd8cf297eda82e5565afa2080263980b6577657498d0b38f6ef6505669afce65bf6c0e65a08f3250c67d56d7ac48f4baa3ba2ba357b72ba367afa195d14de2e195ed30d747dfae8f7e8d6e96d92c53853e814437069ba0671706f4d18521856c0c86ebab180c83aa7337c7609844ba0f0676adbaca95285726e2946b97d98eead8bd52aa7aee17672b577121aeaab894cb5555bd3274b356715a57d57d555daa835abd4f5d30d361e1ec9cb51eb2590687fe321cf5b24ba4d7d725b657b61e6447361f09d1cd3db0e8f55622d0e8a0d1b58feee8db47afb06c94729295dd8dfeb206f4c02ba2eaa22ae5660a8f5e6d5c6671219b63308acbb8bf8cbbae58ad66b30cd6e2b53b1ad5fa58513e3d94c27149902c91cd32b81e6433c429a16b0f5dbb75b3932c4c68f4ae6d4fb1a0572b02d9c6229b512ecdb8d0b3eb650f787865f4eda3675674cdd687383bb2f5a2ec9b85a9f775173a28641fb6720c064ae2e1957a18eddceb5d69415dd1a19dbb76b34dbb0f620bbb16064290657a4a39e5b522c628208629a126313b66730f8c3d64b313382492dc2f6961b437be2eed65abcd3d90d06e3d76491b7ab56fe360b2737fcd71dc4537f7c02cc4217b1989e133f8294b35013a589e5404f86a0422b81e1404cb93ce8083e547bf2c88e5b31358266298d16576f3ca5fe600d2e5a5e8302e6f56e9118cf6d0e576796d07f6ebdaca767560f7badae5af876cc6fede00d139cd3e40d39e7d92d76ce8a1d7bbc25dfe7af66a57b863af99c22bdcd5f1bac38eddeb6ad8a9a83e7457aaa8f5ca1da25f174674eec2805e2f6dd6ae8cc4a2f7295302be7e663a40f7ad5c998849cf5e6f8671a5ca958978f4ec32db214fbad7c86629b0d45eb1fc35b1da018c45fcb21d124b07e4ebdaeb95dd6599884737fb45615b86c3ba8c583a40bbf26237677f357b984cd48111ae5b67a0c13b2c8cbd5a8b235db6cb4117fd02d9dad98ed15f77a15790ad0fd9fcfa8c7bdd31fabb13dd689085f8d105e5315358f4d72ea2a358d92d22d18ed17ddd89eebb8cc4f5d7adeb372267b38cb430f597032a8cd9fa97d55a04f8ebd7ab46a82fc70c4df0f5cadddc23d7ec044e7141b916ba994ab1af5bb69b1dc5be6ec97e236a36834ecfd9281a6317a6fe3acc9480afbf46d7ace823ab4354ffae9db3d909f890dd9e59ae316733e8ca3c5f2f410f9c1de3ae59ecd566277460577bf6ed3762b5b9fa66b314dac56e96025fdd05b165c15736f3c84de0776ab313d39491584a79a38058c708c387227786b7f29460f969877abc873ce0fc6a053820c106c0cf0a6080037e594f7ba8999ac1b5f2fe30c49e05a4de659aa689d2699aa6699aa6699a6c50e7e5a7e9d3344dd3344dd3344dd3344dd3f4c732fde07a2cd77b97d49d948f071569e0f727bbd47f7089ee28aa5612e987148614947dad1bd4f8258e51f2ef481147d0a8d1c938cbd3d1116129695021ac993c78cf858a2d4b2826f84127f8f9c0ef3facd0304909c12f870c4520d6ef9a6703f60ca6ce019efa074460c1b96b722763562415aec2f4f0dd95ea50409c29caf24adae9bb27e3d17825ea14ab8e046711b5de35dd4d6f4c089e24102abd8faf9ecb0d9b3305a96c7013c1d3b34a3cc5688d6e997e23ba744d6c994e22d51a21a58fc2321c2f8b974677d39fe4e829f98992b4061a418367a306af847d7ae3e0b964b79e379cbbe67a4e090253cf41e0dc35787a66e96ba18ed9f83aead15e2998bab561ead4521fd63d023d0c19294b3dc6794c65fc9998172e33d4c6a20b65e9529e1cd61df53320be5aa385e62ff45906a664f46851e160c3471cf065f362dd4deffa5ef4c2f6dee338082104d57a8ee340a06de3b8ca711c070271dc768edbce71dceb86c26d95e3388eab5cad5ce5366cca72cc4beef11cc7711cc7711cb7556edbb66ddbb46fb556ae7295b3d6eb5639ee95e3aebd564e748ebba88a44b57295db386e74eed7c5711cd7190e4e62d372cfb21ab1eee6d52e9f59944fabfd65dc6ff468aba23aba99c22836439cfdea0c8768547fd56a1f16558ee3388ee3386e7bddea378e0b9de242a007845a2f0cf7fa8d7bbd4502e20b62dc763386e7c3f33eb9c511938b66499cdef4a6294e31650eb5e3a7f7bbe3a77d628bd401f2735eca186f8a171526e9eec1d73d3b08d7934888f008868f483064c12f670b1d8c43b3a4094f26a1c2c820d1d35d8a1b6aa62466615586a35fa34b5409868fefba444f2fec914b20c53826c19d89916e9f5a98791988e9657429d67836aa470b0138fe9aa653b72cfd345551c0eed751c237b595a66a7083db097e38098d1336c1fd83e1803fd1e5451a3f3f3f118201ea5c1266b00bcf05138981e2a93a5d42222f22c11c15c71959b0945027b63413ba044f3a743a9fd2ca0adc97b26d5e4b5fca203c17c8043a3102ee1fc95373dbd8ccb6c13d5fff60e8f3980ec7c39eee208e1726c9c1147c316a590adc37d417d57b2f038263495e519f69f599f66c0b2298623407d3c151eee0f81c63f4cc42612fc32171322ba1508bfc20a9e04ce270f1499c9b654db623bb3288fc9112870a230371e3749712870ab35d06e2ec9bed2bba8bcf6ce3741797d4dc61cd8afef8cc42bc9db4d9dbd4ed991423f254d9304639b5be993224268e7997b29b7be0d9842a6fae24d25d84db84f36cd46a219ea0506bad9af6aa69b10c357b96389208134cd53c1bd69a4da8d2a6bb7889734597b2c7cb9c67437bfca559882516cfc613f0277be680ecda2fed9b9d68643ab26b570b3d3bcc76640f5db30f5f9b0dd91addc5d066a76f998d02e2ed4aa05e29e465cfac84422671de8cf848862a7ace1641f0c3d92f0c8331c3914d4c64734b9b975d64473743ac5d0ae9d28f2e3dd13385af9bd8127ffd7dd3a5fa78ea0e354b1c891344102452ad35af259e7ebae952e8f193912e6d17dd4ce1d08de9f0f66bb3155f8d6df6e19095f8e230a985ecb90567f6b9489cd7121f2fd81d219c5afd6621e6c1d8ab6d23ddc5a7eca16eaf54a6a61b1c93e0ea970813892093972364176538b21ba3bb28c50fbc3db31932c159c671b56699cd5009de6c0c6482b3fb2c53e170f65ce12c7bc94e331c9994c2cb9ee51570e49e5daa26ba80ba25533840a8db85c9bebdde9cbd3e4b9cecdbafcc2201f17b33ea567f65f77597ddce6f366a38309ba9cb665983e32daa06c75712070a194f250e1413e7ae93f16cbc87e54bf5d33db48832841dfc8e80b1420a1a2f47b8a6ee67fbd99e10ce2903475903471c6fe70538c39dbe898d8363ebe01882e802b390d7470d38c62e70a4813bd60c6a3fc7609892f1d38bfa70efb4e17a2c170dae41ac94fc8d6e9a2ec1f793a9d7cd1ad41c71863491b6793362a481b76bbaeb199cdb0602f145de49135b220daab4c1114807e912bc0d1f2e7a6a7a37bdd38d500c35b5487d5608a283e96f599665599694d763d29d9493493be99f68869a23ace999e823d23c1bafc6b26aac4cca721c537a185886635e19057abc549315d5a9fd7561182c5d10569de1a0bfa85b96855116655996f58bb2288b5e96926e699ed87259d8855916765943ba05d4019106df680db8e10776830ffc1e838b25f85d66d103705e1040fc106305860a290565240a81b8ba691976591545a72963d7d09aebf5f52613fcaec5ce4d11f943d2f82839926323c449131e2d8cdcd4f48f253e3a4786d8c019142461a2881a50fa1cc1ef436ce48c93243c384680d4fc2c41a24364888f9ee94972054e10204c9cd8594246476559c4162fcf7b3330896169a8f0f2cec929b9debb59da48214e9af06861e426fe58e2a3736448dbf48c93243c3846201058f313f39024d1b1bc19efb28afaba83f0c1a8c15324503db86984fcc0950f8a60895534ce9984690355386c9fcbb4792dcf85cec41984e70295741b01f7a4600f54d27009ee2420a69743ea53f650abcbe7d893630e6ef83c6d70ff6574060a298150ab2b9fe94c9c89919aa133d333eca17936e8336d01a633d44797689e8da7447579ea158575679fa47aa81597f451ab175f7c11be1ff01be197e127433e1b564c548a1eaaa85b60fcfca77592b38624b8fa31f52b52918a14c401a1f5aaca7054f1f87a9356a7f16c34a62e810e14e0f9608a7a4972022a1698a2287aa3dbb274471dd2a0a8675131cb715c55cf94cc70c0aaaa604bcb425d98f733a3c0051b2d70010886c78ab854bc004fa3852ef8e8824d178260788a0458a4261667938b27c6e70dde8be7e269224219ab13183ec2489920368c4e8817462754f89ef034bd7f7581d2699a1ee5e3a47a305dce699a77f698ba3a830dcfe4e1892ed5b5d9135d7c4c019fbc1c813a15d4cbf54753c4cb752aa817eb79fa3c1bf2ef34ec204bf367ba70620b7cd1ec54c56be95f7636b13ea8f88b0f2aeacb7b05c8131507c8930f045c38d1e5c2c1135f3ad1e59ac39507dc97cedbb65ab38be76600474ce2d924c6438cd9886793e8d2afc58a02470a638d67932ef5ebe67b3a018e87d82f895116e2f9e4b95cef2a70ff9a9a3069d49382b2a2ba6569a817eb97f54145112fd67d91867aa92e15adb09a50e19bba4de64f972e1d3bbcd2d5056aa9d7327f7521ce26d1a5be96f9d26c0dbca72f1e196df013224e941e2a4e85f42066ad285837194fd127a59425f8ba78191b0a52cae9f3a494525a5078ef5d31c618e77c5516949473ce39e79c334a0ccbf1307c33e4afbff7de9bd59cf031ce39677d33debc10ca9e685e8cf1595398a689521eb419a69c92146193c6f266c6d8645ef8664c09218431b8095283fb17840f3ef8dec4e70e45e13c75289d1ee8e07eb457cd1729d5b331618c125a519dd042cc117933fa110f153e7352def766c8f89c34b97676baa445bb7045382243382254ec085bbe9419388d70e104bf2df0c3828b20b0f47bbc2800bf1f2b009ba266ea32427dd913f1c9130c047e7fc903c02f0381e91435d79c96c1102e92b095810b1e8c818b1a2ed230878bdaa263773745d198033965c734503a4d2922c540830f60e09793240668d041f4416a721d81be807487ab084d830a1d301cd4f747439ccd31984bb12eebb22c4c7d63cb6607d9dc61abdeed5866ddb22ccbb22cf7428724abfec594cb12618d0b45cbb2ac697d641de5b22c8b1addb252ae728bb26eaddfae6aeb2dab89ac8f9400818e4d585542bb7557eaad732bf5a16f189663f04abddccd31d87ae86eb78e816edd1c23b27e51ccb25ad5340cc3eaadd75b5f22e559ca379bb76d82d90320c67e51ac5a500ac84a397661529e5d18d1eb53ac6b229135b2ac97edb03e42b16addac0db3acfa0dc5c22cab6ed6a65dcaba552deb66cab2d57aa63066653138543109f40bc56e29903b8add9e627387b5ab695a0d7db38d492f8e3611e8dacd220cc2aa85b98e61d9af5a351b023d04dab65048035d170e3d74738f6a73cc75b76b8dabcddca5b0cb666cdbaab6c58832ac0595c239469463440f331183df8b286484ebb160bf6c8ec11748db9e650ffdca324ba9b69d9e5ed84399b41514d26ae835bb39fbcb6c8c08bbd38c599665716ecf8e9255190e2bcb5e1d80f22c233dbb3925dbaecd6ad78175b6633b776ab308534a7f5511ce2aa771b1da7accd6ad9e7e1aa1fc9a18b5d9397bfdda5eedcb76641c877db3da338b6d4884cee5181da1ec46ac3673a72ef35663b603f47a6140d72ecc284694b71b23c2a04b477f6f98e9185d95ecb5a6642837432c12d16da39cc67d136535c3616d9c767396659716c6be59eb229b63b0088745b54bcd9ed583eacd106f598c8c6547a8b31cdcb73fd1ab85b8d6ed127447a05f30db113a04bddeee4016566d22ba6d22cc5daba09a5d825ab218ccd910f6b7fdba9cb76ad92bf7300cf66b0777d061167a15dd1c13b239b66a615703d98afbcb72d4674f47cd2e0362e4457bd05eeb31d0a9e8f4a23677766af3ece12e774c4607d36712f8600a0a6987217a8e135d6a7be06af385a3d1544129a514e5b88fe8455474aa4247a7a1934e6f46a197e2b65f170569e7b86fa0ab23bbc72efdc6bd3620c471dba7d373b6d66bc7b06c853be8dab113d8e413d8607a2c63a738ad7e3a776142cf2ecce85c0c95971ba9702414d125da441bc765d8b773a2ed621947e9751068f2744974ed33c9e829bf46560b8decf61aba19744dc405c129230b718a5da9a26a334977dad5eec55dec372248d3344de36cde72e5ec768ed38e61dab14d0355ed669c053506d97cdd98d9a381bed913d8e8c8b63a7bea76b3c98373ccec91c1c12fe70436d417b367f483ebd7539ec8228d5040d805596e9aa649d3346dd66a7dab2a5741419944a28903598ebb0edf84fd35c9a6fc75e8d7c85a28b6be1998c66916071abd024db7eae8d5ab5145998e72f39b8edd9c7bc488f0f51b31c5e6c63d65631886b5c40e03fb686638aaa9b1fcc35f77308e5d4ac63876f34a632d3a7761dbebaf974384eb7491804f707645c75e43a15028c775141008847ddb4476fbcb96b03e6d77a58a9abd7e4e87b15d65fb2531914541c1eeca76ecd7caf6d0b92cbb2bdb4522f40b6bd3344d2b39524e7a8f09bbc8041887028ce5c8ee86e3cc928078bbc8a2fc6547b9d8e763b6c3fa7661ac4f1746e5dbb5edef98cdd98d1161ec55e56fbbcafde1ca440ce3d8b79b635cd9dd06e3668847a3297bb64d1b37d567df46d3312cc3517d9ab25bb99bb1699b260c9ba669b25e59577c309e43183bf78cc02001fa1876d9259563a7bab472ec5897528ebd3e1ba363280ffdbab08b62b56315d75fbd6d1b3655bed99427e0133c3a56b163157b08c56a8d351d11c5428c412330eeb9e2d031cb3d0284b0afd8d75d28741818ee877ea5d89185598eebc2b0faed31d35145dab1ab7c3b4aca497f184525f42ce5a4936e4a6874ec25e03ebaf5cd08bd07c642dc7561aa5bd5cd3d6e44cc56158eea9785a92e73801eba1489b2972013611b866d58c42e6bbdb24ffb8dc66cfd8d6859afb29b7b60368b3076b7ed18b6617974eaa1c74c476398ea9655dd42220663bfaeab3198e1b8369b03f4ed30d301dab6bba203f4ed394028df2c7c825f26bb033db3a2eb2847c9b6bba26303dd1c23cab62d7bd5d58ed9ec21cb7db375746344186238b06f365394cd213af698e910e1eca263cf21ea8163a603f4d0e5b6a204e821d0c5ee8a0eecdc4bc0ddfa6664c76e7d33b6f758d1518f3d47ddb49ba5c0aea807bed1387055553686092caa30ac12d0332bb21903583442097197c69dbeda8c61cf28a5f9f4dedc0307d76a45c7b827d1dd06fab66ddb4643f428f48e380c604dfbd3ec8d9859884536b3c4b0607890a6699af694107d8a76948b3ea25c3bbd1946cab5d1553494eda06fd744a7dc41df7664c7be5154ae3893707ecd2e76b7d36bf5229bb7bf3760f4807aedd3e93710a5c7ae1d941d54ebf64cc2f5b862b76ea7461904ba9f5e2fcce8d88509bdc6b0e07a6a33e8c6b0e051e83e1859e54a12ca35fa14945f281404a21bdd0e42a1d7ae530ada34aa51aa5d524a29f6eb6507d9ebdb8681eb57ca4918d62ee3352d74d0af10ca2864eb47363f1dac6d2c58bb26036b35b423b0764d437912dd814216e21a5de4e3492410a805a4724127819e7251625840d7b10bfb8dc8d91cc3b2d977a3417f1908c30eaa366300b31cd7632d06d662e0fad15f9748224bc201ef42217fa40aea75d2551e9fbb6f007097213edf3a5ddae992cae79ba7fb8a2e6548f0ccad45e71cc1f35bdff4cdb36164fe8718d4a528eaf699ee04718aa22e8f571288ac793a958f2eb17cbea2892e42bc263b5da22e5f53491f92268bf174e60f8f4f26f327bab48d0d3cddd18207cf4b18a3fa8d66b1b14977f342d8e8a45b46485a87b27da48db4cdbd14e579feacdcda24ba9bbf95813888bbc4ceca33ddd9d961d2a44b2b4e9848494949993ffc653862fc460361db48ff605762dce36b27ba305123cfcecaed1bd2559efb660b2d767876b4e0c16e59a4f914ba0385b450c869e6bec1423b505026a6242a552110b3a506d794a0738ffdf9ab1f6edc3f20ce853857343db410a7fb22d1a7a7dd87767a4f21ea4dbf24d6d3390ba2a24b175be6bbaef11482e882726df4ac8f1da53feaee4749290785b81a38431fda2a4021d1943b1de54ec7029e7e27907d989b5e8540efcdb0ac2e68ba89eb8234b0a2d61469af1cb68540222c542d9363c0d3b1e99b44039ece85ecc3a0e95ae888a6412aa9c4a6439a26683ff48ba2b0c9c22bba0c71485ae0d9618864ca200e910aafc55fb79e1b08ece2cdb74d13c1a91862cf42f00b10f479362acb1482670fe922785ed94692b012a8993e6f6e1a98043cb79b49d3b939fdb60af0c4a2e95b77f7b435e85148dc7f62c31d3a13ddcdd08d0262d0cdf0743a0cbc4d74372f06b2ecf16898cf922e59f0074fcda77f7469dafedef664df15d85702ddcd27eb5f8e1bae988f385a5ce193d3a5fc748e7429431aacd3a510401f12f7c989ba93df2c1410d374e9915e4b5ff3c16e266126a20bf5c983e7255025115da8db399814b047943fb8c1faf5de0531fa6e3c1b8dad7740065b96f59e8b65596f0b6cbdab257a60961c3638c16f097e396ca8c1900964f266509b5956e8b33cd2f2c89b01f3e3a9f166c05f7236944f4e9a6525e544dd4d7f396b7082330d6c7ddea26a74c9b27e2387fee5581587a14a1a6ac31bdd5917c107297aad4b615da20e67a63bbc4b0678b6cc67156ee6b5ccc329523e337d011138e5d3a65b48cf54cb6a8271089b00f183e7330a7e94867ba6fbdcefa27aa20b1127e25633d7c28761ff10248e6a7086e1bb1aeba17aa89e4c6d7ad0eb42c4ad6c28215d05d23f86a47c5173c06adacc21ddcd579776da40789180af5edd954f9b39a44b16870737785644a04daceecdb594adfc464bae769702e12b9bca8664df051f06edb4892e2d07e241fc4594b758f85ae03bfb5e0b11b6b2213dd7226c6abd20e9511890489004e1c302c30bf1b6f0b2883c2f090230fcf57828aa8b9e8aa7aa7a64115cfdfeb2280b0b750b320bb98554835c82050c5fa5cf110c935756bf370a58553d5dcad5ab9eee84e0fb955f97ac2a7c5d18699e7415115d2214220a218410f6a6a1e6ca07cf98032b9fee26842c782eaf743f493ca036a03058b13e5d5af97cb5a4a5ad90e4c82f30bc0a308c392dc030e6cc00c35b2b04c35f7b070c57be124432b9b2acb31c8d6358b08570e5d54e7713663bee576e1410db9be5abbf0c0918792b9eeeeeadb4e86e5e293e55bea05e1806a5bc1512216c865808593d52b552fde1da9dbc1592ee661551ee7f394a8a4c31a74bb94272a44b79dae8e420e952ae7a72a5048820324949978478f856d45dca4955cfb4999fc1f340dc4c057169dc6a87aaaa99482255f0a63c1ebee66a774cfa44ccae9544ea2ed3c0458af0433bbcb421bb2ead5c9ea54bf6f232e615e38793a011f7f3309e0df8f99557b29fd546f54c0add5efb1e01e42bdb5922acb476a58a158bcda03e0cef5421714c6130c3216ff48acd2bbf8743fc4b30cb8f2f8cce4c9b6933336de88c8d8dfcb4913612e7463e4f22f29046c88669135de01b60cf139c3cdce03989c035d0a646a80adebc4f0b7e66009be400e3644a8416c1535e12bba77cbad3209ca0920b7b600f168281b8fc55391b9b7b6deebdf7da0c6103843bc410bf4208c1b59472071097370a08459504726d2c723361d0da4b2b6df6e992921f188857b2676cf7ac42440bec892e2d37e690ba4ac47db1c5882a0712603e378eaf1b82be962186f8e419628821aa9c1cc72c7106e175f3d38a57e54023a60d24c0fc9c55e079029e3309784e20bc19f3b9b2029eeda34b40a01a9ecbb4892db3830c31df3f88747143073c27ce17433c4f1b000000040af54025b0b15d790262f82824aefec486abfb3fd319205e1da6ba30afbb7b1988ab21374549a58c744988cf5338d185888f5e7dd25436f6f7666a9e081b8b74cb3c006c34d2ddfc1036e2b0d87845951912d35394d44cf5c0503e3210c7233127ba54331cf6a88f792f848d4420118cf25c0476222d6c622193eee6af854a66ecdd43859729a419e3b75410560662202ea50486cd10c3f894d199eee625102a7c8c8bced09967030e897195bfa7a272697fb05089853eddcd4fd9ccfc24cdd4384931e6afffa22d80a7818fd48712daa18dc2faca8a7d18867d58c5420149160a9962a19028367f7e643326b299341fb25050905da976489982b0274eaa07cf6bcfaa5bed7489de5afdc41eb7238085121f210c41fb81d80f7eb20ba906b985cc02bfcb2d3ca973052d27ba68353535115eaae5d464c953135de416afa5af86205dd8d081c81737b8679e20b79074905d4835e07e4b27b8cf43c549cdd3a9062678fe414882235611c1fd69ca740c816350526573b5a1e6cac6471e211dd1840f2248e00f109c4e323344272ea99146284c5251ab693373306780df6cc154017e975f3c97ec35795a209148241249e2f7ac27ba6839b1a54f55524604ce5a0e1138673db82f79569e8b56135bfa941002201de04660ade60ab87f6939d145e5b5f4ad4ecb4e01787027a007f7cbd1de206d8e635aa92b7bde15dc5de31c9c2b1b4841ada64b40b42be625c1b87aaa56a3a44bb9b2f1e95226499e2ee59aa44b534fdb1ca97c7edc68613511927329a129b2833109f26c34ee67573c1b2db54072e40852102ab5aaa8d1cdf810a41f2a0f6e9ed7d267782e954d6ce99ef39072ba39c5f740b243f50277ac241bb04a015ecb15d91cc7b32909a7e8a1c24b53bca82b51484cffc486e99dfa53b6239ede28209e8789954d97863c1b48baf45e94adfdc080830d965a0dee0b79d04608a638c04d6b384af35a3ad2173c97e9b406b85da63bdbd21fdd35cd909ab11b4a13b11b0afa60373434f1664aa3f212258ccf2c388ae86b89af8b7f46bc4780f8f7952a2094326a355314ae4ef6a466a2cc1cd09f58674ab07417839a2452d76e70825bb611ae5b39d4aa3217ed342ed8398ea7b79ceeca74ea62dd5156126a144f6c18feba2c16691c9184049a8831818f1a21448ce420e1691c7f69d6c7112e0aa7f7cd175ba760670e60e94e74bb9a840a458e87a9e780ef11205b876f1082db01ad440ab0f5ae4bd7e3a54414106788a737fcc07dfa6c3c9c3960c387fd04c45357803723d22baf89c96703e28e1de1d4e4275b23a7004b2c2f8d372316a9f9ffa9b2c0020c448051b2a3a41f15e3843142a824063c86c97e078ae8fa9a8a50fe782e359e2148fbfc48f9dc3e524a09654bd9ed438be80e3e0435bf1d1e10c207a918e3d43f5305f3375d7a9fe8a789fe7d87a40373e4a34edd2c23e9e40423f0c609324897a40eae2c0dd9f076b12582a0f6e1779a88d4fbe11167b703df3cc479f306e22967a616e015f0078434d088e9d19b5fce842e900fd2c8862e80f438a8862411d45c4411b12fd62def50f4a00b62843f705b1217f0be38d34d68507a27fae94928a7a9d325486ded0eebee614c74d7f740cdb0a60742089d883f3f4df5e4f6c112c6996ed23ed4f4e04dcf26ff58ae4aa68148fc284ad17704756b18001a51ddfca8070d10813ccabeeea87d75c65bb10fd4fc7672785014bcf14ebd1a4f1a20dafa5385175c8169e097f3821f1853c30c16007e395c30c1dd83e7319a1ff21335654a5858f6679663a2917dec4797e43b42201f5233d421f25ee0214e8444601178f39a7aaf214dbbbc97f73229bbc709d7f441b453e7cde8db50024ee0b9d989f231f2ecf04c1e25d4f71b58042331c6d983eeab460d7587e749cc02bfc79da6993c780b18e69eb2d802942f57cd45a383fa9e2f1a0cdfb2ffa28c7707084838fcced47936faef7007c73b7578501f7cf783ddf08a26c0f8de7b3109f59d065bfc63912fba881af7ad4ca84f76092e210e4570fcf1b5a9e4388e548a1e6abc94110997ca15b569f0cb02b865eceeee31c109645ad67b778af1d429fae285efbdf7ea3eda8ee5985e96ee6037df750765c0cf372511ae773385bb77ef762c1493fa2019ae1f92280d9ce5c7f0cfe6febb3e4b775d77b0dfa576812ff0e5e4018b22acc08a15f8f7d2941ea2f0687040045f5ede5fd6b70af917485e8e12a6438c44aa249a2a68dceefeed50330df89259d78a8a4774d9b1ba9be8d20abc4683b3464359262c8feefaf5d0882dfd6bdaa17b7a68d45961d62d6b7ecab00a0775d237fcf05c36f0623c1bd6a921707781fbd7fdc10bb06e136fc617f54522d489a19058661ab9621ed83a8c3134f0743ab176a9dea72a5ba35b9ac294c2fd3ad9a01ee7d7a3872138bf1f1a35ba6b29600f2aad2d50d90fea217e5814810db8027406c7bf5030801d839a5f939c48ed204e4daa28a91dba34fd488a4e5352749a724e39e58dd862292a6a7e4da69ee822a30f8e7d3aa349e19a314218e3af098bb983c70def84917ec89e8c1d7928aa5612e91d0ffcfe304bfc13d3b3096e142a983ebfcf7714455114759fa02c3ea838ff654fbc7bdf7d627a86f2f3cfbe27e17d825e2be4a77b1a5811a2ee17270faecb724286c63bc3314596494acacb32d49c90cbe87d14744974d2fb492cd12518eff3783654de2958ab9a76372a056f8737a7e07c9cd9fa6963261f7a8f4e1dba8c5efda22cc782ab3fe061ea5615cc1351446c45b55eed09ac27a8e795cb830af8827214cbb2534c8102299e54818b39e4610f7c78f2629d052947b9565857b1a09b62a598e23d79a15179211dda27ac20dd16d4d78bf258501fca59504f3a0b526e3d0b4897145bde534eddc71650c716eef5758bc8caa042f7af419507aeac3f3ceb2fcb66af768aec28e8f1924d611d053d5eaccb82eb6510b853588716bb99c270064fecf96581fbd96561e08a4cc43be0738533bc565819be8cfa7b5c3d8cf7aebcb6facbd18510f8eae6e842c8bbc2143418423823737421a4bb874fa33bcc226346f6905495c92167fefce8093299c01a9b2145a693191f9c1e254b7ae6cfcf749233ede0b6be824d42ba94e78f8561d627223e66bad425149be9c6c87445bb3c3bec4c3f6aa24bbff4b1ca6aa2f333994c275d822bc0fd1f2518bb45319ae1988f72a4ee0e631d7bb68e1d9b939befaf54d6637449d4dd4a7591685cf9e81e4904e5f9c06ce6f0c4e5d1a127870e3eb85f2b6b4e335df2d125192372d4a753c7ae15d72d6b4e285dae3bcd4c4bba7bd7afd3639795ddd233c3b1d2826addba5d9974f7b008c39b313c5591a2e261f88c9866f0ebf1926bf53a88fb55577dbda2288abaf9aa0eabaaaa705033c351eda0a8ca4afc5e093e57bf7e55b63aad28765a55d5cdd8a5baa3bfe82d7ad92ef2853e66382a89219d56784c7f78aaa921e86e3abd4d7437bdeb50fbf9f554e182e25d86c239568977fac6113fcc0a0f0a69176a089e8741ba94cf44a4a85a49a4fff1901e0f85462e3a873429ba345184aa14a99ed49d4647c2b988f942913a1a45e039e76422b6ccae03cc41c344a789d24c5315b23012853c78b1a38497193ce1290665c013a93b48d50109b6b430230b64e09763872be0fa0422f0cba9431d306604521de81000fc72ec900339f000c656810e86789a931452277e394fb80043ddf0cb79b2733dc1e2c29e201161f91b5fe06079067469d2c11c7e394f6e9e0001e7d390d10baf082c4f63e48e56419f6041884e12f880e9cdf3524ba294a24498b680522ac2f4471430a5c1747ab20cd50e264dc842055ec4e00b2bbcbc27586241d383e565cc210d38c30f0b1a2e4c3c5ced82238ee8523e354980c6d3dbe3e5f55b79afd165e55d1ea497fe9484fa2358ded85a5cf0b20b353f1f9af6b18522e8bcc087408994d0a7a749853d21a0a920cc71b202fc425049380ff5611af842ef838a05d0c097957957261093785183272f34891740665ee8a7bb323ffdbd4c80a200054100b264e685de95797950a7b9c6740769624b4d6c996aaa133704ddbdc75033a48134d1e5b5a1093c48c0c20eb248c3ce0b7c8db2f60f9c2e9041d708a0a186daa731a875c993a21b5cf44774d731e045244a5235c68b3deb243961821ad45c2b3bd4fc7e96d4646fe0913d21f0e287090c2cf9e1e91142cd540d2a4cd2c31373b250a5cf9bd1cff1b0ece2cd6819a99798135b1ade1c7f7a7878787872de0e0f09c81f1fc89cbe699c213553110b153ef7cd0d0d8973c30b1a358ec4971824b688a1e60e7732644e4d133c4c789183fb1850f2721ada24f650f3e36182e73ebae0caa9a2cbbcf76874b34243cd9d1575a8449e8cd73d119633a7d079f103f765d480394f06844aa834deed280c6aae50b67cf75e272347563107382c993514a505ee63f254fc65dee0be0c193528852c36be3775f2a711d0bf2c30d42fd4dc3e3f5d82986a9f25d64fcdb3a626baf4674d0deed6892eb28bd7d2d7a93ad027ee5c486a7e393718306f60a50368936bc5c17d11b481438cc88b9a482589baa4d34420112245aec09d6303875c5fa819fea839dee07ebc8937427a08111a9c9b4817b17ed44c3511dca7b06973a38822e06083fb4724b1c44bf28ec8fc091ae32920104208effb7cb432d4d4fd684585771ed15dff7a37c760f80831d4fc74e27112308925ba14a38c33d4dc755746775da8b9e22afae99a19521313c0c107eea380c262401ff38fa5638c31c61823a513a52845297aa3e9c5a6354177338d803e5d43cdaf8909662c39d44c4d23978ff99946004d23a05f715173cf482fea650d0cd23472a66796e07efb90333e2490fe813b086e1fdd354d773df366b4f4892ef27d5883e195333de3d32da99927a7192b54fcb4c07d59452c988cb6921a01fd90192a753925948a9aeb8e4e9be0c1086184113e9b63e49b6fbef9de7b57a6bbbeb4e050f3fbc1f37d1938bd9fceb5767104f7454ec8401f3170a4f4c207217c10bec719a738c52946118e2f7e62ddf574339c71c20432b40d35c31ea804833f1515177c792c5617b56190dc418a50580c0844c80b6abf6770864156e0dfdb260c3c92e80208ee2ff1b4e853f11739610d1922d24b82472320be4a438dbf2b5560418419dc3df10a3628a914d5850fdcc748316488a63f16b9806bc524bd11d09f34a8f1ef05eb8cf40a6a3f5ee1e383db474849e50d4ef08b0877139c597cc09988869e970c7e489796dc6099c625901497c30d49babb727461132f88f5cd10fc700e3718e9d2cb821634f8612d4717365dba39bab0c18fa55bc826e7e79c3b60a69c179c3073d249270b33df3a7a4ed37c7f769c70a2d36c0b33f5a0139d60a6c6263a4df3493a052b84603dc65f13052f4b891434be7e41aab33210c35faf5f59566559958ce9329e8a11fb8bb471acaa595573aa5edd4c61965aca3e7ccbb2e02dcbcae8dbca49adebba60436aaa2aaaaa286a55546555d52d98caaaac8ab230555559535551f44edb0f2245a0875760780b093eee4f78b3f54c097ae121b417bc0e6f6681629281385ed2c63f49a34bbdc3e2fe8d485191ba24916addde375314f547dd47f8a246fd46bf2722a66420113175a937637e0ea1d2439c69603470cc0818f06c74d5a5faf91a457469feaa16ebaebb8cbbd1d56aa7ec8b82c24fc43ecce488b8e38d8e1656168a89379b2b9c5d6cbaef3a3c0cfa3e84395d9a4e1b5221d407a110aa05ad755f770f36bcf16c440c5b0548c0eabd1994b65d51e2e1eb9258052b295488214565ead5a985a1ded89b01ad23d4f71b0ce812fdec9b255ed1119f3c99de0fb7c51b8a4829a59452ca25dd3b3728c1f1328ae02207c7b35031a66088125eed8e0a351fbad4e8963ebc346240a307efe26287891d31d0330c717a78e74f7419e2be6e41f9bc280d2994a30861a51042087b0494cb4b77500e518ef297e140a13b5daa1edd61f9c4a4c90f45fd30a17ee0fc994cba84f2799ac5739135740b3ca91af0a45de059ddc9a48774b2663ac1f34024911fd266f281e7e56561d864d2dd0ff59b4fef21afc90c9ce80ec5b2b8cf465715c7dd67156c137d5026b4099e993ac1f308cd70b09c6638e82b8b62bbeeee2f6857aa8b04f5294be261485d96ee2ef5207521864279e80ed5a208d5faf4dc37d7a24cccdc3678f611968fec0acb7ddd517f98ea2175d6ee9bbee91bab24a9caba3b2b409c096844f5790b5ce095ae10a021401408f45b514308712d8aaa95b4b3b34365ebd5afaa8d587dd3377467c787fed026344997b2d3259409eda14a300f1e3fc498da48d5377dc3a30ae2f257a66d2c42b06841844e93f474492ad9b997565a1e419cfaa531f95503a7b6a8d44bd7aa233dac18d108020000b314002028100a8704c3e18040cd54c10f14000f93a24e6a4c18a94910a41443c61884081000018011101001860900f66e6a32d46b91df771e695ca8fd92caa2f7b9edf4f7362ad8bb9bfc37b69a8251164f06e1370eb5d91a807a4cecf55bb649271e90a13cd6147ae874f650569e84cc82166f0d68aa5969bf038981d08fcccec7040c97a401b10876a50a48a7769c77866ae91cc7c940a20a17389df9511c41477c1b8e608416868b2e8d945f53e5fc84ee28e0d67e8b01ecf9046290065ee280e803e8433e98915c2b312b9d651172e21d16c2ca3130b45eaa04f2214aa0c4df80299a8e41369703ad9efb52437c6cb51ce312964526ed9d47f0e80353a0dc0c05051223a6c24fe3c3d1f4d9200ebc8105df00e13481430dd7cd20ee176ba984f6d9018eae3d5478d24c5d8abaeb10af775771ea3f6d4ce1c5586b1d6465cf45fa5691182e820dcb6aa3ef122c633d3d53ed440694343a9cd13b1d38c1479c6623bf5782b9bd46c3605c068f95a10be025ac21623c6f76950860e8b4e776b9b65756bb1a5e8658f47c47dbd4945b5e2dbbed840a6e242e735dbff10a400285d43844418acce8fb91aae745346ef71a6f4e6eac6f2c44b4f4c06bfb1851ac4f258a578cde6b7615d3df90e3eaa21f2fdee3ee3cde93c8b76cd9e7dbef139c710b7f76f35e8f971d5dbf59b257854948124cce97e930f8500b22146e46ee73a7dd901b57b968d7e3c8c5fe22b418a7b299b01f103938150f0fbc1fb3e714ac7f2cb8f638ab5279d9a3a0252920415f2b7c35a27c85e09a39d39300b9d66d2041bae16de4d1e31f9ba88f2ebb0562c28e3240dff092cc52d56acec81ee23445698d0361925756f968b7012581bcf89a88b8cca38a27960dd8d248d5bb94075317f4f6383b47a46e5e2c01be305f4f2a06ea96a9fca938c52e2a9763599d7879643675d49415ac0016b7e703179a29b9ae055d50b00ae04d6e4242aff08fff044b04b3ce871a01dc80cc0e5741783bcbea2e7d213e594863849cf944b19e4d9309392a2e27b32e1c6d3291a7525519b1ce063dc21bf15866fa09b430b7f77895c19aec028c2bea9b09c9ca04d3a6e83ee1db93941d059f8ba63927189d1bb1baa390b091282620b4de9f4d0564251762e7601c5b8342bce4cd97304955062c5fbac93d2f19da45fa2b84f02a915184d129bbbbf82f3187b9f3d84d3e3bbc26205979842cdbea469751d100d57696f59bfd9d7aee2f339039f4b7367a7bbeca9e0fd59a17db6e65f16b7a89d50d0e2aac097199a6a544bf684d6ff1bec78d60d3f3c6c38fee1440871d92b095455f46114574a2882a6a2dee8d82285c1b74107bcf94ccd306651cb584c41ce20384b9f66101969a33edd7b8494bf4a8d1b90b75821677c9e2bc0bce2b792c58f9850849495ab902606cc32b808d2e2241c61a665012ee90309007b25cc35d0ef18b88f23a43e2f7ed14ad116892091d230c3add627c3cfa7e0b5514e590bb8ad54847bfcc9ece10c41bb1d7c55f3be737ffcbd3dc04328dba2c1481278fb6ee7a5b2f1e5425c75b7fa416effddedfc703a1dec1a8baab1b388f2b1ce5225f54081c66e6c30b8c80aab15d898366c5964cb7a02034fb68c0de3c85e09a89d3b3ff13b160fce8b83e4a18691389b1b2e71367e41385687ccc1fbd6e4ed4c1b96c6144099d9fe466cc417d2f1c4d0d09fd587b04df8578beee89d641351d2bd0c4bb5d7a8854ad3208de078981c894d5ea3f5084b72337bda0c6ca356518d2e97d54aa77f7cf33a457259d396bdc15b21f767883c05b1f65006d44f0063ecc8610bf64a7eeec17f192a664a14fc4b9aa499ae878a276632fd3556e218a4cad95e30e83b6cd26f4224e32bd6edb0a1e63e2146cea7fbf73936e2a84577d35d59847ced1008632b29131ccedc36b2b0d70db5d05ddf92128d8c59c97408f082c7e20214b6b0273d5a6e7ead29ab9248b2706bace51425bd313230bc1bcb5c06677ccc601a7dffb52d63c31fccbe1b1321087b340f55baa40b479d926cf9580431188e3fb3f5e8ee59a3bee4cac4f1eaf61858255e9b1edfdf62c7ff29629518f42e7dd82dda217c5cef35d425a7063d61566e1eb14d5d3f0a740916da19e9698169bd02a519747d9183c5169a0f347bfe879b89141bd717cbad10f6ca797f771ddb7f8c896d28fef91da110974e586417fee9d854d9a8ef5883891201357b7ddff001940483ab0f142bce4db06830b116533982a36398525ee299b33df7fe844c86124390873e0c9be44c94b1ce5b15a06b602ca4ebfcc1ff55399af94a9b856a4e1ddb49f252844a333c8e6abb0f5fc88c352f03d145fbefe4093cf8413f7a579ab0c68624a9d085b6941b49a2fbfeb68746c901c9333c4c5aade623505bc3cea220600739112e6a7064cb089af15c58b5185e87a98a7506546465489e02128cb490a01dcc1e3744fc6077607824715866646ce3b713c0c46da0280a555f159b800857013020a2a2e64c5a7b640d2e92246ae154660cbc14e283824e3e44a343025e34f320b803abb59fa609cd7710b4827e245044ac9e0f7bbcf891702a10d359be46d5a4397f784c3c5b9643fb975d22db01d8bf32fba933b53498716f05631c7f43bd6da73642e76bb94a5753ad30c9e17d6ce2f86c40b2282883d865e6c7c75bc38b2fd4d807b657a6dc1161c7ee0709d193303244ee29148f0d5559b3d023c8746b596099c54515cabc507b12195049137a2d119beaa2326925c6bebbb61813f1960b2b0b7ab6d768e1f638274d9742c5fc41b47d2fd6fc6105d7134a45da08d5720d2b8922fded4eb49f2fea35ae4e9366324265865931660b0fc1120c98323a64bd93d1e723658855e506201de9a9295b42ab05c285e32356865844c7244f4fc02226dfff217eb1df29d2f3c90e36a7721c6e8be1c311a8268717ada1649348af48b79f9628f451112b3204e88bde9817fb8efd15f98377b0bf5ad9aa8ca8b95fad4e6443730339a1790bc74e54bdb5e15387dd011af2ad4d86e65c4793960a61e369a369a2d9149e140f91a66a9d75abe4eace9265b65029016d17783b6ddafaa3ad89a4b7dbb08ce0c3125c890d045450d6bde7e378c0b43a18ab5162e3c236c6c014c4de95f87396d0a94409e208de0c2a675da527c9be9c18c023d54c90381124c3ac74e2af25c67cc1ab02c3a7f2ba661046394fe260320c2ce9b72d207bf95176ff25cb0bc62ae6443b8c27aaa0d5141f3df5f8ae6611345938572b23cff29ac53baf9817d234e977e040073592d9bc1661f9da88709170efe71a4d22728630e1b0ffe7256a5ffb9d3d571e770b946ca4f7b5a1e086912282528053f0ea392dc3824111c59b13f1711452e39e7b5aadd0c862e6bfe7904f0994084f0401d4833bdfad94dfe02f9ec9015c8d7eddcae1039b672eaf5745d769f991f5050485aa9fa41e91efa5a69d29bbe9b920c9adf394a52951808e91171137b8543c9cd2be169e8dea0616abb77a86db7166c1d43234b658564dc6c0003627d5415e2f684365834b9795ce168720bdee5de74346d56ddc97541045f32555ca381392324173b64e9a5c6e3d2f010f3c36872b8c238190056a18521d329c2db9bbf232eece7e82ef7c6ef58a3fc03c6cdfa705b673b7daec36d0643eb01995f512f12a45add3183de5bb5be0b67d3a79f32375093b42ea37e8ee6cf13542cf7e4fec751cc1884c3b0f1dc31f8e81c36f1ccbc95bba64b4da7036ba93f39e3dac09a5e2df3f0ebb3085b525a5aa074023b4957c19fbe5b327534707cec4fe241a831ca9f3a5a69f7e03e6ac174e322cf8eea1dbb135870412e3c5c02c9347ab392d372ad23ce724f461f0264f7caf93d64e62de4b939d90c6cb0e2d79f24929a06e7a2cf95f41fc7531db054493f075978712079ac9df3763b2bf908558a11142d579f6d432e598acfc209d64f5b90cda7d938623e194072ee76e3c3062a0ebd4a7ef04eed284937ab16aa97c789c9d5c051f2a59c4685cd8183a3a49e4361509873078dc57011dc9b2450f2bf5f3b938ac04859c5a592484d147c117fab3565909aa545e8fcb504a8c539723d46105961aadd5ecc05570b80cce37036430a14b4cf1892536ac2697d0ca53402db0463bf104f39edb9cd86b3acaf6dfcbf8ecc6403ada4451612b6e5680453c9e96078c5ec8218e33e19b59e52ce0d90afc8b06468163b310be408685638e52a41c815083c1cb9ed45c824e0b59cc3bef3c994073694e3d46dc6b81ef11e6b674b64225edf4678d888304f404f464dd5da7a1181dfa42280ffcd24b65ff1b5d4e4cf51461995662cb17c3b3a0bb974965bf35a7b8c5d8848594f58bb58d5574c95244ea485c13e6f81343a2a1cf51de9896c6a8c8c83f5e5dfd0a082215d0bb4a1983fd307dfe1d2e97cf11c2bc889086e915aa864b650bbf16a6184af2a452211d960e1fc15095b8a3b424406a6d0a1bd74c3dd342e2561492a0392f3451ddd74845973ad12c15a39b3f65ddcca529590344253bbc17fbdb17f202a336230da51c89907b209d254b6618128a7dca8597818d4d6de42d367fe0240e12512c1c48dd95ec8a6cead982c2369b7d0ac7ead567b343bfaf299437f259d5cbf4be0a34963792d77dd1bbf9d44e0d0f48b7a2368093e4aa5e2ea8e31a66a6aca19ca2b95bd49b3fcc04b385131457f9040585c740accaa3f0e228fdda5156cf9ada7d77fc97570126a218b9e567cdb69355b82a611ce9be0324eb9639a22fd41340ec7d0fe15c6e495b4a27377e07f0453feca938bd555d8078269c1b2fc6ad145a1da882d50d7dd51683b9295acf8b63f88ec6dd97c1d79d6edb0adf007f4dd0556d2ebbb749796ecbf29dda7b2922267c92380a6e4e7d48fa82b089d62ffc2ad38b26f70017c22f0741e228e72fb118c6cd731ed67ed52575488fa0561dc5fc3609620833c4e4a68a67adffdc39928455c852ca62826df68bb8511e83fd62ec300b88f39d2a0e0023e726142834021841280bd9a8ee8040220d1507115543f3b3e2078ad6399327bb3893d87b8372540ca02c0bfd51c96e4a38f62cad7b89f44de9fc04cdcea00ea502d5c7d4e1a20ae611fc60b0f116208673a6856528608a42515cc0904710fc057518fbe0ab829815555fc85d3d597189943c98d5c94e8884aeb2858b1835bcb6ec7f290ec2587e279d7aebcb7bc8873be07a241f08c6351340ca62801d096dc7556cbd90afe63af1c4843a1cfbe13118a2dbf5fe4dd2c7a6d2082afdb6ff649a85abec01c4f251ca374b4f8ecfd948300d075e3e6c67294d245ddcd7dcc6d9d85722e899f2694f893e746667591dff4db59ff501fd50108494a73ce45ce18513c6b8d73f1d30dfe03a143a5740afd3853aec364f45a0d40edb8acfad59869ab3896555412d7c9e6acdf6ee6e4bb6e4249facc35f4a7189bee782dc4d9a3ef904563656075b5e254ce257df1db7a352c1fd3f133a4fde19f83430d76a996c28e92aca4a7d6397a81af18235a80a83182aca32cfd67826d5127a343748fe9050e99205cd4ecb5432292e34fc4d7384a00ac6c8ae86d1788617205617c2ca027918acf16a907a45eb57c3aa1fdc259981a56d8ecd65e6e16a0675fa5446f1b807fb0fcd2439170e8313778bce2676929c395548795a9faf92fa0b532ecb13b15ae71f58037b27d3b223b176425f2a86f48cf4792c3842d9f28273232612d2592031a17bb5b0a8c0d69b58aa36e9474d9abb948a6b81937464f789828be247534ffef26a8163e5bc012037a781b9f0085897232cbc7eb498bc9f43936d7c70d789c9a99332ba74ce7c2a21018b43e2fd5d9408ab3a2782170fbf56fc58794e1940ccc350a450458d589eba9d8dfc8a6b30238951cc20cfcca0b334e968e9922d52466f9c5166b4e170e27349981bab6b6c4b514f515378e3de81c895496b1368d82be5a8198a1370f3954ad9eabd326b452e53c4dc4c8f9c9bc975b95bd102e3ac2140a3d069ce2b7cd87e88bc682c84c9e2ae70f4fb8214148d9f139f0a4719977fc6787ae2a420f5c0be8deb8549a8f5fd2ceda23f234cd9f7ff94da8ec1823c7a5a472952f30e3b87e168eae7ec17a34ada7b48c9a0a3305b7125048f31e9d6ac2eba59250abf51fd12b4932c92b4de3f8606889957b8f071659a91c3cfb603a57f4dbcfba51717b0bebf6f42b1599f2fea5dc4cbec054ec607849d2596673c7e226b2a05c00b28dc73f3c9d1a872c9fbe6671d3abd9d40d04076fcd41194458326352df07147c59c77ee7eb2dfc51b93e1fc6bc4b997d99d61f67b91b3b50690294f5ee5e0a2e0753c8aceb6679fce059762eb44d206884516b2cf39c20a260d3226977186ea22895a6c691e434fb39220bae91172874e5c22e7887fb88ece9259a509df7bca82621a30bca8ecb20c1bc90999840f399bb779d97b0560de369c41cbc53d48649840489a96b9bc774c93d7f588ff9dd4001b8140a2bd5f9a28b5a9dd3bb267ad22d918ba0d04e90ff6014f2a9e008821f0c44a00f3b008c146679cb27c274fc4564815038d53bbc9eea30660c9bf00dfe43e7ea5d2c0b5864ba1c96c28370a805059b0b52be5bea140c781ea8c847c1fd9c41c144e7af88d3b45ba92355486dec20d6a62d31567b6de73d3cc6d3e3a81930cd802de0abe26dc054bcdb2e990102e97c02d37ca64dfbd06375ab80dd07dc3465a8b9219c94fc02a49aaec29d20614c875443373572c41ce6bd03ddbe7655a324a5c40181fb371394d03dc98946f8fd8a00258baaef7d2ce2d1e1293c704de7e9ca9b2a224020f9bb4505006e7dbdc178649172c7bb2a2fa05529078bd0ac0c521eeb34218955d7b7726f3ef67fb8c8f2cfbf1dd31798d24ad29a7537a6793bc5ecfad1150b967a0547352363e88896b8805ae0d1ee046f3c4614684c001ac2317e34f21964d6d0fe56c9178fc2a80cf0ebb8cacf00ed6a8e9b296955baea4e8ed736146c805ffd3b11f0c3ac73adbe8619a85eb343e50f2f8c71490337d1a6c7b022e670f996b53b55df6fa9d751cb36f5729a224833a9494fa99d036a7cb15e077275f18cd745b01e5ba2ab68f6fc5e512b9b59896013a60382a9032480a855736f8be3329ca9f80c79c23fb197c4ab8d704f3b08905f379c78bd6d2c2dc02a20e8c8f1182af2a27566c9e318f7c067e51531b5da76105c545093596021b84d224461416e16404e3144f2cdbedef80092b4e79611b759ba3d9d3bcaa49fc514ac689230b97b5247ed75d6ecf4dd728b30689fd5b68ece365e9438b214f4d7b068b703c5df04b2cdb9c51824b5e35365e8e720a6ffd68979808582a031496c7a38b7f98a767e24235020354894939754aeb5e4b25e0f96ca4dd9c71bece173ca0abee2f6fcb0a6d1c5916acd766ef9ee067318c0b4ca166c069fefa748951983f1c65cdb4163339bacf370a7c8de7d6b242d7b9e8db9f514845f26b26250d4ac15d3c83b4ec30515b2faf5ccc6c57901f8a8a09a8d490a552cad227bfe610b3b94d334d0311725051f028e468c3e949e22a75aa0c6ad67267794e6ab36dd86c881e5be5733acde7572c8b9ed3a6a0291aa67c832e32adb9b1709454ad12e3cd5cbb4ac9c6fa4f5d495ad4b1b3416dc7cc91ea5f25dc07c9bb644accd06b633d678cabb5d6d300e8dea2b178553b1842059aff1794dce36a3f2553956ebc1e69e830e89307509ba3cab81021868f4467be4156182080bca66a79579fa9b25fffdb4ebdc2a17de6a3b25c0f630ff24e6e3de29801960d526605557e211ba4d6396e077bd0083ed11602d95780724b8ceb79f342fe8b3bc2296a653e3bdb1447fdd742435f69324980d91c05b7a57b4cd4fdba89650fe928117a27565da6880b17507f18636a29f1ab9cafcc6da4faa31139df554b561b5b1107303dde8497d7fc3ab399297a7d6438a6df0df39ee5019da39100e19b14a3eb75143d42e2d4f175c23d4e0050b593cf4d4cf8a9d57e57994c36558bd43d24526006fe3c016918e8baa8aba3c0698d7cfb4e0469580eda73d06bc534a9c7c41cc8f596f18da082fd17fffa5448000920bfb10e6f9e5e17bd358664fe895fd017082207dc819e4b8f5f48fd244901b26d12e5560f78db9610163c3586513f50ffba48e07a4a998ae2b1032b2efc25f86e63abd34525556691a4a45e22c96894278d45fb3988cf8fe5aef26c0f100b7d38213f0dc24fdc2135f70ddf5414e7f0fd40fb37cbc01c7cd596818e97c3d535346397db8b90a52a6a6884bd7d26b49e6f0bddcea9412cb43a5d6a3ea3631e217eee162ea29586c84abe88bb86ce478e8cbfa6c842784bec0bdf2763b87c5268b339c17d25914adc478aacefd6c1d36109cfba9cfb809cf0bed09b067449a714f430d3373abe11c6063a0254e65de66979c70e2a26191ed3bd2702235ba97491a2d43d1bf4151023fe184e733400d09dd5816a790ca877e8c14e8a020c04e7df30ce8522f690e982347dc54ae4063aa8ff46916fc4724ab3a79996985ec9eba376777f58eb85ae4c35dbe52c125ebbb741744cb340fa7550f751142d63e867b039a068b25c23df8c6bf3ed26b7439f5030f27f3fb034fd3dbdd4d8100d4dc8bb8d7609018d993a61c8c368f82cb419cf77ed37e64636e6eadc866b4c8dba96f3a68dd45fc2de76608a19651c470f5d1c77a9581c5ff68acdf96ec41a83e8f53ed58542aa11ae165b95927aa59085b432a8ed310306a2aaba1e003b303c2ecb2b8ca3d8462fa07e690b124e3118e63f3f188c6cbb12b95a23ee03f680a1b1034d704d36e0c72e98d083d4f5c46f22226a78034664c57fe1632d53362e62e2ded1b00e1f18176e08a0934d7a1fee38fcb6fc8e2500cdfd60a5e845e68bd91b73ec2fbd6e9372733e99779a6657e3468db4fd1000fc14c09b3bbe85828c5e34519231f5d9570d9c853825cfb9595806fbf8e8fea108033b9cbbcbb78fd1e8246723c34987d870e7f2ca6633bc42e07d43dfd07a2bd0e5d30abe5044a4d25fa05b473932c2edc0d5f96ffb2c925060fa95773caf600721407b21e445342bf07e5a8f2aa4f34df6eb0ccea83d8cd3c925b9cc4ce143d2a06ec266f77e9af83fe323fa21f8c90128ad57db0ce05f1482b176c506f52e0d3c67229609b40a350e1484c3025989343f8e1278d72056bad106bfc25682a9e26a0f4d6f48590bce6d9ae2f60c80f9b8a74d4c30e293de64f514ef3e92291fd35d3e60cef173d9807958b7af3264419ab6ec09e80d1ea8be13a11f6f69cd527c793e6d2ce8a2c25dbcd2b7d83c0210b08dfd7d6d0fc787c356c2a15c4822a9b52e63e2343551dd1d639400650a6e0a3e180a61113ed73513ebb307f972c5fbcc5cbd54853763d216d150f9142339223321830b05999c54e48e9c814c37a273331bf144d6398a4aaf56ae68bae89753374cd4c41e2efa41c111382f1bcaa86dfa0186d89ba38f620a467244890a9821b2133337adf038e08342400014d071762feac6dea073bd1346f9619b29d797d8088430b24e6be9b71bf3edf8c7cee888cd8826eb7859252ae338a6b84fae4dbeace5c1a36b19a92a638a85e64f57a02c2fa746a426427ad4e6f0e1e011a092184af3b6c30c24a106b4cd0c8b7738b81ec0a799c72b7202ee950c563b880fc8bac35229745683f9ed4014f184c70155e12c0bad6d2b1cb18830fa7f5e6eeaf461932a6c01a00a2a6dd52d9fa4ceb86689c7037cb566b060c1f8adc13f7f69a4197f0c0f384398b1dacc05afbf8192b99e2c2c11e2fa8613cd1380a81a727ef5a1f67af209d01f4c766c8d5e64e605bca85b5ed0164b009578d1c85d3afd46cd574e423cd84f4688da8642d8a3d89a69a69c61a365f30ae4a501eb7f07c70a27017f945baea07bac1c02d74eea5c4955d5c1e55b6bb1c63b2204938729ad61a12f278937de63846a15c58068bd09e616d689fa8acdc4a75cfdbf120324e0461784009b6ba78fb268e020042ce31fe179f31c71984065a7091f713eb5d69ab4eb8cfa0ddbaef1a1b7f0a10f3c837897d0c71d983f1b782c578ca16205aab3996abacef9b0412aee4d6359371045c3d66359ad310f8de7c40d9531aa633e18f78e2f1d8107fe3a0da6232f35f0c3a86e1f53c7d96a55207f1f6c24e39109ff818b0854ff5b15040c4bcbfdf417c78fd47987bac82e648c0cb935bc4e3eb47570bb1e1b7dfaff1471cde0496dfe0e9d09e537bdac3ebe115c1c9006ebaaa7d59cdb1110d822978aa59826eee128f50b54d8bf1fc6dc613fcfaaf7a1b0dd8e3e3f0b2ce6e7c33ed9a20eab25d029081d7f4f6b1df2d0a7c1fcf01965eb8bda852c10f4aa660f048393fe29dad8b879842fd5edf101dfce5a296938a5fcfdaef7a0a378f8d5d32bf11777e4572ec7afacd47cbafd3551c5b15bf78ef31fa8a4869835fbe0d7b74bb68fbb44b1fe37d1449ed93f0bddc4922c435a3917e149a145b2c40f73066da5d5cc20ad7c23aeac8cb2c3e0398f036794c1fd2f717b642fe52156d57529fcd59db5cca79d27111d5bc31806cbb86362717c4156bbbaf74e9338b8595e3f7adec1a4507014c49f6ef27036a80970206644db95da4168812dc45daaae9105c8fc3f316ecac04aef0f0b81a2c86ff231c52565094a5e15a7213cf23311c52810d71b624faf394318b7fd8aa9cd097dbe4a0d1168b8a5e152860a64fe8929c67cf9e46d3186959ee34e132ca617810be2b5e694f774d6f2d5ceaaea9066330f88d1bb80cc262863b926ebff3ec6884324f8974f55cf85beaf9e0fa590a1617c84750b08ceeb85afcced2fc72ecdd96abc51ac19275f264b43682d13e662f140cba6b183533abbb0ace03ff285fad913ea5603c7e7785478e2de4db2a1039e282644328930261f4d45c94827fedf20c5856029629f0d90117e67501184b56813b8c571936aaa5706a48505c9dd1cd905452702296db73b5c20b7feeb0ac30d5e15d11ab4e560ee006f4665f4d3fec1df51aca11370134e022be7596a388768acd554f103b9248fdb8aebe464bf21138b54d5b1630cc668246e04fde13266ae03e8535b283eef0cb178ee6442ae42f390d2e45bda23f95dc3563b6eb5d2d856a249bc9a653fec60e51335ff75465ae47985dc94b8a00ad24faf5b4fccb6adcf070f31ba5410aa54d14d7340c9ba2954b4e4a4ce28a2cf6b16387dc489026b44672380d52f6b93915baf19ad17b28346a98c815d854bde4a662810f4f6843a6e215e3452314533e456d3395d6a2847716e806f7c29bb9fb9e0830cd9b26bf0395cfb45d5abc964f02824543f3ac3ed198cebfcd66845f70b2ecdc5acef7160e90932a6897ba7f4e3311c55fc25939777580f27a074054f892cb7d70aa3699604cd17e797bac4dc6e40d839e83e74d443d7b154b81f9f15a8d9c50f2a9b595d4b954949b3ad8fc4fe908c58d8e49c83309b31ad4c8d2a09dbca23758a1fd8517a899b302b06ebb29af290b9f45418a94f39bc64d9aeb659c6f776d2e006507c20030761da1ec1056ca720c5e7095be0589cb3e91f449a16f64b82af2926ec3534fe7e02ca454447cfe6d07983097d53cb9135b998084be280cb16e11f14ee097cbddc544a5d0400352c2a7b9c99be875f825227c0289a74abccff8bc4c8dc0b2598ca1e954543e0c6b051d92249e9b1ce23da2dc933750c6a3bae50ea272c6c7a533f16aa7193b477a32c14ed4e3340b90dca06350f12642f9ef212e269af5dcbf876cfc7841651c28627a98307b222f109fea54c02621b413703820965990aa6dc30babd855d62f36104154d97df560d5869e54aea142beba8f407ab92a48a9f46fc2d3e431f0d93392fefd36a2168b7a8a1e7873d40c0139ae409f269ef7179ad50ba5a0066273ab9f494ae418dbd0dc7c71d70d07250d84184a4a053bf43e06b218106056996f617b4c7dbec1b06837515bd50a614e6a1186a15473ff849bcd9a52644af84ce68f690e8663022a4ac7083f7834f5e4824257d41b3505b93f5b5fedcfb7eb4ef71a8407022fd9d83e80e11eac1f36b74248401064a41e2dc4bf464c019eda3acd340199c20c21f100c64cb32c1024188f76a9d50c66dab0bd7596ba1b9bcdf88a24cd07aa06344008f419fcfee3836950a0e42cde4d281bfe21c6703dfd677c7129cc593515e7b6d47768e4c1fc17f772ef7fc01f7795ae2989cf343f512fb5cc68e6832c3b5e0146a1f47c16e1e1d026de28f3cfd0e55691d801a6d2fd9c3928cea56adda0014a2103a16b4506de81eff5cb99882828a715f0fb8a7cc6819f7476104e3f05d0891a1ecf5b30320cad0f89e0dc373cc7d840cf4e1748250f631c146f1ef9eb6af840fdb91124957549b2990058557a2579873df9557c384d957b4d04a225f1c414c9a0206c8283758a9befd5ef7897adc2fa52c32a11e05f38876060c13496df75fe4c1afa49d7890cd0baf0ea8746d9d8090d16faa65e1a922efe81a5375a22ee44bcc2ef20ab89dbaf6849b48c295602adf90bc0a8238ba1e62b9c72a22594c928542a601073e1e02193326495475e93c944e13c312fb64cbe43830fe4cc6156384ed1a9e43430c3ab9d78451489d3ab533dd7b7015cb35739b86ec3b235057285d03daf0e1948583745b863b2e78f51755f1bc59c5cfd59a9a89ccdb2595b29fd30de4b8bcab350b401cacec38ae0d1389401f02bd07711509b3227c7257aa8dc62d736fe25ddf6de34d61b30a9c698c0c80c31f2208dc7bc640021160a6cddf60ea9dae52a1139fdb5638ba22b4e0c7270928713abcc2efae97aae5fa1befb5ec0be1463d639c902b7c1d9de93816e6f6c90722185e965d8a2b1ea97c084f08e15cbf3b1ad88d8aba5a865dbce42201e0e0679528610c6462635ee153c34e1af8c277a75617fdc0d6c7b6139ace69dc6e082b2062363400f3d15b1dbc7b413dfe3b09102dee17e28d58841b9d84ff993ff35569ce518da5ea68c9973eb5afd7ab37baa8e94d77378a0f360bd0491f7fc3289a76ffd6e48aa109e80f5755dcabc42ab00a29f135894f3c98d832f99c6aba833f11610c5556b4b5dccc7b7ddda1e6ceabf86316175ba17a2657be27fc7384f7594202e084f055554544c7b183423bcc67abf0667888a6f4bfc16328e43094b52ee80709a461241b89c80a42f72c3dcd1a7d6332e42554574e610194b625efc18735ef900d2038554ef99b55d635f2675691cb1c33c37e3ad7065120631f28fc0c024823f5625a70fa507992dd93590df311c8c7302b388976e05650b589a77675ea569e5c6178731c508d0e3501dfa4a01b98c4e2474edb35677c22473a06f482b64258098ce1823985d55b6a1f673f58978a7df5b00a8e860139c46c4265d1d419f80541bc4c07ae44c8af3d36a0a6f4c66f57d06442c462de80a34a27289b8550dc9de93c15170087269a5d701124f53a593e07e1c109458d006c979ac1329e8a6b67a3c6482fee0558152f80318d39316396943fa04b39d05f5a4545c96ed7f6c24232956a3edb184d303b0233160c5124a9b7de0ba6197b88d7c559783b2d454ca15715492e5a03c5b70749a38ca04aa0f93178cd16db4a86ca40ecd70161d8ac62bb7a3b6b223e4e0dec3d6277a12501bd3934fb955b95144ac4e2a1216a8bf5429a678cd0030faa729aab96e925ac0723fd6280146ca994f02b396a484f0f7da6351092356c485081304d985285cfc947df06860da294b378c22ec7d434da231656f1d8e11517bd4faa7338e0b0ec295097525f8dc8033dd87ce125461cd6c3023aac3636038b2b5ce827e529bf6eb1299efdf851b49bc21f08dff7b8043a0d5c0710c6c2088a0354116200d64e820a0d97e7060ed04a46198f972617ba05325e38e655c1d085044dc439a570d1a6c290cbc8eb06901d4aa79e9b56b5198e4d4cc705c837d0ccb07d98c40bc1c56bdfe43a08e2c6b54a975d3c4cbf79c1c7863d9e2c9d9e42cfc8ab7386150128b129870d4a35a4b9c953d3383abb399b958044c6c6f5d5cc9e2f3c2e11fda034fab14757a38d545c253e1efbade8d4c2760c503c4c0c5f87f359109de20e1e4b5f1dfe3e6de9d069239f920d7b370c5c2460763ffb96b49c592b6be23a86084841ca81e34f68888b484c2dcf36b0c3df6e809e10a5bffbe4a5d74a6ff0cae6bdb9a1a2075d526da2ac3ad9097b31aa2e85ffce870331fbb3a1bd01e3a36923545c27ae1ead8fc73d3451278b2542f725e5a51ae4aa87b002c969f186084b9a50cc4f40cacb72b6ce122b5699957ad128fa91372734340ac72511aecb13f61da00ed925dfce41366ac2545d6130b33a0278fee2ea0a7c32af5d0b0c71c48400b34a91546235121217037c32eb020236346878306bd1e76d487ba485960649fb30fa33230bb656f12f7205a82e84ea29bc373295a35c54770bb7012666bce559981a7e0d555dee2a07c5a2f60b8d9ba2ebf83d14e867d89d6a9ac27e0c83f33fab70e7c6628e5d8d6a30881917f643e58903263e37283d30f984548366967fb911a82d41ba1868b61ce42da0b572206e89f35628802abc2e4bea45bda1fcca63a29b227a0f46e3f53211f98f3e1c1f441d0486d5f87b248984b71117b6c9eaaaf415cd1ce185def9b186f2685a4cdc71ab7c55129a9206edb5686033f9222893efd72e4ad683cca4ae5587e1ce97facfbcf8c8e4465f117b4f4a85b4e49aa8d8262d2cfeb9a8d50541dfcfd15c3f391c4461ed2904795788abef4b1acbce69183e4127bf8f2724eb34076007bc825ea9d85e6245e5fb173e5ac02696160bc5413e3753ddcd582be962137f2fc47873e01623a5337602857a1e3268897f2a6565eea0fd5668a7de19e6eac167c4f0c1ba9d8e46636d2aad92076fad602a24093a7e76c339036058142737214ac51d0d2aad986a66b7a5634e6a566eb94b5c918eb8bd61563569b883044e1474cbcb6d7fc7e6ce5524a9cbdc998b5e1b377ee616df9017c71b1aba349bca8a4661f9bed53952150018b0da1beab3832ac4d2016a34076e2e5bb7fdb6d9888a0f6d86a89442d604b5dee5492001e5a9a7772f3333e1591e180ac1fa9e7db83801c0e137469e8dc1ae3f3d904f97e387f68507eaf57a1e8a5865daa9515a7158861d7bf97cbe4767f14261f3dace443ec2890c8bd38669f33ee152a79d250ca87d14c2c647accacd88cc59d32188721f73485cf0a14f5e780cbdcbc6ea66c887fa8b98b37461968b4420dc8ac14cc768a01c4caa47bc874394331532226c52fd3b9a423f72c216802e5a55fb017138057182df99f812166bf17e4e35db549d4bd600b95fcbaca21446558837bd3cf191573e431e74b7839bc381ad7f2a48401f6a3fb2fa7861819740122732ca7231471ad205b62ad68089687bf42d9358db211b286589230285bda236244307de6b8429cd38e22fe2849b2eb157ae4e80aa34375ade64525a2efa2928a5e0890980a7d968d0e0f1c338058ffe312c4ed081140456ce3475c3389a02fb1852126f61415e01358c7331bbb067e4f643007df24a40a7d76aa4075e25d59ee8181fe79b5e9eca3b98d5330106ec63efafe35378c7cef87a5e64237a9586e9c649d820756bb388516de10749f2102dba6d3264fda34c68be53a83b802b6477b30c041d45e6fea1767b2867e081520bf2762125e394e232ca002af9c9ceb0683c61ae4a826df08b5379ae00c06d08992bd0b38ca495c47c223fd4a26bc00de39031c12128c24fac105c83ea3c93347e0af0efc05dce46f6cf092eb30c77697121f71ab7758488cdb3898a661ad3cb6e7d6357351b2952a9ef96e331422a5b73470c7f6e5fda1370d315fcce683ce80e68dcc87bca0028ea21f3fef946c2d61996deb1afe9c3a58894fe0c0e6f241bf468514f21727992f1e523c3cbaf2101018c8635e1f776204738e70c9c8abbfb3354c36c3c614db5eac0530dbfb7bd694579929597e44bc84acd617e892036c9e36d0609c1ad50bec83de004517a53ee2c0596dfaa0582d660f9f3f7f191167bda0328a10fcabd9fd5c5601c693f20587983fb570f5a68dd67f0ff7cc9e97c1c2767701a1c3191c88e24aeaab9bdc59498f0f65afabb46e10b39f5aa5e81cf70a4d09de984f2bf9d14119d4adb205fa3897d8c9e870159c49a33c6511ba633348e0d93e17c1a88a3f6259568f2b545b94c45add440e6839f0a29872192937c50f2c1267d965c932def2f99a42878f1d74922f2d8659f3dde4bd40f2949006d402f366924f187c1b82f1c64031e753fccfe1f58d01ad244b6c377eb116e14257cd88541e204edd5bff6f6b36f39bad969c8eb789267c13971c26562f20ae1e69021fe4accf5004e12ac9c89e033cbff3807bfaca5e62ead71c00449bdb322d94ec3becc02f887b4142c8bc5ff0d70911466ad3fd0918548c6ac6c021238c2227776b8ab3b6cfbc13e16103713a6799b0c665734c51ee2dd4c112b16bab7ef77181117d5c06208fcdf9a1c516ebbe080c8e99361054dd24a03c14029ba09d2f57a889e94deec7ede478715cf84e81128adb840c43f20a9c2881e7b44b89f388a727c861da3a161f2b53ae6c240861f2d4db5689d17fa41662e457210427de6e1d02a1c6d2dbefd9a368d4049a019df4daab732e47850efc85f6e971b35209d48f8c843d88bae15bb0c24ff791a383277ba6f386e42fa01b6680138c3eeb1b0c628b904bb4769612d7c7b100a9ff1fe663ba2a4fe9e42d342628c971d8cb2af63c7c20b70b0c970392cb3349e588276ce52d785de7124f3871c7c703444206fc83ed910611f4a6626a634093c22f11838f25e87e7267e0e905571598300682433696cbcf1446b453eb8d5fae391cc2e3a24b37be035a9609815d80b04ae00040e00fd4b5bcfe63c1e63094115474d80d02977a36de78565aa2b7b3c39f83bbbb4866960d276f40df8671a03f09dde196d425b7f5ec5b9fd6ea3bf9e5754e19a4c3ea666a392dfb799319a59a84be306b7e38d3eb5896f193fec15ab6b9902b8b7d6f9287de8aa612f0f062f2cf1e338ce677b14cc8f64088608b04df666151bb17ef395f183ea437635de922492c78c7b2e51e93b5a36c6b51964fbaafc8e948af426461bf44f2530cd5799236a901e958052017d4fe370acb2ecbf13f9e62a16b9d6fb08701e78f0d119ed2919f52fe41348ef25c392dbd5b59fb950850e95b269aaf98c9b50a14b7d22a88b0c2ad8fa3fbfb28084fb2d0c3a5333399be4682ceb82449f1b725f109a1f31187a6346395b228c6652699b113972129c5b9348b96a21bd065448d123a5a7ffe4024caed0c74878b164c10968141ffb978caa1ce1521e9558c8866d2c5563a0582c08438fa94f1d1925c44842ebd7cba95305362634356053f55433226e0967ab2a8683973b94cf4f8d570d86d4a80f08a29b6233bb91e8e9d17710774e1550bba0e30b0e86ff428de7f07a1dd6fee4ff741f20916eca046ac767cde59ceb17833d919f2bae14789202a4a72b17b6407a88f919cb9e4349db80da083bd0b91d3e5b0026feed3176e6606af71139dfc2fb6bef0e8c043062b2c43dfc12d55d95b4b997891e10730eacbad463d3b3a53de0cee56dafdf38c9e63acb6874afca66b2f9ce857e667b55ac0f6628a8cbeddf9607bf1821eaedf7b48ce3e1d8ae8b0fdab7fcbb5970f8e78ba516d5ddb574903f54a694e96858c507c3a20c26140bd826cc06145def7e9fbbbaf96b41506a5b4a5dcc9c95151ebac1018138f020212769030c78c950408bef7be4be79307d9ba30bad7983fc331cd824f3ba41fbff8a2c5aac279f7467d2646a865324e9c6b31b1f5559c8e7581f8aa173fd7a490c7b0930094ddf7243c68bbe81d878cf91858be49b9ddb07a4792ce0b29f78f121c5d9914ce8c0af652f7bdc917055e3483aeb1382d5aa456eeb4b9543f3136781a3c0527625c7ade4cf034acd49b52671025889b6e45df77cb08f5d87132f74ecedc6a5b89d3f9e48857fee5c20a4fe1ae05ebf22f28fa196725e376d82156a712652324283298f4b4db5a69c577f6380ad1152a20e69110e55f3c14083260b274f907aacc5337af4877910a67adc838d8b97b81309ac1c8c6a6cf0ebd1b55bdb4522089ed72d52042bd51494c94e90b48333f1c6516be07b2c58219c84de164fa40b5dde8482cf15216abce0995517e0c017d2adf0d8178d428456f773749b552c63b335332c23257532b4da802a3ce01c55ac720a737fe7fc52938a8e49603eaac8be17552108af1197ab89b983b40c8a821ea4d38f2859cf1b9b636f8e6dfd998c4bf16b6eb3eb985a318093b9859abc8b589b930e89749f0242f191a819c7f8a3009fa5d70342501d4726b8020ac8fe59afcedd50f1a905c8348d3ed6f64f5d9573792968732ae566d4a1376cd5fefb413307115c07701363de570b5dcf585f915a223754b7de15ca113c98c9a223c1f182ff1f3fea1b84a8e887efd6707e487078c89cdcb9a6f0db65ed8b7a6e1da3397f84383d7975ac4c2f6a842470d1669a3c52b6c311453c4a4f6127b608edb584a3fcfd832882a8af0f7436e282599cd74f70d2e8a8d9662cad1df16fe3ced1c2955320ec41484bf7da216c53b929f968db7a6c1535b58ce69561453cdc50fbc13934b2a704f1c22c4bfb4fa7346776fa2fa227335611f3d417991d0233eb3aabb68d4e86727f7b1c17edaf1038ec88060c0a18604730d0a2d897ed51d93176678377f77ac04d4938a3fd979da44b85bdbd7d1627d20d1c40dad0f97d3f149cb61cb0434a817bdd967087bb4de844fefefd8f505fdfaa8450001c82443c9794388c4b4a1fce7373123d7543c9bbd977c6e4c9f7893921c49dac76d89f4755a3180017f3f823c511cfd9c55cd29315f218ec4d4dc7c2240b23347029fc69722b5166e620b0a77e175c21a008955684310197f3aa40d4b9b255cd12dcd6b0efa41cdc044f63a8233a8a23f53bc81c89cc1ee4a2ea420fcfa023a7ff87cfdb3b4e202ac350d3d128b41f9a166914b95bc1f478c6cd1fa53921f8ed1fd1079cace20f4dc59fd7977b7a9ce85c5c10a3a29688c39eb0deeb43fe57affcc133a72538968d3b249a8c52828ec54891c64cd05335c1a8ca76112455957b0417655525ec39145615016b73f02bb29e08c4e2e4c4f7a2fae4002d9f03355afaa748033c911793975c2688c6f3d94038ed5db3150bebcbf058aff4222e03b21aad5bb1e8072ecbe715130761747316c3f96c96760a3da457e049bed5c1d402f84fff87e38335985053fac75dae624ffe4a8da6ef9a9a99006cf6be80503e7b1aa2b3483ee708490b1a1b9f75c514d1dc8c844f685f1cd2cb4f0f719c5df87d0adb78a025d2d5d16628ce01d0ec24a95c202468b09dd973389f90ebb88e19cebe8b1dec8a1c9ef5f39fc1eac4fb69bd0debcefd884ad736f748f8b087991b45f026586adb98e739c1a5a740f75f35e397d9529e81aff7989630c87cbb223484c7a69df08b41a815e03a16b00741a014d83a0d3486834068d86a0b1017434001d8da0ab01e86c007aa181cfc48d545c1dc9deeb47eeb64c48827c24eef19a2e4f252098a4c2150882843190a340901efeed1df07d46ece913812b96033e43accf12f319627c86b88f11e723f4c967c4f988583f1113f443207b48ec81533f781c18eccf0ddd60796c92d01ae795ff3f079cfe0ee253e2f8409c8f11f333e27c86389f11ef63f1e14788f900b13e4adc077ce2033ea6afc013fd4f65d6f81734601fc7083a0ace80677d87d9dd712e675e8c4a83900cd69a96703ff3c1a7d377d867b45943b81e8bc86f45818bff5c74946781d7efe38670a892cf7c469c1f88e1136291845f20d8fafc73e27c402c1fc187d29f037e86980f1e31f04ddf6206562c23f03afa1a98a82f380482096fb458c718e2d9fe2b9ac0009833a0a61dabd7f61373f27b52097a1c924136730ac6afbf41bd528a106848eb6dd3ee282004e2d4bc2033d32cf0528db1ca91c7034280a35a675866b4cd36c2c5d7662d6950e57d7f5717e74796c6f0100790563758814505e6f49da3dbec26e33313130e987d56eeb605f7ec5c229bde5da7712c15254902e7940636a86254b733ea72b9de483fa423de4f01356140d2747e24c8d62d01aec7937b61e3309d0d1281121043a7f0b70c8428556ca2cecf0b9a66221f554da7878afc0de7d6f463b968e414a6ee1e608b62797e8eca728997cebd14166b3a93834397a3c013d22a58c6b9240804da69692eab9771ed9762b0afdc5058d6a417a32f180810337a5f5577f8d3330a82487042e722ff0f9049fc44ebc0f8f8466d9fbfdd2228fc34a8ae36109f0690fb4e9e0d9d61612a291d814da96c3e73a4086595c87e9b9db02cfe50eec48a6e716f3a768a93c4089285a81dc1c8dfb6323a0e9c15e1ce102c2532e1f300d0203211e1fa5b818351f1232129b2b96662f90b55b73d388dc8fdf9ddbfcdd245c365150fffe71c80018eae1443aa9deb300a18a263bc141a8d44044d3ea4d432662b676cd3866f4505e647bee1c2b13e99e911084b61527f4b299eb47ae438ba48f124abfce86bc5bf05d2c2ae8c46c0eb19bd5860042e35ab006399d5de4afdc456ee0ea31e2afbef9366fbe40ccf18c99b86e97714a5915f1c7fad70258819652fa88002543777080320201f845b4af6e16598f2153442c0ddcfe3e5caf9d0d4fe46242b15736db4d93b3473d50256a674407bfebb095cca21153a90262e7cafcb31b485bfb905b4baf85ba56af68526434395ab95d5ca255e7490bf0794891df97ec16b65363f12157bf93327cc96e4faa67bd4dd0268f476bbc1f52537608751b604f05b27013dd9423c2846a7f18a87a5332503fbc5cee2f7d1aaa3eefbc33ff09f5c03c744b6ce3ca89d069b3256e34e90da009b4b80e6692ca453a347ae8ceac9f4109435d84bbaa638030b9f52672f827e7f8f354738c256c9857325548fa6010f001c4360f92bfc0944fd7ce3956430ca7f8fc6906476e0b03382d50df95d514788f9786b35c1ef91574fc05d5923f8717e53b4916d264e35353da739fcbd2c733173232a904c7f35a5ccfe9a3a47c25b10eeb4b70eb43a373a6a4f151f7bf541d3ed2d345192e3deb9ae71f549740999b331b5ef9dd5de3104274e09aa3da078a5737dd47971ca85a0e204b61674feb8a0fbef68439db5d8391a022c6b29a2228577e598dfe47c78e5b87be57116fd43cd678123de94f31a76eb5e2e90e7e0e4d7813048d2de33e3188b14d49b110abd413720460f58acb29068c1b97541819e2462491fb0bdac2b4b9c7ccd0510bfa4ca56ddf7e4eedc016b40a0e6bda47ef15ec3c6ba97f5b9d3325767331033cba04e4fd81976dda034dcc65b7b6559d3b84c87578241a377eaa30675be3a9e7fd4ac14bab71445b09ade4545ac6d09f2251853809420f2f213e3431c30e3c53ee07659483a82f100d9394ada5725f3aef2971b43b1c4db576075c1498448bba65f544d52345f2ce6de58e5831d483be3bdba4a9721589fac4b548618529972232afd9eb1a0890ca9e34adfd414858e380713426a55cda58e7bdb408daf74d84255ba8edb373a372687a322b3578c6005effcee9d788259859e4f5426e8763c0bca56a3beb0f2e45206ac3f87f3ccacc16048fc284319fbb2994eb1fef8e42e7bec0a21d47724579ac46186a14d8c0fc1ca7f5b0e813e41f01ff74add22300827792e55d6115658a1b732936b50684f020ca64a47475717738b7b4c64877eef2e780be6f04030e88edc68c61293d814dc19b5fd1ac882025151e01fc15c38ecc0ac4016958c39cab4772766089151308fc9231fb8fcd05f6b8f2d831cc502f19f0d32ed9de1a0cb884e0a021445529deb97add21487bc3d25fe5af01cc741215e16e5cb557d717e27e21acd38e717e7433a4502207af456f4a3f9cdface82d41de38c59708193d5e70fe8610800b442792255dc99dd2085b545b1decb92a0866a3846bd559586efde3b2ebf5104967b8ba42ee8a92aa2c09d0fcb8015d619c05991d4b2ac7091ad461865471277eac7863006b35848b30fc77af1563460e4e56e5f0820e0ae576258b469e36901b6b28b83fbc1a28683c5b628b19c70b83cad8956b9898a1155c6c4e4ec35588e395a515dca903eb781cf4fed6600376de15c047154e6c57e172f21b02bf0da4bd07e01a450721036457e7f8a3b1fafe66b9edc2ed256d260102adf67a040a18989f37cb54b20f2733902157440da253cf0395daa3e6f9427f860771284aee48960099a407d42e92bd9133201d743e429d53afd6b10041a62989a84143ddb84d47243031ad6bd287318fcc213783526cde764df88074a650c5eeb3227d4987e186370890b800a3b5b70bf64ff2b8d8a7fb28d28ace628c6269dc1c10a84022e1aab225caf4f42e2ee1a5951b74bae42bda0b6abcc553d87f9681d9ffb8310230c3408734fbf2278a07049a3ea8e9eee32a5c0f081de390a82b32971bd22687d131bd85da1626b62e25699c28386cb8f52a56e2ca20ce53682a00183c3a34037900cc228e3462c8e3eec577e4dc07b870d8c05e9c9c0dc6119df3491e75c82324fff385473ae6f9081df1042dd01297b5a062b461d29a30bbafbca0aea2c4c888b3330b93114c79a3db8ce7cc0d82a3176641b4abfeb41b566c90c7fd474120bb0bf95171971e8bfb16d546a4acc5953c2585821bce615c5598f386af77fbd49ed8c2b722f2f5fcec73c6618dfc9e37da961a735d71170b77d473f58dd63f9f615e3567dd9ad4b46394a21ddd6cb99738df200352430e933e215a87beb72202f52adf2bb8d0b6ccbe6ecd22c63d27472e556b3943250b396c55cdd3d5da5c06b24af50a946a3b0a8020a37d1013c21ab8f2100bcd945b8746ffbdf764afd35454492adbe0ddd039ae45865002afd400fe72d38d6852ae02e9a7308c914922ad896ca5831e28f2193bacb057f6c16ce8c14603aa51bb3a116fe9b0c9f2f519ed7c28645b8f104c39d0cadcca92c716dc87bc829e8ccf70fffab520b78b06f98276d9b65348c08f05243ac6e5ac34b75c3df8b91e85193172390759f67fa6868bdcbdd91cb10eb2ccb50506b2b06589b4e43726a31721a7d5211eaad1c3913e178a05910ed957b344e87db37b55a20154618216afdc0c37daf6ecc7ec049ce731dc0cebee4eaa87481b0c642e8169c843f175a46dde79fca51915d50d306cd0279c58cd57d737fb12e95330e782cccf2a64b770a005c90b8d60c421fc8693eb4a07c844b9fd4bfb90f0940ede823c849890e6d64b3f73ef5414f84ab7d91c9d21f2695a0392c973d99d061eccabb3876d70ec2b4d916693729ae349071917ff7dd84d22c659bf9e0d8c5e4ad74694330bc38fbfa8e825c13b02c999ad27b43484ceedaf43f57b94240023db5d2889cd19cf4208a9dd41b5ca82e0e82bc8db07d5e9b2b8c71b7b8799d84dcefe4f8bb617ecb355618d2ff081027ef768d7a002555274981cca571d6c90f15b89508cc77b13b28a7cfa80089c2f160d916343159473c3a1f87e862e222e820479b20e7349e8041aeb631cbc9801904389a4b1645579cf6ddcb3e57899f1d6ac2fcaabdcf84d2a978797180fc524c16c999bd2526464c40940948348662cd47dfbf971a5241fcf3d2bafb388560d6ff0a3b24af5c0e5baa22dcc659dfcdaf2010b713ded2783746fa15db3891b160de36ccf75d0bc82e8fbe77135bd3d02fb2fb0a97f1c56e44dcbbf9a1954b8c28a7bf71483c1d8ca8b013baf0c061f0a7269764afc7d56a58c2e76dec2f7aa25d5db91759b0bd95c736737953f298dc3f859cb380208d507bca2cc5d2f07e68f3347f7a9c95d934a72a2eb05cf9792ac58d8a4a4029f310a96a054f3e4f059c9ecceea0777d0b806c297914b1b89295f60aeff1341ff810b009e2a323a168809c7a3c9d42bc4c1f9728a07fe86f1a1346c14a5d5f1a58335ff14c07a600b8af82c6c1fdea2dd718324b254b8d6d630d91234c5012e4571ff0088a7594d7a4e1d6f4ff6023d817110d46ceffa008716cb789fff4d349a38480368299b537fb0be4566f44fe88e111570723a2ed349a8fe7202d2d65c89384e19ad119ee1545275cc81f6c022bdd373198d3778a4be169390abf188ddcece243e317bf13c56f3a34f3322becc1f388522683d11848c88371bd3757d1f3633e5a6d5d525ea4d29ece3ad2a993595e5252770e545477a3dd7a1950321bfd940deeb9f0434833e89360560a08985a0645c1dc4ae495ae077f3e33fd1749ff0e3c4ed2ed811017938ebd3f991a1b7d64a580dd127705bee704f28d301db44b0202758d6d2af900b81895a23e0cad87dbda92082cf5d1a0dde34b571fcd3692bf48329bf9f70743a9811415cea894b929928650e687efcd10d4496472aeb09b4f183d3809a3f29551e4427c4b3da0b7890406213ec2eec08bb3b0a533bd9e12e96047c88ede40896c34b681d66d0cb2e1e85693f492140461b498e185d012b20d0c154c5533ba41ff2e847f0fb03e6c95a5b967f92be976d000020219c386141461afea243976e0a11e0ac557eb5a7389fe5d8543b0b6f03d1201297d9066a44403ac96d76faadf67a723f3a56278e0393ed81e74f25a2790109309e3bba3fceca3cd92564424752384ab71b1686e233388dc0b5e41a70603a43e99e18914d383353bc4de6a3f0af68f4a4b932100c95806e84822ba8d2218db90f871130a4b63e730d51999a7061678c9f02bd454c7640555be1528bc827b4986614c7de3a2e3c3a530c5a658db63248852f0fdce470c9cd455c13385e2cc5d3ae0b81a6d715c2eae89cc0a71fcda65939ca0dc2aa0543fb3e01a9df2521ec6934f33e8816eb3b384f1a89f8061529a487de551434582cba24daac4bdf5f324a3116a0544fda44ce4736c704983642065ce2e5cd10002c8b6939ecdf45de110f3b9b96b7ad8fe24735291ed042b4fe94477a3b91edf68bd490f6e6660d29f2888879bb5298cdaa84719593347e24b50dc2456c86e4c7e153944e70d4b6c5c9da2c69bda99e6f85bea42ee64a8d148a11427ba65ac01d88a5da618e5363bcf8dcdf92dfea2a7ff43a379032b7d8acc470eebb5618adb1f484c0fc0c1e530433e0e2750140119c910b0f6ffb029bd896e64ca2f2248a2f940d555ae169541266f60d75b13f874d4f2e13c7c085d85310b8a99ac048d76a1cb23aba4eeb9ba0857ae142b192884a8448a0761c1716a893de1285171ef9437b510b36fb72e96500103a19ed82c4b6801f0ea6bf2620cc917b1945791cce1e759bb551ff0db09fc16b7ee5aaa6c8c2f142fa67d4b239aabe5b29a0802210178267d1af69f3bb554cb50f59e4613e2c0b1b07f0c1f98d3987ade2c949d20ac8dcf9bb12a2409101c19eb826f95308c6f67fb2f9df3bead24e1daf97db51047987cf03ea907130d7806af171c1f718befca9aac89c194d461f7c71f155221cdeeefb4dccca86b5b668c11b318488b055948d8b339ffdf2afadafe31970f5011461a21e945d8f5cc1fee2a3c24b8da053c61df51152c65823c6040ca4312f084a16f5594cccc952343427885b924f496efc8be916e1cd86de2adbca94c2a4c9cd097fe8b9b28aee4193be84391cacfc94b84f9889f89c8ed790b80a787c24596174920285ab5a8623f44ed5444cfa87fa1cd6a077c53f1442b527975dfce4f933af673c867282ea82fc4d189dce0ef377688925678ad623828296136b319e2ccb6bf3406f524d009eb3ec74a40bba5fa91ea4f69a7ae2ce41c01e368810925f0eb3a1d25c71cc08a2bb31a8286c9749d483a936a5c31bd6c9119b70203b70b38fa82194db9d6a1a6b781a954a2929f82fa92706536b323b0c7cabe3e8540af97d9ca372f43b7b9b4ae58498ac5c595bdfbb9c53c90898503120ae65cf45f78df8b9ec3a0bde777bfebd6cbff0ca60d729d778d053caee68a1fd5a0ababd7141171fe66e6bd9f7085c111ef2906b48cd6fdea07795d7e15d536ff878146bf1291acce8b97020e9d26a8a97830269a41b7c29e7fa1bccd4d3ef48855f4e054a5154bcc2dbc3c5539228dd1fc06261332c27916bfdac6ca4a2bbec0e6ad5be0f538e63083113dad98cbba7c3492ed05d237ada0d8d31b98eb808044e8af81a19cfb3d2e86000a0548fd8b466ebc22d2a562e4c80b394e646e03663b958668ac308f84ff78912204cace66807f341d49498b9679d80c26b4aadc9bb10087f75a053c07d02581c7e34598b4cd410a32fbfa1c31cfbae6f8a13eaddcffebc9c1c46b0e2d937044b4ebe48b3c8ee6ca66121f48d738b8d0a0e34fd89e2b49ef3a5bf5982db05db458860d42f303e95ce929791414b4a3af1a45ffa73a4a1173e810b4e44320007837486961f0549be2472025c3a4bcbdc17288ce9e7b2cc877a28f817f75f5e98d46ec4cc48f033776abc5e3f311239b175d391f033177d2d3585987b393531ed4a191bea21a87011b104746be3136e3bd09e8220f86b73e385ca400e4b4e1dfd8e131ea53ccc170ca8a664c47248ea922af36b07605fc93bb89c1ad23dd889a6d4acf0a8bc7250f3169ca12fab45d3acc082061d9bf16721ae85830b3278c84e5a7bf441823c6bdd71580745210b42c0c2346b1e5785245fcc911af11eb990bf52159827f2a62f251824875ca2d5b4a56806a0615e0f87ea3f713db830d38bebe541e91c8c5c8b7669bdc9eba423bebf4db0995179e67f91b41bbeb848518d64324c7e8fe1ea566c2d7b16fa679736eaca19ed184d4fbf1f9fdff38e74fab13df8151ee49f0f83d24f55ffa7714e58a093c4867d4825022eedfa5107199ce4adc197fcb67f2d2203e11e3f39113c527c920c775e02e25644a668afdebd0b5f233620b60b6946bada6b8343dea04509952ce88c986c2af469c3465009278ac27474041c480fa23030e3132f9646d92d2a70a2a13aa3769b16a63a74c0e234dbb0b286e1230cd3b6a4c3ecd520b2cdd26094dd4d22e96430833839523ebee10778d99e6335c639b648bae7d58bbcea148781419504cb64691393502b1c6d828ac0c5124fe7ecf92ec09a5c99739cc02870bd06cb49a29f09269322639437bf6fef548766c0bf2ed934f01ed4f12192e42441a886892bcb1d1130481ed1b456ae0170271756a926c731c2b7d726e47e7418dae758cf4a5ca9224d8ddb4f4c2c4ae249de5a2501ea8f393f82c7859d22d83ae656f371322dfe85cf6ddc957b9e3606fdb1747a2adbafa585eb2b7078590944ff1b2fc8fcc4386470d6087f7927402a8dc2f59e9966152231fddb0b73904d1685639ca649fcbb783635c68ecb99e05ed3a7b2c81451f5888064b33205f35171efa3bc4648f06a1e6fd687da362780d9efef411dcc21453bb70538f8e094cafa728e17fa371f7fc0d631cd9052eb09f15a3e6fc618285119843a5d5e9d57dec79395555d6a7e561cb6398e6294c91a7e650a63a8050d5103a9ef3b8b680026997b6e50ffcb48ab40a43d5e5ceb3a0b5101326d83956e4dead124d779b174ba436b8c1f6bd57de678fc39ce0696af7404074214d903ce0de2c1cb264fcdfa5061f13de981bc0279eaa6cc51fb52f1f28e8e0954b05e4182606363afed3545f1ecb396413c979849fc979901df3c4c067a1588a6c88d5ac067b910ce5a3ae0350d98af3496cdd0864e5bf60cfc3e8d6aecedce5b6c6d5617c0e5ffe11e609bdb46159be747b692192bf7eda536b844a5d1d96c48e53334570e9160f63b1b3f250702df289612259d83f79f2d6721d985ec39253aa4bc5222c490e2b0c2c1c527cfeb61ca79c0bc34141c90138407879aebbd2e84479ecb6d5ac80a3aa3c6983316c3e05108ef7055491967ce2c63fd37a662a4c04482d86d984b4c81703727c1e52fc2acd0611c0a81b08a0bbc880185e7353204889a61023bb7b4246749d6dc750844a63662c521c98d0d436c6a16df1c9d9d734b5b9173e9cf86042b1823e9d6920a111e41c11bdddf144de2f34583e47ec445539ffa8de1271568552027eaa0ed7537398ce2fd4c39c3241b02ffc1c418bca72b865460ab6bfa5443b989a7e3f90589cc623da4be990790448cbdec212303c565b7897718258c95bd6f4e7386d01ab9b8a4c91e68b06f4d2c75f1ff2bcd32d830fdf7a3056f69f9238802e7688535f3b67bd178f2e75cdeb876525893bc10f6c10e407d953cbc630ec3bacf7bebea77f63dfeb8a8f746281512c65d369897bc06c43917053db03e8225c65a81ec68cb593b564b8c84178b10118d82a077c57cd730715372ef0241133ba8a9b4fd70cb673647406e473da74db7c3cc614cdd326b19e9c0b0effc115d1868fe97910add1d306e797f967ad5453dd8a8d9c352fdcbeef8f15af6c4eeee10cf89558ea955321e2b30a722c7e982d0d54e0ec844a625283f5ccc70eb4a11cfdbdc6095a62b6626b281e5bec9bfa75289324b17418ec12fb592a800e966a8e7798cb415590ceba0cce53192e55d2c333f62914573ae18c7527573a4a66aa599bada7d9c215e6755a5f2211114fc1ca047c0e3bf55cd96c772c570d098a2b12854d24f529f9d9d5b6a4afe50a4542310b415dc7995aaa68af6884b9c978083c67330a42005630b11e8391d73048864abb9903aa4014a7da4802419af8c4becfb92a45086ec452ef0eec3ac9ce3c2755ef5ce1b1ec5881fb2d72b4ab76ba1f10af65933b1aacf2370df7bb79fc918945ea60116932105fc90cafb80554fc9b0a23473ead989259f2a0577881d38d74dc967120b71411eb73664a134388c8548680f6a97890c28f7b30803684bab7caf40a7fc408b00424f28f59dd2640348ce3b8502831ad25d15d69b0a6e03db3b6269db2f9a06e6399da6221d93859ad8a1394fc3f1027214930da640f99051d173c44ef3cad0c8b8f1bfe40c4c744850f724c68fd1509d73943a64eb1d1a1676be0586bbe0b7e1c9c24f114e746b6c33d42e823100438626726060c468f283840baf6428544b4c7c0414dae6a30eb5b4b658b28609b5d04770d884ba2512718e909ccd96ef982bcd8bfdcb6fc7bee984b12a5538b480a9ea5ba425eaeb065af0e074bd9773ef8552aa0c64930fd306bc8cf5bbdb1ba2ad731a4efe541a31c918dd41517e0af40b1e279185c721c54aadfb7a30ae262eb45e3d445f5c88e4590f4d052c20b623fa177597479d32f20415970b3a6c7c1038369bdf005acfb8ed0f3fecb350f61c19b068857b2e415a82697fc1f4eab229b53b8bb0819238ce64b7d0785aa9d0d8d1034c716193f8a212430006900a576a3c98a69ce54f029a180f4f81b64de2ff32bef251406ba1197b222d35174d26a80df67208bfc41d5c02f607e430a7d7388b21272b556275c38a89035fe3952d7405f9e6ea0ea8fe80cd451e157dca6fbf46c32b6687b7a852138c37e12272113b359a75fecb722b2fbfbe1841b289e5633b0b9a7bece06a4c8a499d762a0a569f3662f871efc1f2b1071d2d095a89b9fbde3c2511797092bee3205137c7078340e543012ba636f5c33889a9a7cb04fa83f3c8cb0a4643c04d612f53f80024910410672c752748b602d6049a033204890696213131d776ebf56145314523a44989e8eea13a0670c5a19dcc60b404bad42d5ff68dcd6d62a114123f9e305b7722052a72a7c170a97ff8e578b5e545b3714fb410394d0c72a230655e723e0731a4c232a6234193ec249a2a544b962ff0798902bd897ce9ee7b75fc6ac4e0f451082ae94e512249d54d51d20a683b3b1c3c0c167ec633c4aad7af2a3e9e9ff32707a944e533de682c67b7e78f5da61e8beef922c9993bd70b53ba3c6dc8f924cd3fc08a32e93abf1fcd487ef6fe738c7bd78412cbbcf3c80f08114197203fe20b3ee059560d00e8110cf2b2419c7235d76d3f6110dfbd22514b3283777587d29778c3cf20411a13ebc1de9fda3903ecf7727fa7f1d96c983f4e54708b5928c02cb3be65a6a73a7d83ceffb7fbaf87308184157afc36f6c5006ccd96409a2a6065dcde878329ee70671a71068c634a14107d0322b4d51e06b8dcaa069ea205c89dbea1785e44ab095f1156b289628daa8cb4a181141e82defd5dc64c46f2d66c1749391d886060acc74af2cf4bce9026c8ccb09ecfa3d8f1a57fa14e80e47e53708f2afc15db7852cd86a80129aa59267dae64671840ae40d7e6fc36cb0f98486d2b361984b127c18820b886493ce7bd6baac62049d022055c512b592fe2c98ac6612983852f83a94f09853b317f31491abb86e4c43a6b8d53f06a8badf38385f90fe05b7c5d9fc31906b5d069e7959e8858ed7873e80da4bf8fc8197d432391f0ebb64b352eabbbb9ee25f7fc0f510bdf93de7ce5303db2db159ff3f5fbff56a302831a07f09762f5a7a1c02426956c3a0441a0cd3ad218ba67d23d52fd3b0d9c6f73ef0758a5b41c5bb3181da05aad4ef0b01181eca235a646a643a77c70c8dd452166b5532f98423b8c69eac6ad3b11235736694fc31a4c10a4ebdc520648218ca5595b33a26e03f8752d97e4dc4747039301c0c03809642e896ba75cff87fcc74922e230c24335d57a9369d98de549ad9ae123c33ad1ad818203c1064fe680b15735d48aeff28aa9644844b244b7fca7664b2e16c0c8881087cbe23ff833851d936ed1674c2481330353168b9cad2d880c9eeb925297e464b60e17582073e4cdf99b0fdc67443313175e5c468f69a9950c495e0b7cc54bd2af68d1c62fa7ea71df343ecfc576856c83c00d1431d81b2d91427b97ec8e97385c50fa74917835f6e78f9b87e1113693e4bba36b131974c24dd15255f02a7abecf7ed15c2837e72708dcfe6afc87f44382e1ddca39b8e238d59ec49575f4720f730b0ed491c84d0bcca6777acb9165d66e77106c144bc078b5feaed8bb880a836df213a9d28a4418fe027bd3ca52c1a7d4018a8742b32540975a3a19f7c66bcfa55e86d23d603e1d4b1d1c2dc7797907a9f125445bd9dc93cd92af4998522f5f09b7789fa340d13e7446faab2205e38967ac6337c4edc922f985f73427aae0fb178494aab1e9e5083c4efcdd77c44f9998a8c9325eda19910f3077d983eab157fd3a09296330a47ca7f798bb3d06547a27d5f0c9ef67c7856325b646e9a3f8a612e9aa1c8308d768199628d946e595e784ac5cba21b3bc91c3bdfce2794812c29e9fa6554639d29a95a8f4059040e42e4fbabc87a896e2e53aec3fda00589e218c47bc1ca1fa91e55aae18b929a350d42c972d4031214261b124c782ed030ce1f48403dddb1bffca02fdce28a954b8585c150412418120e07ffe330f9c7a0979d4adec76a200ee211c047960176580cf3940d90c5ff2d0c28c25fd220228cafaa352e545e2fc67a9a7035214429a94e8c61688d0059d99b820f40b42b045b08c4bc7b32938e43d703adb3852310af574d40975b0a5588a0cd221ea3fa99810b4cd8e4a9246c8179b9e5ab467507b2f81d06a16ad16cf4e30fac907901620fe010d82cec9f0f68b0515cbb6250299991eabc3271884a1160d12a1a920416531cff00382356fa1a8c065c11e61b311bcfc81c44d904cee6ba308c7ae2fde30ef4291a2b22e49ac3ed0c0e2d547077b07ad7eb6829611d81bf603928173fba6f8471088777cd4719cadb9aa55c223710e08c54255f6b4ee3bc700e29e80fd0f753895815243b6c3fb24424e8849ce28b198b0acaf8f67244cccac40b0ac6018b0803c20adafad630fff481433b245ec8e20a058a70f1b0344c91c34ec5b369b9c9b2eb1e710011f05d8b818c8a9190b5d95bd0612e5a6af1801e751ed60e8824a0ed0c8a89c611963ad2590bf098b7a38c4df5dba9d74c654dd366c86a31498ca002096755f3ad1d0cbcead5d175cfd8688a1d2444a1f7358222db4d1317f0e2af3238bbcac55457b7e2b8a4dfa290dbc6a6465b3cd927fe2d324b388db8a416f8655062774f6f782e84dcc34c019fe4b563856d584659edfb84e6e309313f2ff7650c45134d8ed41b47787d3ee0df5eb9d967a6d6de02cd7b53ca82273f130c65d0e8e166eb294d132debe8b5a294696c81bc613c3805dde7939c80a06a2a7a17d9ccefe8bd292d9cb4bdd864afae53af90f460e713d690634e7d49a4c618f0b31060619e6234fd98fea10c84a9947bd70d389156e448636ad2720068f990313a08592c1c3517cee4c9a17352436f7c7a7f8d8109f8a056e5863565fd6525df59ece5a49a9fd4111ed8bb3da3b4b290f98f1c310c2b0ce23f822055c01a0680e23dd8e0834ee0bdf2916add6fdc6f77527d00ff14f9862cdde800797f48e544e4ff3f8b05a004910a4b659d6e8505caf92069728f2eefb38ac7d8366a92cbde816fafce9bd1da5fe5923513f955483034772e1740fccc9a1e6adc7df621030f0e7bafb802c0c61236f5923f9db2aacbaf0bdf88965d6fea1a4b5baa603eb3493a55296836b4d6c712c0494b28cda70a4450cc77e2ec39b4157634e420071254dd5d0f9d9fa3d88cca102c367612132c61d64ecdb19051bc5ddb926e3740152175df906e4ecd725649580ab23b12b58e35a179f72a16a019b6e920273b2af8e4a1acbfa4a0df96abcb17894492d929a2cf5c6a58041acda41612e198582c0168a0171b4c3c6c7a000d56597bcd6e9e64468a5091f1e31b548c31ad84e0dd26fe32bce86f4e182c450929d4398a38d6d0c19005a580b60942629e1562d6570f8bb6c18a28d2ea64c1740727ee76391bf110cd95a706a3e02858cabd8176c0f87c1b6d7cadfefacb6e2c1cc35ff555a35bd2b52e4fdead7c960c7c02b34aab88e3cd2d06a5fcf2186be7a131374062e55fd779436d2003cc8c90190697389e7bb6435f9c16f4681d02c0661176d209d81b7879f3a1a917494d9292f729b2b743881f95256dc4e22374acc9226cd5b494dd512a1197afb9938f4e89547cbae048303902836ba6d2ed551663829011e94ad627498413718f2e18122f311a2a6ee2b2485637f8f1c46b771c1b8162003bb9f355da292fc69ec3802ba4b7201f82c87c045d9213f8300f8cce694cb99255b1d5e1ba9c2812c7d52464f4c7cd4b089ab99363746780cef8b31aed7c81237dca5a87791cfd0fc0b49a23e0ad90d35de7ebe73b35f2cf7b9c90740a6644b324fa6847bde5160184dd10f9a40c8e307374174e6cb9385a89d7504075942a50fa95ad47761b2dff6e1652a5ea9063c874a399b3280df686281e661ccbc70306485d2ebd0b8e8dc576e7d37c1a5254705445a602ac2e2515b069bcc220d110751ac0b77eff861a4d5303ca4cd7474e84ff0f5d0ec89b3cf389b7c7deefa81f8ea5678f5742ceb8db195f3df0a32f27b54a9d943f6d52962e2ab60ee79722a4c06be8a83ea3e80328c71f5f7f6b1e2809cd12cb8e902af42c3511b29966f086a3f1a2e7efabda1444e05fbc86efc77438b9b23a15d37f14b9ea590452532c822b16baee63d24529579997bc2b8f5aa7789d1d2b27678c174fa06d786523b6c10a6b120954c437c7d700e84b526d7c3546192023f4a120d54f490137f85d9720d1bf4c1182895422aae88c211bf9859bf8dacef21ddb0e62470de371fba1f1fc0b8d56219a1d4591c177221e96c13349f44ccf6ecd67049785d245cbaafb577000015ff2f87cffc76c58535aba99fd9f4a41422709217fd419d8c0b9d3e5db179cf191b056f218c52d816a66f0c60194165e3ff445812c626db809ab3aa75a9ae29a0e54b303b12796326001637fb40da964644d1538ee60047d9c627ac8335a2aa08a45075b493dac687b78b44acabd603120281ec40dc120dea9a162849a50336d8c1cefc582b12bc0a5a0c6cae349cd616c82de9bc3f33063cd5a351811dbadab485bb9e1480904ae69c30f547087d14d94c89a7415d8438b622243343c0f465f94ad8121c830b362db42c5dc10e48268276070856b6639e8410eb8e612216e5a589a8661f1a73e006ad232f44bbc2b97994a6de183d32b924f3a0a8803c52e68613189427a28a5b627a0087120c65972285ec9572b1aa19c002314a4c66d8b5084df04c510a70c84e5fb810b87deabecbb248057daff1b62d4288206cafb94815f4155feb53a329a564f6603a43565405b7b40f18f84c9f25827794034bebf1295416e4ba3b048509010a4458ebfea2bed861303bbdae08a0aac05635491294d1ca1064ac49b686fc8bfac8d72019717a8cca059fa2b99bb5f36266c5e2857ac11227a09ef0ee46e8aaceb6da3d43234c8a44b515969646209889f4063b7f18e4424c05a55230fb4a6a14e31acaf8314ca837f24d200c40cbfaa3f7fecf33d5ad5f8312960655839890eb1c94b337cf9481547aba48fbd54d3878e8a28f5f2e51b8ce9024a4ea2745a3098594d07d5dbe39e702190c4c26c372b3656a4389643524da2c38549a898296d9388c65478c4ba5211bd06e2e2f4eed5b91f03ffc3037107bbd19235e123075de3c42c1b987d21978ec89806330b19f7debb9bdc5bca94640a22074e0753075b357dffd56481de2cc96acfef60efdf7fa31380d599efcfa933f0a785eff5d899e95fd336fdd809deebe5a11968db26e04543e3f02a0b491a75d9d5f6f2eaedcd91ad2a5ef67215f8323acd4c0361ef0cdb390d2b0d6c2311d39c6cd9c7dfb26f861fb3d1fe2011d1fb62e3b43112d17b03bb1c5bcbbb60618a854796575696d84d125117bf45417d5c3e4276cbb8ec6b2b3bc65a76182969139be27f8d9579d207c3772a5afaf830c84ff0a114d86a418aa1c5fc62f8489fcc77f783f430d422eca49f301ff969a0cdfc2e0626e6617e832c7006c6441862f818301e005f0c1fdb7cbf6d90ab1fb769fbfa2f7cd7887de1b32ec70bdc73dcef60cb55ee8dd81de0f5c0ca72cd73377ffb8cd8f91191b6a662b046fdfa38fc850f4797a3561abe0fdaedbff95f43e3cc8f6dd4a627c28f1f1aa01712c1c6bfb075272402eb3054b529fef7b1ed81fe73ada14ddfc7f6ffffc3a14d3f3f7e8a6c23f3322fa3def962653e46a5b1320fa3e2f0d38c1d4dbd74a64aaebb41c62d77dbd04d6ca1f9e09fbf7d40b837eeb7ed77b0ddaaf62fb87af97b80767b7f234e7ffbe8f38547accc6bdf652a56a6b63c0e879d8e9677791c5efa60bedbf67211f7f20d892d3e97ef0681475aaa8d8843f5d2d53bffc506047a7935616b7382c21f2fcda7f137e0a71cb87ae7a435fed68252da148bda141fb6da14e3674fa017b66051cb71b8f6dd20fcb2f3990628d427de89fce57cf6418bc156fda278d36a947b2d49302c462c664792881993249e4d51072bb3d2671c1dc62a6c1cccb0e7b81bd3e4a7373727a011fb7eb4a969a974830310dc32cdf87183b5494e2b63136e4a349bf1356da24318a9c10f5348c103f4840455900421a1c2def435ff181260b84110868858474041d881476c86c490cd901882cd300cc3e6c43099fd906519123f588111e213acbc008b617965e5872b5861efdf482bdb4c3fb7ffc57e885fc4c84e7cf971200af1bfa4d6628c8fc4c84eac48b2c74817073b6b161ffb842c895588f848a48cf1257c2411497c2415897f9bb088648b1da17c68e107bb4a1f6874eaf78685dfdf451bd76c734eca35b409e6a043150bbd028dc3ac23a8f0918591e7882b9663c12089bdd007e79a7e340efcc14209212cb29586362de97a73a34df0dfc22e04412019c429058bbcbfbffff78e0c02edbf39b649fda42fb20df6f37b611bece3c7b10df6f2835d0f6a2f912233b2f7c7b2ba24c69af753ac38e8d0a6fe8e3558b244fe1bd9991912233b9f919d1907643fff4713ec8b643fe2cb5a24d619af933e0e39f8c97f764cd0999ee9fa6ec35f05c37ae98dcb2863e8dab8660637d30fed85d61d8b212cac97c6f2e598c8ee29dc12cb66f6527047894519bf6628fb0911f0004fa6d1ca26cf66c20ba5a1f925d8e3e81928ac01218533fc671fa97e97c82d62e333660379d2a7f7467ae4c1d61d146640483b14503f95206d42ff8a120b6be063bb5380bd91f83f3cd6224db8fe888fd522d917c1de082a7215242a48d6211591442c10fe1da46758e3c6e6b29fdf853afaf963102e476c1bf81b641940f71154b971c3e9df1b1b76c6fc309ac9af77e287110134dda621b3cbbd95b0cd307c1e330ef0c73e02f1b3ea81ece363d5034d7e641fe37b007baf33f279c79f77324cd24f56a184ed4a8325ad6e9641876ecfe241f164e639e79c3ee714387ba409b61df98163cda724f17fdb11f638e3667dab40f8374d725dd2bdece4126d5ad18515d88ce7175f7ef3b1ef723ee4cf0a84df7dcc8f5f3fb4fe5d2216fbee8b0fff3ae363cef870af3c2be708c2d9adcecc99f9d19a80be5d618185cdeafd4b99a3a4480a6a4131b71461f99d8943f195b73c8aa79c89071d597945a1e26a457955694519f218a5c4b00c4a0ccbb23949319b9344d2b48da469dbc6719dc66d1cd7759ec702e1d7f9587eafc763a145d023cbdf62c5f2cbf20b4969ad845c3da5572dd42a98c4f2f74f7b9092eee923cd13858aeb55a5156508160199c020155402fec022200ab215cbdfaf76f8c52b965d965f763b8a58585828a5b4a5a5a5c585737171e972c01a258665d99cd92491346ddbb48de3bacef33a8f8585d29616dae2e2d2a2c5cb8b0b980803e30d615abcb870010313135392893232de50a624f3e2c5cc0c0d0d0c8e1163c4f08631a607bf5b03bf7bc3f2cbe4c8bc78c1c16f6686831f4db703468401c31bc298991eb7a9c50b7d79797971e1c205e6e206869f0404bd37336068608efc00638221f5501aaca75423230365a417d3f588f198ad34334c964ab0c42ebc6bc442f8500190c26e8ede2d62e1df96564bcb4f2d2bb7e1e79696e587dc0f28f36f41656f4bca32d79920680b90e56f719db2d8206c399906e4bfdc7a1cdf8e29a0b4892d0c1538340ebfdb1f8dc3bdb4da7f34ce10681d729d5db8c1acd18725110a8c55fe4a254a33ce6ac464e46c0a0b9f616741849e20a48b230750401f877310e9e2e101841fbd089b77f37bfd9a36998848cbcf900b53e827fe0b80e502e858ae2fc37efc98064759e33733d91da1292f2bbfd4c1950e687c4d93d2389a7ceee573722fe65e24db6c7dd538d76bbd785f5b87d2382d4f8a37a571b649af67b3a11cc84f70e536f24b254a1d684e4a4ba5bf26e78c4dfdde3704daaed11b4328ac6cc1cd36c967f9b8cf6b6141b9efed5b3156a0a4c7ea902845005cf49a80792dec3f3e792db6918f61d8433e3d6f02f0cea000a4877df7c9cfdcc79620940b7b830e571c5c6d28eeb5a15e56625e147c9a2fbd25d8ac724bec254db591f6204e362816b2941b2ca7a09757f779bcc04143e3485ae3df603341d0f8dee5c022941e25da15bdd3f612eee3df1b0e876f9f0e2054dbb0f54f07d04f9b628e40ef8c39e2779ccf42708f64e3b6ecb91782f44826694eea2721b44742d248a4e784e81e89d671af6dcf55214a3b48b626f2b9df9e7bed4910646fcd4de3382b042bfb6dda47fade6d620b8d1f738d95e327638c314af9f1659432bacbce9ad8feb68535354c037adce1b56387cdde14080abb0a23dbcd4326c13ad829b041ae67586e0fc2ca02218b773bba1d4512b6c7494177dcb0ac83a760b03dd8e9701aa3e7549041939acbe2bad891112c2254967d6099e563c56eb0db8159f86d841d222d76856924416f8de5016d37760553422590c7038bac048a1c507b3c1c90c7633b09ea47d8116347b69fc57585ac016568daba1b3a73c70023b1bfe810966008411842108cc0719c467ac869a4d913721c9771199771d9b665d996c119596bfbde7e83829a2c66c2ba216b2d966562aaa0fe2c047a4d167b939fb47ae7c4e6d692b3969a36913ebe7ced8675b6c77edb3e68990036f46fb247bfec7af4c671bf7d1c84dcd603b45b9dd12658b5af86d8a68e7951894923402641b8013e1d60c52c24575e4558f72fe23fd62193089b073802eb300797e238e40f85df03b3ddf990b6bf27086d89a4a16d608f756e53f45181cc684f9adc15288f3eda809fe6fbdfe974d6acb253135c4523edd8141b75016dcb2c21bc4d26054e0c19b1b2fe9b94524a2d48ae208410bea83c722be25230d5d3d3b93c150bca3f3bb2ee1f3f6c0259f797d9508ff58f3bd8cb65fd3fdfb99ecafa7fbdf36226087a5950d63fe3e207778cacff465a125f56b9a22bedca86659334492ea86a541bf92afec49747896533bb935d50d528235fc51f2cc3ee64175435cac8579864175435cac8a38cec82aa467974764155170585eceaa280cc939979c269444444444444444444444444444444444444444444444444444444444444444444444444447a11c2c6351b6c8f12cb2649dbb8ce63a12d2e2d5e5cc0c494645eccd0c0e0dcfd35e3ffc25fc6bf14e3fe17c6bdba80114346c645f107c6c54b0b9716198f19f5c0d4aa85b278dd7fb2c7a8e3b67eedabb15d8dd2485d5fe050ae82aae9aa176a1ac5aefeacd3a1d968148d2c7b0c50ae5e9c8891d42233dda1c9dddddd994686c5c6ccccd03a53ebee31467777accb114b259e714ac66413ccce835396cd40943b0b1c5060a9332cf384b0032a508f098b0b7a9234415f2cb37a7a54945a660571644d41bcbe26081e7bff268a7e68b969637648e484a014e068bb0ab67b497c1c7814f19831ab5d439be2e3508f206b58d0ad71b040b20f72437a92a663d61b78a115c231ce7fa3a216894581964a34108265cea23b2aa20338d282d60f56d87e599a9487ba20458c8d13a38a17eb8712d866826d2bb6dfbb1ced34a051d88f88244860628920ec800a4888408b31462570421a62fce18824434481042bae50eddc0989b09776515bd14ba15716087c7801927ee6e4ba1cfe184f1350405a416be3b922059347f33141d64332a208cc3571e02379b21ea0e69cb3c482208c08219873ceb902200c4b421073be9458b0721bb6d8c7e9b213b533e79c33c9ca6dd84f74cec9f2e165270976e6bfcc2c0990db3010472c208e60540537f6658c58866145f0c9b2eca574c411471c7184ea8f589972603747322a30c166d9bf645996654a7eda86816650db604a4a9867850a56e0821547ba2ab4c5f2ca8a1594ac102ab7612b65cf14984bd3a4c4b0c88e39e6987be42c63c6b0ecf92332ed84ffbaa2bdf611e9008d449a8722ba0d488142172665f3ec999979869d91996b6040f96113cadce5b0768f1c21dc81023333ed1f220f2758f0840f3cb892821ff8605db801cc13147241eb42d0159e8be0718175055be1030eacb022e2c709a3215eaea8acbbbb37a1fd6ca3a541c4a7f101094b04adace0841421c01c09a5e1362c9ae8596f4663dde92b18d1c12a8a0d7ed00407200012c2c55e1bd6ddbd63776173779702922a5af04312214a10839e16f6dab03bdd4d5f009db3b8220826a8600434c0420a18040b0eac732403eb0c5b1b3fe43951145020fedc969b18420f2c843e58aefcf5102d7ffc7cb018b9f1677a683656212c969f91606fece52cecfd688ab60cfcb013f296f23cc142d2b2cc42b2b22dcbac2355e4d828f20168c4941b18ed64ff01d80323ac8067a70990ede7f7db609dd614e07dff0bed2f1189e3cbd1c1f65b87e3d91c3fc11d560852d67b2ffa173f88c14bb9e5a31f16bb1cdefbc73dfc583e7e371ac7fbfe9c8de5632122ad2744011be43ef825c8a1fd0e5246850d7fe31238fc8d42b63f26611d97ef772df069871bfe461f1af0370a61c3df48821afec69f23d66961043ec11e1f7f9d090cf8eb51e8f1d7abe0f1d7ad48611d9a049f606ac75f77a2e3afb372fc7520d0f0d75b29d669f97e27824fb068017ffd043b7fbd05f6afc700c75f6762fbdd87753c2af8d43c0af8db5548c0dfd60202feba0f37fe7a8f11ebb034c1a70e9ae16f43a1f3b78d7080bfcd849cbf4d050aeb70dfdf2fe053b74e7f7b06387f3b0919feb6131b7f7bc53a1d17f8d44706f8db480af0b78920c0df3ec1cddf4e62fbe1159804f6aff1173a61007f211534663c2c629dcdf4174ab9108a0b9b10c05ff8c4f6c325f8e4436ee3357f610a86bf700631fc852fb85005b61f92804ffe721b255807c53aa417fe421fdb100973e1f215b6ff027c8a3f6ee3347fd9889d609b9360bb079fe22a08ebf0e0cb501020836d1af6e5efbb68b13130f0291ad5b04e7c2d2847e7f889b4721bff09817a277d407540c52167de50ef6c40bdd4865a43bd322ce9c747bd9301f5d21e3da41febcfa35e19f3b5a3dea9a35e9a23e54f43ca4f736801f5ce9d7aa9854543d61f072cb23e811450ef4c40bd14013c377882fc941dcd50efd4a9971ea0ded291f5cf096af9296b9dea9d38f552195a365afe06a87716a05e4a803eca82acff4d1f59cf6ad43b07502fa5e14033ea95e1ef437ec28afce502a87706a05e6a536fa9c8fa9b862600ea9d34867a290cb5c65f3f7ec27ae2ca4fd2f542bd324ab5de12fdeaa5feff3363d44b61c495cbfad3c495f58f467e922ad782fc2467ea95517a516f89cad44b55d6bf148d664cbd93c2d44b5dd42b51d6ffa55e1976735935a1a83514815a00e04bf4e5a55ebf62615b97afed46493f3a48059180e08b3402520f89e7c8c5956869bb794353b88a5a7002f540950aa25d85a5edc61dcd144cd4ec993c435dcb0a235791e7c1b69b16f4c44a956a9e2de3c98cc87e3214c77d6db5a22cbc8c5c2a1296c288800d612b4ca569b5edfcda6e18d6832dc180b01f0cf5ca5c591cbd8ce6ec2daa90d01a5a99eca705310dd766a00dd98015e404484bc2fa5ff963fd650f683ff68cc9afb5a01b41274e069e81b4823526842fa6881209dab15e68bbbb2176c37a495a0dacbfe401ed5f597f2dc859ca4a536929eb270b7dda609d4600a90928eab5617166cc15343a004e20074cec8089274f9e3c79f2e4c993274f9e3c618209269810121212121212121212121262820926988022c552dbdf1ccdade93a245ad701e536dde0d0ee3e9bc5f64b69c835a61931d8f8378b31ce50036bc4bf72623ffd947d0df611c36a4870c4620feb84106625a0f03d320e12f4427b67dcbe60bdde20b0c75621d7880e68af09fab4a947e680facbc71ec6e85df76f5997a3654b29b12b34e52d9c530262a028f385d2946aee0c95957ec3f286721b692a95e6f41ce84b1e25964d92b6719dc7425b5ce2ca6e2f2e60624a322f666860c490f15fe5b87fbbd57853acfc9a1aaf76395e1b4e73af97c3b0c1c0619fdb0d3527a591087ab9d78bb3d2a7ece56f00e0b02fda968f7edc0b68a2b8d70bae88a0577b69afc67179f99a953ecd972fd67117b882e2799e27f39ee779def79ee7795ecc7b9ee77934ef799ee771ef799ee7bd78cff33ccff33ccf2bbde7799e17dff33ccf7b58e4799ee7799ef7500917ef799ee7799ee7bdb4780f7bcff33ccff36a1a674847dff35abc7fe97278de4c0c99970143c68593ef65c4803123e3b57012332313e3b938a1999189f15a9c7032311e479dbc9089f1381627311eb7794e4a311eb7754ea2c76d917302e3715bdc9c406e8b5073e282db222439e1b6f8d282c46d71c356d3a82d665b6c1dcda6b4496eddc282692c1da5477a2cb7719be68f95dd47a03e79ab15d44ac15ce993bffc158f441f7fc5228e1a2746519f220ae54219a1a434cecbfbbb127d8ad185a243891ec5fa7baa7164dedf517d8ad17fa203450fb2feeed3382eefdf55fa14a715da57ec11eb31437d922854110a4a2be9939c4cfc1ba8715cbc7ff3f449ce1e7f78a571e09fe4a442ab586f71c2e60a9b49ac122638d8bbf6a59e13d6721bf9252a744a0d7dc25e411a67087d21300bcbf243485fa239a89b120d7db16eea5386aaf1bee594a5dc46fe1021300bff18ba1cbeb2fef213476f496aaf17ea857aa1527ed25aad542bd54ab5fca4a552ad542bd5b2f2e3cb4f1a2abee22bbe507e22bd5ea817ea854af989d46aa55aa956ca4a522ad54ab5522d2b4973ced70bf542bd50293fcd562bd54ab5522d3fcd54aa956aa55a56ce392945c9cf507ecae6cc50f60593a5fc94b5dcc64fa512a559cad278eb85b7609c2506f78231536fa954aa975618bc975709e6f5f2aae9645a2598d64beb05ce454bc1bc6829172da5a5acfcbac5682898170de5a2a1349495ff69dbebc5e5b5bdfc492e5a2f2eadade5322624a55c3652ca492918236b4142b96c249493509084b2f26160d8ebf5a2912de86ad596953fa3a2d15e5fcdd436533e53d84c5196192b57acb4d226d486f289c2268a145f78466d92912dcbd7367bcdacc593796993f49337a56de44b8f8a9559cb4fda6b52d75edbe74d11f2011edb154279b94a25aa057ee2b68904e0846cf60420005462e3a4a0b0089b31571eb48f07a6917dfc88648069642f846b64b0c4ddd00cf5d0cd50e9f2ab147788e97a78b580dc7272fec6bf832ed5eb9073802e79f6eeee2ed3dd20ddbd87485ab73a98ae07567b809654a75689b8626d70247fed73db829385454a297376689c206dca7020900e39aca33dfc1bf7e6e5afc9c52dc5fca5b6f4f00fc033b09dc1c22f7dae6a9b0ce6e3feb86dda26da17ba1b60cd91f286caeb7e73d58a85e5b70ad3f5e86a0fd07af57abfbdfc5277837c96da03b4f321d017a487da943df701f13b4d620fe5d8ec63a040fb67bdfd5b9595d380e6ec21daed85b05816ad0454d372a8b8f637fad4436e93cd37ddbca9c4edd26667af6956d3349677d1dda06937b4bf462cb709de1b0b79b04e37eb74fba458a79b6b5cba1cbefd4b778356ebedb7409b32ffabbdff85463bb429fb96ae8756afd7fdf69b0cdc8d9f5ce536d99bfe55b208f442206c08116a5dbee5b76f8341ac036df69e4b77c3f6edb5743eb6efdbe27970a7b4d4e2ab800526bb7c100a4c23fb960f3a816b64dce77295ab5ca552a9fa65dd81d61e5df759a00212079407a6015daca3695fdbfe6ef7d8eca11358876d9641239b3da4d238f1338971d3e85da522dae2c16db21ee2e60ba529d5bc29e60934fe7c5eb9ca855a147bd84018621dedb37f199996961edaea4c0fd16a2f8465ded926d734ae0768b77afb6157205db540059a5d3b54c002aba156914b0445b1ebe17dd71ea0d5de3b1dda93bebb2022d07afd791a0f1dd320fdf65d196e6897b56033226dcafee6581b2f9007a2b2dfb2cede851aeb7cfc0e20b44dd9cf6e475720a40a8940bb55a804c866da670f9504350ed42e7462332578f07a6dd8fe1b130a8d526229cfaffece3a35c3005cfc5c055d2d43f6f32302ad8fb7f2e36fdf8d9f3a4adb649f5929aa62b31b2a43536cd6526cf63156a017be5687c48e92695d0f58699bb2efcab5299be9f1397bcca48c1c80c20925a0405902a5e646948d8f3deca95284f59748d03bc3c6df48a4165297839fb356d226b690f909ea538a47e8152bf1a7601d17448167e824b0467ce9270f9a458ac4aab911eb604e858dcf7536c0c77ee3baae077ccc3187f0b7c7dcdd1dc3ea8c36396cb98b6de21b915e5706487f03d50e62eeae1107f47acb5b2ea54f5ce3dcb8b241a9c6e1db29a32a8da359619d0dbebabf7d85f5befb84b068dff56a57fdc706f876835ad7037ebf0379120ffa9c49db04a954b4f42677f915b7e25590a0aacdb5847b1cedae762f95fcdf5d44a0751f6f5def89eeea699ceb3d4ec5c768d252f41eeff17154e338b7aa5936b69091771b359596d2516cfc86e25578c01e300c89b6bf5ba84db1a7d8d82aa611e31214883f11180464a3b7dc267a50e330691c255827c8c6eb4b7cdb6b9f1016fb5abdd07fecd6d9d00f67bfffb4293efc240ee4037a3b15df7f3c49e3006d900aa7d2420d5b055b055be5541ac77dbee6ba8bdb22d74ae2fc211b6f54a271222c028abb3ea7c234223caa6293c0193c0a5c237e7c9f9f780475c5c6e856e2144c23fe6d17d8f85b8c15f47aab58c709ebc08f10629d0df0dbba575884923691ec90087f20cae2e8e8d33833de5d7e6a95e9bf54a2d45d367aab6cfc56b510ebb043efee861de4414da6116fec7ecf21d2167becd3424027102140ee0d0b1f35fd63c126675446312705bd54bea4f34bacd316bb6131ae3cbe1a78b4099355877c7f973a8c58199d47e370f6d8cbf089bff8c5ecbb351dd9e3c7cb4536a437942c312f3e333373e436612c2286ece5218b3df63c1a07fb1a4a3e27c5624663b0a0f13dc3b01e297c52d689efdff232a1851c7dd00417f38eb65ace4e938c87e5bf9b85f1a6736c6320c77205029ff41bdc28a46d6af96bb292859999b9e58bfcc11eac760fec813d0eeb6d1cddf3b9a3b9b406c9b1b07e2ddd6cb0a74d525009e8c06d37e7b6181b728c104ae3c81f6d92a6ff52a9762da0f07390cf90eb03c1f003c305778d19e010d7907f6958d8d359083f4eaede2039f68da06c6f090e4138d41f8cc21f1c827128caec895d0d462ce4879ef7dce5f0f893df10e955530e7eda5e62980cb6bfcb4536fa0ab7e2586cc2f6776b22c4b0df368eeb74b4c520d7260c8b0d31c7b0374519658c261d6db51a99467c18edfca295ffdc0bcb278abd2636c92fd619e6a72f8de3df722398b78041d012191859660de1b291377937e7bee8236499e5a384ed0e320dcd429717373af046c6f7ca433206f47ddaf83739e8c00ddd93b2462f62c562b1582c168bc562b1582c168bc562b1582c168bc562b1582c168bb5da2253e15cc549325deb87286f4099350413cbac2194d8adb9d214d24aca6ac550e29c61724134dfb271009e611e5db1f0492b9e7905090ad9431aca82b4c2345a9149eca1b2f224475252ed630405025de1221bda6a06add92cbf246d433646692d118be213928ddcc61aa1c416f02907027f630c1ef0373671409b95a8844c985545ee14cd48000000c315000030140c07442271509207cab87714000b7d90407262349346e31cc86190338828030000000002000a880808150700afd24dd1d380977fd1a75b35b4f484f2c049abd401bea2136876282e6c0e12acd4e64f31b159588282b123ed0c1ef2f62a9f9bfd8ebc47436a51bbb890eb8f6fa770c5840f1b73a1fe7ef02c5b4b96c476bde4c532c9e5498739a16a4b585ed3722c495c9dfe8bf56867610811396fc41b526da786feabcab2113cc3676893d4d9b8b2dfd880cec9b8f2f59204ba60831940ec89900888970269f447a43598c8f435bde0fa090106fdf19f786cc8a715cfc01609b322b4d326e82dbe71456b7180643dee1ecfaba8566aec88be2713633878a1f6fd2f40085c835d2d0a99dbb385e4d2ff1c1a8fac69a71ee41eae3a30a40a14dab64766f10dffdeb6050e76eca31ca38a154dbd344d333dff2e4706fb72d296daeaa99a2a0d7c31f203dc5446acec09259c36c3ae0ed7b2ce631daae1957e98b328afaf73f64bb04748324a174d4981d42eba80fe4530318327365475b39f34e514026f9ba78846d1bb85791f070f6c9d5f73575a7b43d8cfb9b9ee72e1d64d151cd1c7de948eab2039857d5578060e0ae8fe05a7093bdf7f201520e9ac2ba308d5026032cf39399289caeb011fadb0a980a3b16cd25a40d6efb4893eebb2312aed305f459fd2d96c598cf025cd2531d9e15341bfebd4bcbebdc8f89a8d1ddd8fa2bf1bfc88b2ceb6c56e098dcb581772e09cd8167971cc4040b811206366f05d3376d06a8c80221c2090e3fe1d40ab1edbe158d4815477a7e29be432998a1ebdc53b5f27dadf0728bce255f9e11f50c2729ab9a05fd8d0aba5df05b305672ba8b3fffe9b9ba61d40c3e4b2b8025bf3911e0db863e51a1fab60d1112be1fb169f7f12ea829caaec69007eb67ece2f622d2329aa7b4ffdd8c7e592b2cb62421be33b19e062e4df4a42c20ae19b366f2e7cfc347a2b03d9f5d3b815e2fcc65e9b20b7e49c435ce57c026f989d94496db0a7a1e66fd4d0851437266a035ba3c4272341d29a5469447c119f5e06f3ac415c3397588cadf601b5617977f578a1d2729a7f93762ffc0c086107c5f9de76419f48734b57ab73a7d3789057f429214ff4f4f60a3b0c9bd5e4d5d11494a8d6f22754da5610d8afcfd2520a8e6c8dafb81db870fccd5d58414b516f20cc673bfde97ed0089ebda3f7a621cb58b12da13c8aeb230844c95e0a4d5fcaaafa15191821ae0b5fd3936350d96e28638dfef6b51c0517d118fdb193d1b2dbdec4e9bb998a622b09982d7def7d5d971c6a919e6e973c288f3a6c84a5e47f31ce2c22aad883cf8a8c591bbb6782dc33b93c8720fba1dafb53593fa842c6b0f24930ee5d7dffd31bdd7665f905986caf18b067182fdcbf50aa4e751f8d39ea7d208b79e482a3a1b753088d37c76c48460a84d44104500f9ce749d7844418875dceebe59c7a8d403e29f4d963c4a1de7007933ad95dbef074d37907f7cc886724e6ad1f91054a095a4022a40120b8e552ff59e5886aa55c56915d89fab466caf5698d1f5d027f8b85364dd251becf395e7be5106afe9ad2f2df16eff905c119dc7b2cc1a3729091385cf05466dadca9a2d0665d2ff87ba8cc536304dbbdf38c80f5cc3a99500a09428ec0226c332e723436c3a16b6efeff85a3b4eac04d2c9b43fa2bf1cc9a88b3348b1db334f325f84ba4766082bbe6a2003a109593057209b6d09748d4b95b71c12bf2c24d21d3fa888afea56956dcc539a19b1fff04f739e67b09a797fa3876297070262a00fc98a4cdc6bc8f451f90fde438f95a26b0311f56dc3c6c9b7c8e1b136d6629727ad764cfdd0847485ba74b6a4fcb033d31d4d588dc956f16f83f99dfac7b4bc7e64a8c6a6832994900e4846e2932454b4a95e630576a08aef29d996abcbd47386f6b7c86bc40cbd80d2ba24bacf4d479ba347ca9e687a9ae45c23525b503d54c379de5431d62206189831a3b135233498095b22eaceb6726244086dd3a597504a0ee8d9d57993d76f63ef57e3e76d354edb1fc5cf04922f3e63ed11e40261fc7615fed52e2b0b59c2246a9f2e55fa2bab5dd20f909ac8df9e843d870798c94c2942aa1c7e6815c98c1b509233744df8e610b30f857e40a79fc552fa32c5dd64da0b30d317dff910c20bd07be9b060c857eb3fe6cf2670909de9b7e86dea59e6f911e6bf4e4de48b1cd27f5050ac87cf0fd719d359debc3233feb4206db2b1c2ceeff517fbe135f0cda2a79d1a32e02f6b6088b272d7e3db5803b4ac0a4fa75c1767a1334e9153ee3c75ef0dfc4582d894d0f272b98410f0e64a9faad40967e195311504964ed6c889882e178386316e37eea47a2b6beab746fa39cebd55a92782709a951c50644d742f494424d5851d2f989dca8ea76324bdd223e39c5af8cc97a19166e4a22d103c710a1118c50a153dd05d170d302883da6690e259a12baa5761e1b0264c80fe571f85536091b836cfc87048e0503f09b86805fcc33a27d17e362db15519133b55162e06c6d210319be69ed31862e62938c99da893becd210fd1dd903209e32437504fa1cee0a5aa7bedc40d069a1fb422d139d2cea35631180a5e1c2fbbe11c270081dc4b91bb9e258b1d7503aa7b5c8f91ba302b24165203c2f22159f18a9acdb3c60114c5653f8d5cb34ba44dd68b41921cf58cf00ac74c11503fc7c79bf5aba370ac409d32cca419d566308d4a906f710ad98567ee499ef8b8ca3e0bd2424bb284c65197421661e944369a5b3a4801f65c6b149d5ab84cd46855ed13d6a98ef1bacc9256e99e81e80bc8cc71449f6548015e8cccf609bf7e399c34307a80c1e9d3ecc921c299c82fe3f4bdf4aec1e3bb311a1c1fdb117c90bd8bac832d77fee30d2a8f4a22214b17ed5465bfa1b1c775d5e61b6b6aba1c9933e7910a3ddd4fab06dbf38ba3a8fdcd6cd7865c19e27a7e222f34d205e1921bf370c2a280e44b1c70cbdbc13d737c70c5544581cbfab6dab788cf9019c52a1b22097839d0a81eeac743bb47426b6df58fb3c97688041f582310efd70cb6a4424fa9cb7d2a50d04148597238f9222e3a428412f363272f062d558e2a65ac0c56a59e878f2e3b7c16a7226375da5c08bccda44ce6ad6396179132dbd0947e4281c1b0dfe28bf53427b7b1853f961135e9c0caa7b4337f176965fa4b92bc0b19c388886d86e4ae7f08804eb6474323b44a9de7f6176bad0b43dbc4a37da388c634b2976960996bd1497e1ce2811f6739404c3609a26bd7c828805fb1e320a0613d2621e6d45155ce5ad25faa8fdb4175f3e592ae11763ee49921bc3031f9065afb31ac5c8ee604047e56ea8fbf082ef7494a69477916b0d12e309c900be37e2cd11ad79f8c249e99d60d5f193402e8904493e0aa260561da668eee86fead0393010be0d7337056ed61ba112b462e85745eb6bbfe4a6a1a5ec8091cf5e3a192c0531920ba28089723d0f70370fba00953c0eb781feccfe8788ffa300cfb57d0bfaa423ca24b3f6e5a20ca155eee9323e46f54330dcf22792a7f4db8bd24cd081528c50793e3d29cf95a9f38fbf83a87ec03c3694a9036e0ff54019219127a52604e8b29b3353ebda624513f8511f87d6cdb89cc7058a4bfc57d2f9c744d3ef51897b99d96cd8b54b5a9a609693f85d2a0c15688324cfdc583b135e6e84a37cf3e3e320a3dcce987b80cea7806948719f807f3c2dc961f74e3d29df0c9aaf2e0b8132dcbea23008980bd2b9eb9ef0c20518d63fb93fa414fe41c0a287542c2259b3739079ede42c325c7a2c6f26f07af2073fed408126e2b43d31b5568874e73434c3c70f70f175e04aa2c2d97df7dbdc7a6c78591bf0060f73cd80f524a2325435f0e80175db8fd4e2ff724f02074b5aa24e6bf7ef693d6d49bf28efb4072a6845b10676cbd3260009e0f31e442b1a098d47d3bcdd2b10109270a64dd8f0a683bc205b8cfb476327bc4b42e72fc5792597029c3205bd22cbe5eaf1d3c56fae19e1686b32565a5a68c2a45f6833e87828a2a612831bf530537d6c0df1671aae374428a9a21973531a7f761e7e8bebda944e668f3e16c466448522899ef9d8fed2de74f4c76202b7f41441eb0490e3d326e323a161fea33efb6042c460c8c84d7bb988a4cd930773feb00f489d404426723097dc4ca913b21ca8706cb653542eec9644a85a2441002e4a08d07efaa58002e6c9497dd30425f8f7c2e952d8e29ad471f4cc9447e7fb562599cafabf2b75be4a083feb4c7d9df6fab7f8d0692a2c4c06f7a401e8c25eb20644a508f8a299e871a46fd95046ddbe03ff3022cedfd235567e639a5412507773b76e084c7f80fc0eb2ebfa686eef9b48ad0f0eabbcb7a9c42e421cc224c5af9feaba09973792a84db292b7cbc705a4af62325f6e5d9e186a624c40ea0acc1c4f791bd2a83b4e33a493be3b4c717d5ba317aabdea826e245f11732d3967b372f85e259cc23c9412c70f225c843e598f25f76ecac47daf1d73f6284a885e2bfb6510098816680a086d6a5198944df19f2c3b60f59f50a84381d95890cdca9a4f8d6f361af8c282d27bbf2aa9ec8760375bafba6406fec3186b64b8482b9ba03461ed29b4da7e16158b3c5c6654f1e438edd2dcfe072e9fc20213068aa64fd567a5602588f66dca3302817a57d1baa9686a6bf071a7384f88cbc2567446258b112e7d4936989c5a68c986decc7d44cda2d888b82fcfc8cfdd40672b4be49146122b4634f3624c080527299272f1ecbb6b48d34fec5236f2781d20b14f8055a7a299f02dde1412d9140617f3bd7d507b0e411fbf80d4bd7c54737832a73d9ddd7a9ea40230ba815dc71e86725d61a06852e17741e4ca6e73201642484129ce3e6a2bbd43b165340624d988395cd7f7f7daa58683641cfdbd1e290f102808827b44c7a4c62ed08b76a1405e93245b5e0ef357e3cbbfb6a7ca46e539c0956cfe8078a6222209e996614e1e104f46f44fd153cf2e8f32de224cab85f515d219fc580b0735e68c6bab9c97b3f1d919c281c682ae52f965b31fb6b175e150fba1f7f2ef37bf33df7dff7fa5a8ae498539b5f6f196d8a12650edf1fa163bec1fb9b4c0f8b49c0b5e0984b5dc9fbb3daf4db9f7761b72e592b34b15fd96cfe31607c6beed2f93aa78fe23166731240a91a221c2ef73351aecbb694019701bc241e9343b25130baec1292a762e4331f56d08f5bc05a788d6c4bdca169c88f7128ae4ed2ebe8f5b8af928bef5e8ef9df92a035942ffa0ea6d2b7a2b697c091a3341b5979f2c0ef2f14137131e9bdcee0be22e20f1a5620dad008bacff45552ca03f8fc9bb30d0f1c21945fdaae6f35d2c3603feeba706d7ae9e2211ee812ee92ea19f931fd8b245fd80f38f5553f347969472b221827b4109ea93cfadbd5258a88c3987376cc298296fd538eaec3ad5c2dd0703418f243d255bfd9f99e7849c759a3fe793fce6f677b7c41e0df8fd341576420fddee677906d8ee6f2b3931988052e9a2742f19643aaf974ebda209d4dfa58ad91dcdaee603391d7d6d37bf312da4c5c538ad79a2ef6dfcd4318b2ca8825fb1641b27bddcabaaecfea074acd6a5acaecb0f0f8c56426c3e7a7a1b0d1807d470ca49fd544d786d9557df9190d03df0067ed84962f61529b9b7fb045172d45a6f208ea78c147a69edf173f400b7b16e007431a580b178f4006f71ee0074f012403171f4006f31372297b8197dc0b7b81b20fa5809604c1c3ac05bac5b007a4c69c0583c3ac01b373708a0790e81a0d369c21b8004462793102dd25b6050ca5ef4cc31733d34d06c9e2ee1d9d6def18f2b527ce36cb11703d5e7f30d6a9caae5af3ade952401d1d29d18f42d6536e5e56deb83187491c73a231b0a88f04f0717d40b0d57078c023f6505d62bf10b00ca8aaa9305a08c37c87c91dfb20cc0eab60ae66caa98fb4a6122250a24c78fe23bfaa4833b85945341a7752a66ed2a468c0282172f55708c46e96b4c3a1a5b7c2220374c4cd5495085e84f496238094d9215ab54d524dde5dd82963b8de22a41eb438f450770f3db1d101d5e1260ec177a006e7077fddd70bbf71b95eed73b4545f344fdff7bc03ca65fc77329ee0fc62d1adbedd636139fe8eca41cb86093d1308c9f30bd6ea1591d28af5e546fd255d8f5298644378de4656a9a7636fba2e0fd0effa6d0a704be6adca9cefa54ed76ec88a6d78b0cda99a65099948bfeb39ea5f6459eec402ba75156755d417155da2de8efa38554afaa6f04f23e2bbee140ee939c42470abd901a74bf1213fe5089dda01f7f53b3ede0fa12d99630004f900e1e3e9a3fabbecb820fe2c341b13b638cc4a0499f07bf9431ff36301a15cab8e79af1352086183197f78c2dec95e97cfb8ec2f2b83ffd40f9af7623b858abc2258fe30369a4749cddace69213d67b8353663b2e52f9384d68f177535f23119d66e218df0dc728f197ee8d15a6f183373fc64c8e908132b71e284e32ab0e7e1bac281e4ace1796fe8ed31258d28ac16ae292d8339256bc7a8748a2a08eb948d8b1d83f1808c4efda9ee4fc85b53358ac67ec7ff907c1427663fcf11c237cb7d239549d73cd100653a09e7cf0910e1225e44bf05e9e6b3f7bbd7d543e7f958c325a379eaaac9b09084d64f47b730c98d65c29bf74d43d47f4566e8dd33f977722b9cfb7fc0ad07ab378806c3ad068508f1bcbcb6e76aefbdf8e4551689ef56fd54603a6a1535476f983f2ea50bdcacee9a79f183529747f71ca5a5ccc29750139d1bb67fb373f0f697bc4c52f9f8c41382f6b1b41cfcd8bff1cd9dd0e4e81f4bb8526933afdfe68d44e906cd77edcbc7cfb7b68bb4cb36f123add91a59fa6aaedbb1e9e03f25adb0a0b6c4a764cabcbeef76e753ea2b8ac3873c655669de9922c5f79dc97627c1817c75d44ab0f09e057d92a85a63d0787d87aa3ab4155691e0c72c22582e54eb1272e8b24df64a06f7fff93243745c130b2783aeeafe3e37794a5d5d4fd8477e8b9acfd610106048b10a4059f2c107c27ecf0c56ee839154399ada94709fe1c3c87062b0c51c3c3d69226a84c3bdcd3d97f5094d9beaffa56f2104d8bffea7ff4f37d2f95be35a168551ffd97b92578505c911c5d1863b7e4133558fdd78a42ea43dc2edad30053e6d148dabec483c53949a8d462febc3aa5839d12922c4d3fccf7760088ea0830b6bf858a936d9befc884685af855754abf6c483589b03963c0068401dc2276271c1760477a123558fbb59a42eb426f25dbb38039d9c870171d6a34c35bd9da21b42475e497658adca0d3ab9e5e3e092a284571f9d3643592014c909b1bbc4c698b45dfa391d55627655986baaa140ce0b4b10c3ab1d79e7a3712315de38f53484d84d14f81739317563829f764e482229335d9a588bca370a34b917ee7403518562d66e59033ae44212b89bc2396ea4d20bb6d7ffb7fb19822558a49059eb2f3ff636df5ace11fb8d48f56f4e8bfd7c210fb18f68c723b9b140cbdb9d4d38f71544709b1ce97018584513a693be455067762d2670c1f60d3e8a4b7f9ed606ca30125806c5e9213b3a7092837ee0542d2f2da5d4b6878f6b722b3788608853c4a5ad71dd2d9166482d282e8387a2830dc5a6cbb39829dc65222376af7d79804827b8259432d03d2cbfd47e5993f8ad6df817ddec170028dfe30f0e4c5bb03cf87399093ca044ec076f3e3ebf050d495f24c7d4dbd8f86acdf9592c2616b626c29f4a6f57c372edfa38e2fe86671e74b36f1fccaf58373b370f12576317e85ae1bacf45ada4b54bd546e3c81371617a7c416e3afd81f786e8b0b5fb289f12be8bc31a517237a5dcd5e8441e3e53854298bfb004065acf340cfdc265118fe8aee6deec72e568f17d30e29f35a4e0088e4dad3c2d6114913c23c08511ab05a2ea3bd8143e94ad2bf90ea072a0de58284725abaf4bffc84283d4c9a2697b5aa8fa167d7f3b86bf3af5a089cfc57c8d7c49e3a206bd97cf911071a25109738fa083da501cac246c16718c4afb94d61aac26e60d9eb126b5d874fad9d701d35571c093152fa21c1a31a2214f92c9e5d4b7b0ac802fcca4b1e3ec978edcd2d6d0c600519b91621d9a1e22467b8e155c4a32db470182b6daa695d7366b09e1ffd78be530f1150ebaa66811b51ebacb0026453b7f315a37b244d489bf9c86c35258f10188b1bba6a51e821e5012adc4465fd24fb4218516f20fe63c0b6c94dbc329b4f921c2afdd9a9be90d6d724ffe03067b505506d2a8886611851bdbe0fe775b6fb3c3ca02ecd98d588114962a997d4f1257127091e104d1af960f6c19cbf60fe0a61fc36798f23c19bdd7d4ca89917d1301b83cfc5dd797904562c49590136468656c7ec4c3f4d2f03a42579ca80a5d379697e9d36fcdcb430f7b2684bcc5c50bef08d8255d4d766baf016a89d9e432e5095acc923856749c340c6b82b0d323bc09384e8d00e2502e3df85287a9781cf206396e090f381f6cc0b62d2b86c7d1f88bfca4fc867005e72e81f7c5cd578120c69a0d1dd802b729ead4a6fc975752280fe8b2a5a5e2266408c5fce475c7d24ebd6a34eddfad4ad6b9d7aeaaebf7eaf578355a9c84f91a022ab13a86e75ea5baf6e9dead6b14e1d75661dc775f2c975910c88eb85581d9cdd5a80d1a4730efd36f8ba47c48ac41d8d32cae408e060f253b933934d3eacfb1274718ae64b42242ee084cec455794c04064ef81075116f8da9e316dac05e093bac34e857460bdf1c6585ea7eae23dacdab1b14e7580308ee78249e66ee6e6a3a29b308c7539c855558a03ead3f2275535e55fe6d15db3ca28a5cacf275027c98e6940fdae4fb04b42dd2e427df4c28eaddf95ec541a2a7074e0bcc4fb673b270ffbc2d078b978e6d3a59b07cdeaeb3a5cd536d1d2e5b396fdfe1a263b43d870ba74eda3bbf38e9d4aeb32d39a760219b1c6def64c1bdaa4eb3ac9c224f91072f0d587a85a2a7e3cb570c50684ef12c5e316a508c408a1460690636a691cc290b282011887955667289e4f37f9f5175ae810135cb6c26c1c4a2b7446100099a71a9a03884728669ec012dacf9c2c1b82a511c4061661aef452ac06a9264a24242f3eb19d3b4075948f389c3c05a463984c2cc34da8b2ca0f1c4cdb854501c4239c334f68016708095185464e3521a4beee4544991f5d93fd94ae17dc43de8d8eaf5bc17d2142e475f403b4c40a4d84fca7048cc604c3575f639f075d4ced6554c9dafe650464b0066f7e207773ec7fa4952e2a64b609697aa54d8f3e1a6487b7fbc3e9307f6890f973bb56efbe5a6188d45071dff9402161049a5a315f523b132461bb15f10432c13381aefa50bc7e683bec4bd1c82cab1fecc6816537df05c412e1ec0d6f06905a4e1f20746fff05c4d42156273e054ab0dbf852f4bfc6cd3c7edc481d1030bfedf3b0ac10f39ff34722ec638bc8925fa870436659bcdeec8c1ad25b73ecdd1baf7683ebedf86d3afea1e48bfa4ab2f04f60747f83168a4903f8038cbc858620151433338ae1ad39063d8e79f5433eaea0cb440e3993ad97779ee8334a514eb9b50433668201ba756255012bb169a9e160a791e0f6f096831f037889897a0f4f0dac9bb26b4cfa4169be8a4505386d290913e80bb1e328746e4e630ea7ec8e6aeb952236171d863403fc54637e425bb44ad6821d102a9b398b846470c7a441eed80f5f6c1c83f30cf4d788dab1aec4bc0aae6d83cbca8c720f0d9c4873e73da4d7027c57af03986931631c9105035b224433fa367d2aa341ebaa2db95490d0d40df14af0c10a6979f9872f01b06dcd635588f58deda59e007e597c71d9cf975c41dfbec38d52257f6b6cd04fe9fd74c32baed3ba589abcc4a5289f054c7c19d797133e84d4e09c651d92b949c493a004d806137ffc098cfaad6ffde4b73dc1faf3a89e4cee378172f56e2295736d8172e666d31b0e4b129cef1ad67e18b1ae120a89f1b52467095c37b43f2e40ce42895ac86b8889a00f130d7a416f4881b04b4829aeb5e44eebab7255eadb765b9db092eede317e523231de141b110495d6d4d76186effbfbac8954f12a19be698a49fc292edcaacc1a80324afd2f9767e9eab8e16b5832c7b096c3747974574ac9cd4d103f10de7b558a9998ae460c4e5532ac7893fa9aead723cae75b5de4d1677bde8342a2afe7eea210225bef6dd3ecce4907f9c446c68de277e117fd91756a56a71385dbd9fe0c1891ce2c1293eb21d5730031ec1b7c30c70f2e7cc5b7fe2b661bacf2e90ca23b3dce40705121b23d7760367866af2ec50dd1f6c762645af1561059f1a819fc916fd017cc496405143a5482b71a3293ba853eb001a641c52978bb6ac749b25eceebc1c3d9db9c7c5d95ca272c0446ce772a1dcc0c2ab4d226e4d88790d2d0bd660d2a3117043e5b10de12612a46db119f7c3050fe18dabe826f717899be62eb81a7c4073aced8224c4708a87b569e3840c556ba90a5f570231da08cd178123bcf1d074b70d9ba73921e4fc58653b783f12257d60d9bcd420a9be660ee8756ff64c453e87f38be166d597ebc231456935765ba43c2016e2ae850e66a14708adc492920fd41af44e6d2f34123dc21e9ef54339c3fe06335e99e77568cbf307a3b34e3f2faf624a36dbdbbcbe28b49414530b75439d78a04da50c69fa0ad4e7fbd4c75a5ab2561d2ecdd2304cfb88c24231f23618d52ce28e41722eda54a60cb24f28dcb17137a88c6ef741a40ef24edd25cb6f920f021b2f5c18277ad3742da0209f12d6a8d4d5679c0dc9231d128d975eeb12e48d06d1eb9c50362f771a5f56407c304fbdb708da29e3fa4057e59498a603d85397388170d5f2b53bea1c5adf6c7d92bff11c5c444305ad2b4e35e4c135ed22925d5c3016b33c184f709a96fab9bf01ec33ca3ca9b4b4058559fd10eb03214d5b1115fd2d0778156f426a3c1337140bc4eeb8396c8d5ef73f423126955ec78503bac107558f71005e09070cfe904ea051655657501c5ae8d6a92ccabf65f71816bfbb8e93419c8aaa5341fe22597048398697d37910c17526acd08d72373de8aa832cc4ee62463a5769f0d1035708576518cc470b7763cee9fc3c7b86f2ccd28169c69617452bc09543ae3d11e59005283e98b94a23ce7b0947808d0711de214f32ca84bb3822a08f231f458e5541de28faedf165602f637fad9a584680557a3d9d37b390f0307a7f4fa77bc898f0d6964443852e72403d5b212a7e344df875093ff7be90305f29b1d7f14809acfa7e634a360e864015c2bc1c96a5192410b004af5bb2f0731cb16463586ede8698bad8501f67cd595c05d55602e0d2a379a0919b8a3558d920500da7198d2caf58842c7cc5407bb9af76c964400ddf7c55a05588006c276973fc5574dfa0a5b79c51dab173b785196179347755c93fc45a4fea40c904d9e776e5f1e45bce5f504de802df34247c17c3bad901868eb0ee5ae4f658a6dbc8718234e70e6b91110307ac27478767678039b6a9ac31bfe28cf3765b3796ad6a12860b995508225c0c1519135e4d8cc87370aede508e01497a55eb78085dde6f11da0f10a3260d3ba354a8c198bb8f0851365a66dec6d7c4105d8779d3bebc18119881567631068f0fe3ef633aabdd4b6caecd7f8cf9165b7bae1718b0a6719eec83ecabb32a8051ef907ec87fc06e58c5498acdb295d1953c90ad5c1cd58ab066a6b823a4856433037cd6efe441102c4be90e58f60bf021fdf47821f086e4e6b06595ba196063ee01617a0c8cc9dea3a6210ba16c5f1850a138a2aa4717e5922591b2156a8de338870688f74e3542d6aed6996388dc0620d8961e83ec154628a4cb1bb5dc56f177131d724149722a23968f0e4a3ef6f8910db4d752dee38bc8494044c98517b15fba47cd0b23a9ca30ca01edcb3d5dc11953d286e499271ff36d8068d050dc79eb159eb50112e45ca6531b56dcdb023d8022baa212c294145c96675e3ae26b0fc78b9acc08db710db29dc2e712e32d8092886bc3361acc46772b89d71031385f4f8364ae0a181ed5e9c427c5ce35598382cb0f8ebadd41b4a91c1648571bbff47e035fd61e806c6ae7c39f834214c531e60d84564df355efdf5f950d1e870d7ac7e609b1375c60de8ddd84deacdef79c934d4c0ce7e91846c0f17a79a9ac29256b1f0c939155c0b25964099d5a5c36c7e409fa6f1a45b581ad391a5b50dddc5cac83718fda9346d9e87987c16b3143ecd4e7bbd95be493bef02b4a01ae897676d16e9fb798ee73bd9ec3d197f8462a9a914812468c36926426dd54d379c2ac6295e294c0190b0daed055ab43975df742e169b9294df280002ba05d665da8d170bbc1d0c35dd1b1c300155aee1ef252b6c16ed465cf46f8c7ec8442609a9fe5330f2bd34ced93c0f2982b4f86027aa1432f7d40e85612e9f335237df95a9a84467f729e3a4dfff39fea66b2dae3365d39830aeefee8acce7b6d4832145199cfd3cb93b21c14edf6841097fad29f941346db0cd1b69aa0556455e9fa694683ca7d1c58a54751b1c08eeebe2372898e151039be35794b0654ff722f44dac98d10dac2e89ac68d2fed728694b7caf9760142cb9c600d7a1a1fffcd00ae7dd1f61f926feda7a87720062aa148153ecc261b784bd9c7a2994006d24f6b95ce71bea8547a3f54ab8a4b6704157c5113c7aa6ff19a376625572b76c655add11e44c94a71868d091fe8749455a2315a6fcb6ba67beec6f8df0b3f28c6f2f9c60d9fd03c9cc6f053f01a4a7d3bbf2613a23622c16ac8600a36feaa55919154b321f1a5ef96e942339311104eb2da18edb3744c5fa30bd85cc9121effc305937a04ce4404f8de98fd24b27a5c65397340feec1f0882841366a47a33f69b026f0a8f9e703765a328499715c56ad4fdf83de3002b889abb9e27292794eef2d1d23fbfdb49759c1048b85f04af393090bd603695ec6fea9a694de97d0ec9ec32dc5f6b5f28176e58fc7fcedd676b2026b04087aded92b54dd91b4cf37868b57dabf93a38f3f524a34ebee8d782a47bf436fe1b4bb8806719544ed06fddf008523f3c7d930127ccaa8e9c95f860cbba197478529463fbcd9085c8319d5ef31fa0e4c5c2a0039d4590eff3bfa36a417a77bf5c4f2303832ef07c58243b9fb01389829220d23db39e7631e6a028d87c9afdf8282661820e6de040e7f3eb9c9f60b4706d12e044e20954b981c9a1a85ae771dc21f07be83345d2f2a9324998279d30e9354361d4bc077bbe95edbd63b008d8fe46911e85179c268fdb9d161e343b2103f1e5d141207ab0f27b1fb1f47f813a845c70682f6d25288bd6b1b70ecaccd06b33d4cd53bc4318847d7c0532a51810015c3acefc207f77e1b31619209328ac66bf1f1b42d0df90d96d52a79a7969292d92de2e8450b0b431436a576223d442f54be59309c84eb53c6c9423ccc32a7cfb68fe31a4d4089f00d3a844286d550aeb848cf9305f1d0528d0f208190bfbf34ee1fd5be51079f2e65dfdb7755e3a42f18f6c9b45589290d96ae9c55e5990c554ecc9ef4489f11a89c1c653ad84b8c0cbc9195dcf0d6b4ce30ddb50a371dff6e6873fd8c10e3edcc14cfa36ae310d377883618c2945748335cc25da50431a80256f70235d464a51a10f4ae57ef0627a00438baa1f975443a5e20ba69a1c8fd2ea120173fc4b49a9fac5a4a0fe65aa50bf324997fa2aee3c247b3d4746d8af083e94276ec193c7ecbc6a33efff9f39844cdbaabdba9ca98acf51c13c0abd7b912f86d8ae2a7753042595c7a65a9503ee1f1d52d9318717e958fbbb621d2760bc721c489bf4f624e8490a3de0303ee92af0a4a62e839035c140ad5ca24d434b1b4d2d16cbe4cc63c8bcb1d56d09c2a5770ad46fea50514e88d6d22805f09197daf5c350a6f902e71ca3ff5960f5a00fe53d22f6858e3d2af7fd311635acbf0e57dec58405f4e24dcd8574e67f10fa31990fafdecc37dac2c21c2fc7cb95265e824dd1601f7dcd8006eb71742e1957657fc09d9ff894c04dc318b4ac0b3b5330115e0c7324ffea20797c4fe324ab10d915ff8a974f5ea9286b8674ac08a8c7db0b812700317f6560cbfbf4566c6fa2110affa3411a62e9519f340e183a8e2d2dc83880ad0b12713889f83990cd64568a7832f2f2706517d7bb1c0923b863e558d8e785823014b2cd790a04a7db3713ebf96050705eab7c69d2593816736100326495dde6932d07712b87141a46bce5c00384380e479a6ac21702f181ffc876b7027933b7937d08f26e353c4a5a3ffb2983b477bc5be30f1dbd793554a1b87b6c9ed27b0dea6b6e3f0eb041c19143099b0c3600530105def17235267238bf682dcb2311ebad15fb333c0e48a79eae751ff186e5e50caa13fceb0c107258fd568ac4c6fd669efcee5e78d8000ee2896db8f667c3816d829b02e07c3c49d133b233a14c01cda87a05413492aeeebb3efbe935ebb890d9810b289a32db95f219067882501b9ac2f0289e2b155bb0834c0cc5e40d924e453a413ee3612f46d46180ba1f747e9d95e85de2ee2bdb1867478e573a3f47d7fb499968dd489cb4b8886cedc0893f9da1b4f7c01d6dfe6cfa147911cd395ed7adaf83b5228267a908d07fe48956d14b6b3fc3db9bb341a7aa8b06bd5f5ae0031e8a28cd0c80721a32ab10cdb5252606bb9747bc7af61b734112341b003453022c4b189ea6a8840cdc7394e90257b6c54635b5f57b5819d65413ce345ff51867368355b9f9245575495fd5021384403b02f8c302ef312f450fe481dc6b4ffc5b0df1984483481457111e65d85cefb36ce8b658ed2521c61124e09f17be4101f1f91c1419f21987e744cd9a2a0a0a3183b4af62a7536afd6ea9c4b309d84047fc3272f2f28edbf0f0e63839f4574d3a52721180368dfdfae695c12f237b5a24b1a06b850844b61945ae96b6458164f94d3ad2a35ee08246f8c58d2532b41a84a1a364915c59db8661663cfbf3cd13f7cde64db0f5d80256341b5a548bee6a5af244168be61178789cbca8c5436bcb1021dbbc3177d2b6427ca74bd156532733b33957a9fdbf209800e830325cb2400719b42752cb53b35da1086867ab5549d9d2db050d6af242a0c9eb267f2b8f3c5b2c23218023fdcdfc745ee2e7be88bf392baa54d6fe5327c7324897b81e8a97a6815c01c2dd994545ef3c8a4d22f90e4806d9f0c96c02de419a715fd11d10f0ae4bd8d7a1e462da907bb675c7f6f4e95fadf60d9544768a05d4a822e862ba86b5da2a64a653e7f1ab5ee46d56c3bb50e8871b2ff5198619f2a08a3d846b71d3fdcab49f6dbbb48022b04022609a90637d2ac25d2459385989fce1e28055d352b1bd1912056b4c0725d6d803de9b74d2cd257b0eefa34bae3654a0e329169c193b4c5b445f659d563bfaed57eb4c6f040ef7823deb1101605eb0653d24600c17ec6c8f0420832e992ecbef2fac71503a2acb1094765b873c1bf2fa861a77f20c41d2572408c050b9a812dbfb6890b0a2614e4b47ab2f1d646768669c1c15e5a5d1fe0e1dc107df3a096e3593d9d9a6217c9c570a59063cfb981bb20e166c768cc8887b8445d4caa9d7fec65c54a303349a9d143f3ee61eab9a4cca95880088b142df5b16713baa9699e4e385751f14dd85e0eaefe357beefb53bd23e070b0629aa6ea13ae41350956ec9dbfd17a47c25354fe0381a5735e8a3fcbada31af7ead171a578674795f5c946266b7943003f414f53802dd0385cb17507c2f9546311970cf7e24c509cba390531224bad40910de5239601bccbe72753469ef0878240cbc789121b0bd1bcbf974f171ecddb88640081ebb45e7e0b014068ee334cea88a8d4ac46f5d18f651a3766f9a95203dae42aaca6ec30cdf95764044224d1bd2d4712aaf10dfcbea472846ecc84be582226e1a7228ce234be75e02de3eeb56d5c1d8d4bf6186c6a9b485ec78845c5c0ad0131464f4b45a80f18bb762da57513923dced30f1824680a9df4c4a1401981039dd4ce426740882dcbd6c03b25d9424f7c70637d4db7ba312a27350c3b65328ff97daead1d1e069c08fa67459ad9e45ac3239c353aeb5782bf46da20342edc29bc2da0a25e1651e08e6ea88ac7d466713dcfe1c3c148319bc4b1c0ab1e4644661e0e5feb7784a8921448fb7f0532ee7d37d10c9230843e35de74e16feba8324e762d79762423851c3dd11609e67655e072ec535a685c9a90c8356f2c754229907e3600be052fd163c8b1a1c39b612bf8b0a0c6ea36928927dcacef57abf262598ed597a02e1cadae315df583616acb99274120f9236e2a4864048bd90e58133643d64713416ea0a83bd22da33b80faf6ac5963d637309ce7bddd71cba125667dbecc383ee1091d6c3f98cf76862e1b7e1adc931a5dfaf19b14151e9af53dcc2f516ecfb97d80d7561c7e355f7e3138af13ee08a1708b580e0794812807e7c3ad9e8ab73720c37438e649b2dc601e88fa2c999da6d5cace7dbfa28ae6bbdb55b940cd5ce4b84d619a396a20cbec8a45a5f7626ff6f683e72813ecf8796c347e2c484699f42e1804290727314e3596a64a66194b21bc62e18f020e4faab15b796968a532a21b770ef772a06a599d6d1023671ab284e7ca9f46e9ec1770f74061ccdd213232195524ace753e97bc88838d6fcc353b2c0a6b403243c3f8db18fce48362d6ba02dd127cfa9c4454a537e079a49ec122c0d8386ac11336dcb1b4eab24a4898acab2827f087c89684a2ec2bdaa4121cced3355451f0bb79d8a334b6983e7e69420d252ac0bd3572a6df6850ba73b8bfc06f5b9a66950da33e73213a89dbef350113a0da1230d4fd88c1117bee89e4c574d9eae846a39434f1c4c9d9480f3cddc26bec4a30aefd10b4c3f92890172a62781466f82cce4755798793b34cf8b44f49dea565cdea0add4d9e177b87d73156a71855d2f4600154fd8ab8020a760d32c54fa1c897fc0960fa138a370c3166aeec7cb7bc22d82f53e5024526f683cd00496becc91fbcc9382f9cf1cc4d9e00c13a508ad6b0c39875289dfda35f724ed28654337ce89ab8337b17fdf6aec48653ebb5f53dfbd17cb9c46e4260537976ed4c49612a327cfaa44c89e7104b68415b8cd54be4481f9bbc1195286ec37f810c1021d618ab7974616ab41050b6f303ff777d0cbc884b042fcbb3061a564a7dc46ea7cc5b32ad610dfcd594ad18899587ea98183466572f4408046f520054ab640adbb90243024e5185272e3fd70f66806dc1dda4d512d100540262b05cf042a39b09d5cf9e1550fa84a2e26f08e60522743ada2438600112d35068456168ec6c4ad8c9365f15c4d6a344c307cb54208eb66b6a577c450601431a37a7e5371d7858b2c94cd6fbe29088510e81d683e0524172e578b0ed5c10e361605b82a07257956e5609b741baf9f4409b1132f4d4749144d3bcb8bf17cbbbd5f3ea50652486f40e11f236c9bf414b0eb1f3f3155115da71fcd30506ae3993e39fc6fdc60d965622b7201bdd6425a1bf965bfb7ab6985a9e2e1d00b7e6fe6f82520cb62303a7c1d5f08356398e7fe3215303597d35509bbd143a6abc36dd3e95c14db420852a058ad9cf7b5c1672ba8767e45c68417d36001bf5b98dabbf01b7815d6d6610224cd32625cdeac0693141c9e8869e3256fa57717d7b191d8bf5688ebfc6ddba6fc9b9432096b387452628f0f851933175221a8c504f79131929f90b92be619740476aa1883c7dcbee3f4367868c84cd834f897a05d917f6d6a04109c3bcd90e6deee407e07128e4f9a52436597cce27dca055a1e322e15f12c8b33bbdd6871244bf83d62a6d1fe25630be8c8b6c8dde38d277ad42faadd4521dbf9e8888b1107c1bfe2cf1fd36890ad267a86705efafeb7048837bde1c3c60967f901a98124e9a95c7c7fb4f80c5090cdcbf757af485d737e9a4cc8bbdc4af38e056bbae863c9201bfa8f0ce0172a0428dbf9c873278d4175c34bdcb8361088b631d12c5540a05f3e169607ba6031f2de5a47755482518b44954db53d7d97be5749d9a92d51c9b8046cac80a10d66cb9cda2e051682a722e824ceceb3fca993a4a921d0410c100d5e0381e21f7bef1b211c9498fe18d559e66b435d84f5d17e746a6b82db787c96f3a14815c7c4f6157dc11d6383600eba27fc9d1add8bd352ddaa2746025c5128412318809d528da1ea790a89bcaa20bdd82e3e27720bdb8436b69ccb44921f30ea73a8b373820160b929b2678ce3a3e2b46eb3bde9e5c6410bb03a680a0cd844d7e375d9234169a26bdddbfe8ffe6073136c208a236f8233c0a2ff3e6d8cf51129cf94b66dc0a0cc70a3a13716c1f907527ce625c1d5c0fd791c949311d33e28744efb374a988e4f86db311f0416231d8d465f9fcb9c5b88e9210c904112d125c618cd8c9b34e211a9f0f30e52c65bbce0a83389e287222dedccebe24a3617af40f0f750eda98165da229511c149b683ac5a4e90aa666536040bddb1443b6a66e585449cdb843c9dfcfa79ed7a466ffe26f69ff8560b8e9a980217979c79f28215314418192448e091872c844fa67687c9e7653b257fa340780ebfa398abf8055bc3bd42cfc47142ade79b800c0cca22cbf044a4fbedd121cd89a7ade5dcf0a7b30d83a9fa2f651cc1bfdad47bc6d73b48fc2e12c201f97e3275914933368eadfad24f237a5364c72c153a35ffe6f661bca6c9575b0b60663077dcdecbed641c681570c768ed1ec46140420c49649e56c2575f806e042e6735e8667033efe6cfb6b25e1bd81df27459cc214b965e9bd08eb914160067ea9fde74ae2a1137fa105d252a466d9374415fb660c207915b78df83bfe30e4dcda4f2e94d9183af13ff24a22fd28aa857ff60f81f67b8be4621eb684142d65ca9dff4e792395510524502e68254b394b34d424dc7b5cab16f01f31fe2911a8c3005956fac52951c40490ebaa22b0a155ad8a1e9bea53ec9e0b0b84c872f7b66d3c0be474f497ab0cd980c42775f6ee2b1b739c0fdde5983c766cb3d682f17814565ed3a671b4dd2c96a3ad26a6411a442eef66cc1296b8d427bc2d32027d8a7e9c9e25aa4a5569d0635cec2083cf2e778c6f6dc161d6c5bca05d5073d0cabd5efcd1711802973575de24888b642c6b035668b6c4e4f1fe8858b23fd021f8bbcc8b857aa0c91ccff44efac9410e57c016262313d897161b5239244c4b7fcbd974512b35134e6e899ebb98e3e15eb6603f58d2175a29d37cfd167016d694dd98aa56132dbff1e5790045bc739e082711561ac4a0d652a3ad528708812a70127f7c68faeda22b7a511aa5dfcdb6dbb4bd754c0d9e5f27d375d04f7ca2e42118dff607888c6e41c63b848f0641220a6f79bcfc8cca1b2b8f0195555ffe198e22988b12434e826b516df291d3374b1701360ac89b31e0d7d58014b389a046bff7be104c788f35de366bca90007b444cc0691fa9861971549be995f2697ec85a25034cdfdd9d16e6658bed87b5cf66477553f11034342516fd06290a816a4b4bca3336608a525acb51dbaa98816a5b7d8b3df6042ff424d1a22b3542863e4fae0197c61065a9a4863b312dedb464d3c5cf3ae728d0a97dc2c9f07ecda9b1a5b0b85f095830e9c46142009517cb24f6842812e90a6b9d0499f1fb847ef08f9bf1ecf4bcf390acbc1baf6aed00448d1b55e17207eaad21cb4465c3b28abc3d8d9e557df452941c291468042413886cbc3fca25ece6f3794e6624335f30288190d46b02b8817bc2e59f83c9ba3d6fd76f34300e0b221785201436a4a4e33b757a4b9a9b46fc6811035f21d7cd642956cbee48378021ce69e92810e60f285155547247da8d1187d8cd9da57ca0d72c06d2a1c60b54fb8472b8c417cc43a1945c27134a3aac4884ff1eda4f767dcc3ecf7e02553d754833c6f0725419a9b8313fe93e080b1a17087fdf6f4134a131e0bf3f00a5e485952177b9545e72855bcaea89a6ac292399f549afafac4e4def05fa12d2a4bd49bbf3bcf38541fae98aff118bfc784cf45f1fb0381f401949c8c050b8723e3082b8e137d3b8a79b5420cf44fffa07221f6cb752484237e4774e21586e4215d741f25c33d0534109d956f67b4236100aebbd30580c6705fa460cb7696f05b8fd58c76d5072d4544d6fbc26ca3db92579a86ffcbcde1a035b0f6edcbb7227868ea16d7418fd00edbc86328d5cdefa9f47475a55659ca11d06e77f5b0290a4e6fd517d96e5b74a2a6fc18729e3cc7ea9727d8f321e99fb559de8cde630330d57759ea2569d720fd395ed9ab431f051fae5d7906f47402d9069c129d095bae824d2dd85aa13d895ae80463d86f23cd8a98e5a6f94320ad8fd50713f016e65daae8b09a3af4f34ecaae602fad34451bf0b4d19c19bdadd7bc981f43207c130ebbe573d050f49f46ec53220014bebb3118ac03c231bf258f99dea3a9a2a0671532858a93079acd2cec35346c8e0ab9cac24d145c64aba11255271bdd0b01d01fabf05a0727da30b48755203a4e8c796652a56050999b202b198254f5875738627b90e3b7c3a99dfb12e2b0e88fb86004dce838dc300ccf002ff7b13acae430e15d264785c1c5479e0179d8942ec1629b7bc943e8b4e6f67d7f47ef1eb890399e33e8c70ebd0805f61906c14f48d0219a7eb69574dc2ca49b46a82d6187095719e3b0612944ffdad3cbf8112c8ab289807edaac83da521d5f142bd32ea5fb2ab46821c6786e55b747e10d54e5347ff32c9c5788324cb44f6db2583ac02a081048a9865c2546be43cae4358b2e30c6179a674005b61e4d49450324d07d7418911e8da08de094af29b1c04d861c13be65e1c23760aae32c4933e4a70393427392393e500fecfef3e8db372be8fcefcf62aa916cf26fe01ec44d29e25cf9edb5304316509b611f98463943d996613095996aecc9e92a27a0ec232fd853da53b41678cd298f1d061e950034a50970b512920ee0cca69eae5d606e438bbcb9c11cb689f7c4c3415258633592715939c8ceb6e767a09d3c0eeaa8cda002e48850204a11066fc82bbc59de98e2af0f8db9a8e7bc4308f9aef811c7ab25a5bcd06c4cf5726a640d91afa9f1617b0a29ef70e5c0992c5131f2b681091faba2db2e1f01ca9679eaacfe4835225b3917bc1a888cf963352beba9b858f17d89347190c91c89966751ad0a87b5496c6fad30e809e4f93da79165fe02c3acebb23f2b539558898b082b99654d283fdd15e5588a1856a0b19db38e819b550652273ec208dc3bf02e3ce846367d0b0c104ea0da50bbaf208e3c19b148c51033ccabb9c1c1dea84b3a1d3ec17a4004a416d387080e1bc49a8961358810f0e7a68be80794e5f6e3b0be99d7b66c077abcb2a73993209b8f89442b7b175f025996465992572a538fd68e46c16be638f449c41ec6416606a29b1b60fe181413dcfc00468e5a9cc46842faf69c1023b0512f922dc185b41d4f39ac262e14eecc93336148af78a6dae2ce0a3dc24a13dcb70e6e806dffe732ec4645e294b5b592305b1afb64f4b1a28fae62d1234e7d35793b2d24c15e5dad7160c7fcb6ecb93d69bfe395d32e7d3b789bab67d7a9634841e23c9123a352206f60357183807389d7b6dec4c981cda1ea040847adba11c8387fe8ecd01cdf95e123b195bede1d12cc49a383b28b6c53b4c19f02efe58c09cfbd50366de8031a1b6439ec38277c5dca21c89d48c8ad0b93cdc8d83cf6e4a87046fb2d5fee4ec77d68f271ce523045258228ff5d8b9b608bbce2ea57abc3c54c0fce420e965365d010688a3c19915146ef7608ac0ee32bad1e7c206cf698d34d22c8d5438b014e1e5ce53177063dfc9a8d615b28db0d09e76269cde9383dc6e5f3662b1cc34639496464bd5f739850a12c6383436c9834298f17f9f0b31a9ab245ba00d3d458f794890927988e3af1a1a58cf6cac9e9c6d02665b75f825a8f7a5420746ea0fcb6a3194d28b56007c58e71dec8baa3b13011b2cd523af13eccace509d2cd9c1df2a8e8bc3109d5eb5433f7d8e95a321255d9d36a1e9088e28e3c51b9b57cc72ef657d096111723f5a87cfa1bcc3df1971e6dd369d2f5fcc4eedc169620aad55d67b1c632d853d83d9ba3dcaf0c64531a001c3ce6b1254e2ac190928b5628966b1c8a5fd1747c59adea91450df748cdb30c22450116209c35a63c32e27654668a12aa56b5f7460701a5e9cb98ee2f2db9239e289a6c34520ee7b8d18a35fcfedcd4d364edbd2bad634cbb14c5504efe40ebba82e2d830042d840b1997047dc70ec759be7b2e527bbb2ca5ec95d2b8e317c35a601d38b7b84c44ca9957cd0a965984a3e74e52fc0739df83fe2f7ce684cfc3a159c3705f9fe3df42874b67b46f19d09a2fa38fb837fe9e009af16cd70da25786bb3b1c6ed5d83889b844047b9bc38280cb4edf4910eb93ebafc240e36f84cc616b4477b16d07f6a7a320401d104e94aca1a2a29592018552869c5754e8486067c12b790451405dc768be23951a73811131acaff94dab0331f606bf535f3b9674324664567e5d0f17b6a8b9b3079269066696c9652356e43a951eaf6eee5d106611d86c1bd3bef8505961580d2cb1b7d9443021fddcac5404cc4490867ca4cc2c560bba08926365d559aa8af168220c879c10592d7777eca1f205f7a4dcf053cf617e27d02176e28500ed3ee282c88fc154a3cc09d37cb57f5b8d846149e854b9bebd18520b8110eeaff0a250cbc0fe9a60f9af451cb5c30aa52da976299cb1b54b3b34d7b4cac32f13c0673f0db59ee3f643a77fc626e8720e46b542e30ed80f1d77333019631a37ca09f74c3405aef9de9e9a8d604b59037f33a390df93a96e6d3e06357d264127c20eb0a0481334bde2e65bf7e730ba944e608fc30d31c4186eb23d228a7e88f626efc0af871e442fa0a8325020550c96a3c63d905f3e9efe6051706429e426a0615410cb3f0f705e3c4b1d511b6cb481f72cecb94822b05f3883e3a4cd25bad12e5321de6dda4be6ff30e09a251840421fea642f3438a36b2175a6844501853fb16238b22587afa34716aa7894d43591e5da27c3f7d52a8159925a35f410c20dc5489281f8e3ddd2b281f5e45a6cfb5813d6cc6a2a73284bbf932289de2d3345e6aa09ce002ca24a6695b509a15a503f90b969bc4ced4bd12d2626a2230266ff3bd13191fab13622a266ac47f0274e44d9c24bf2ba81bad2ba8771a430c501c400017a0f3ec7215d4befca987a82e8f9e69cf5e708e8ac8cd5ab2e5267bcb2de59652a624654206a1067906347a078852f1d619770251eacb98b3dc76c500b95f7e3cb1058333faa7a8c35f9e99f348cf36a525ec303b3cf8c1edeeee08379e172819737c24635bd15d83c44c86438e3082331c724410b210dd79a5430a11849792234f77fd0f02b2c4abbbfaf22bed68a44713074f1f3e2ec8338120a42c22cbe8a35578f8c8a69823b520a47c3ab1c941ab89ba931f67fcd86361017378d8e4f830d7ea61ec7b17c81c0cf4e2e3a8df75074120d0e6c508e260ac0a26816386b042981aa10d5ee46ec5c1d8af5fc5e8f7aba7d11e8a72a08f980fd0856e304f93c7ff31f98bb1c905da60ec7bda83beb117f9f2a507e78cf0bf14ab0113b3c84fe4f810257b932ff9ecafc95662b27935e458c23d8c7d0f9401efc13977ae07f612f40edaee7b35640f67d8c5995eb8f8df0dc6e29670ee087f7fcbbdc7c326579c6ff33d081ff5534e0702d84eca4bf797efa3fe1ede037b0f0e571c681e717e8844118514d4b23e63bd31c6ebbad77561d867b5d6eb5db8f0cbda5aed7facfdacbe63b5d6ead4a274ba8d350bb988c25a6badfd60570d7d5c605756af5ab18b5a944eb7b15e51d86a69a012936a624b2e9066d5fbb92ecc5a6badcdacb5d65a2cc3322cbbae0cbbea5fd56fdcddddaddf68a3750c756ffdfe18a845e9745bab8dc2da6aaf7597d6d62cc33ed7753d76718239f0bbaeebc2febaaeebbaaeebba2e17978bebba42d77b2efeba5c84acb5b6fe95c5adb3bb8cd1a54bdf5e60621679baac5ca499517a123e98b5d64661adb51fcc85777d7551affa59fdabc6cdf3b5d5da18ac85c1dabff1fa8d0ba949b0d6de6aefb5d53ab566bdb5c616f8805aaf7a71f586ebd2b736e8d29e6bc530bb0259607677771c5f71cb350779899b788b77f76f615202d22c77777bb975776cdfdfcbdeb1cf7b963df6d6fea762977feefbbd975f777777fbeeeeeeeeee8e5d7e3177bf28a594524a29a594524a69ad9f5b29cd1e074c532f0cab15abd927c36eaa0e46f21d867daeaf7faf7a23cada24d85aaf7a2f87e3aab5d66a6b96d5fa57bdb2ac7e3678d9acdaac7e2e7b69c06c5cd65a6bed752b36eb556badd5d6bbc12ce55b9b0d64adf43d7fea9307ddf1c495f063fc8ef54f72ec2845594e23587e0f3e8a2d35ce7017ad64297bf8614e2c8228fd5efc783cf28ca5196511aa9657edeeb65662726cb11f7fde007b31c7a71d73e82e461f79964aee18b76891bd599240a6113c3b005986c0b3145bec57b348aee613b2fa49a344a6b5c26cd49f46545f7d08a609600cb9e24a12486c1b595f7a9a66660c364bdd55df3fa969962a6b7fd6985c394f7a8433fa53727c29e77bf1ca6d56cbe2a457e7ac5ecaf48f29aa4a86c65f90ab77203ed32ab37dc681380d64813666c9ad69ca38edb9a440535578e553441591ec3df8bd10326c81d577efb03b444a294fbe6fef67d97f68e08309fb25f84bfaadcf5bf92dbfa5bbf514499e9fcdc774b2f5fed6676f9980f8b849e8d050c36537983fafbffe3e1825653ecf6f567c278b28d7388ccb388e7b8b835c7fd6e2224db69d00604bf1faebcf6fe40acea1b7429c8b36525be860138eb3deabd87776effdcf45fd4994737fa5efbdf73ebc7e73efbd97c36edcecdefbc1d9e77233603c7c62f6624dc66dd88dfb560688dde8afe1da2de6ea42204af5d77643c4810051aa771fc019d5a738095629295812d8bf71bcd844fc503c530654abcc1d9e18301bb2562caaaf44ac9957c61d5d73abfeaa11fdc8d50351c28f8033aaef106497c9d54b1f268fc9d57b8a9752fff5d79f67af8abe7a0f72fddaa4feea47d8e237714665fd10327f1e26cb9f0d66cc5afbb8e4641f36fcece7d02ad8db966cdf00d07629df89de5adbd95a5bf23360374aacb5b83fedf1dbf74adec6c07828e1868859e3bc98f157bddf44dcd97a7bd2dddddddddddd2ffdd946f06519190e1901907c65d41427b9901e55ef3b9ee346bc8813b1bafbdd63c06e34d7eda5ecb41122cd45b964ab58287693d637b9481323d6442530bfb18ce004967df3b8d7015c5b17e9ae3f24f84dad6af5b5cae13defc9d53b15ee93a24d7b6e8556e95cbde7484e3bb98a393a222355f5f5f3d67b3648ae60090703e3c1729f87580d1c29d7853b1d135129976f9e6263134385a1daf8c805b87cf36c73e34d13d9a6559c8b81dd90df361ee40606664372b2eb77fd26a222ca6f208b6cc27570b82ac450946f44b9f557d6aed5e2faefd7fa5d7115e117051c7cbcc9ecd80d8d9319560347ed6e729e79e659e6f1739a5a6323baf9cca6c3ac6f71e1ad73b6b54d108812bf1d7cdfd73a523c211a820ff2c4d13c1977b39f6da35daf9bc8b383c42f7ef18b9f8d8d8d4de7f4e9ba6de3a3d94f80a3f98d6d30156a16707b21c57be4f93ea8c88dea1dacc6fdd00e9e97c391fd8e98a455ee835a25e31ad59d6f3ab2f76e29ec7437e3cd0e9cb36f1b2041444ae499b100cef0a20a200a16812b20aae4e3457b842fe627e6bb758e44208bc5d5b0dc8e38a794380c68e2c213e94e699b8054dfd5b34859c5e4b8a37eb01b514a29ab2c73abacbc727797ee3cd4e43e797878f23482e71478624bffa441e80e24354dea23cf0f7683be7c0cb341e94b6a7f5a0149704e2ccc2ee439dd6bec563db67fbc940493a92560ffea250fadb62ce6a4146b53fb7099c9639a99d57bae825c7150a6551ab2401b55c533e7a411ce58407fd5557dfbb7da20b5accad68af660201c91e28814593449b97925b127ca33461ddc1b90769e092229f01319fa10723800246188f1e0f93611680508610c1583a4f99576f28c0c87c8fcc8f55a976559280873f4803a26d545c424f3fbbe2fa64a4c44fd635281430313894234251ac7ccfc803d397678737ea92883695251488e3ec05fbfea20c2acc981440d9c9fbf80e1613cf4b99327e957debb14afb527e70b52caf718a852611c0444e1d30e9044034600aa682f5f07a882bdec20697e7034ff618511ca3f812a2015eba594f286ac52ca1c53e214adf5939ef2fa77c69493e6aca999187b6b03a570700d0cce1389309e3579fe24125b669238633e913c5f7e4e8a224e7e9247a69c44639a9c94830727c5c9d7b6eab14d933533496c9144e28cf933499e9fa79cc7f47d9fc96459343ee340a4f5d806721e1f4d3963aac1ce834d5e5383fd9552033dfdab6da00ac3340ad2aaf9d52e4196eca70eff61010ac01cb1c57fc419f3659030c50f78298b18033b1801d8c9d7361d6027838023e9a41de0487e2c8952185694c87972ac0db49dc04e3e7d6972520747f267852322382ba614286ba0ac8144f2fc29359056611848ab60685f417f351a25349f81597b1aed40208bf6f33d09e833511082f21d9cdc06d3be665fd31e34af6c4300ccd9df14c6311593442f34903e418ba07b80de8b29a83de827b600d0a663623c68afbd85492c06612e477799168174373f4916b7005970d8d7deeb12e8b3b7313bb600d06bda8360b63c80bebf36d250685c1719426698d893edd3e89015b01753494a8ddd88200e26669010822891b50c851044266b2f65aec830f3c83246f249864390b8c9a0a734f071ab2bd3b61d10a50270347f444104a81e797a2107c4a20326c7e3bf1007ac3dd43889b50067af618f3510f6208c9d642c09ac7dd5b86baf6c8b59db21b37d07a2bdf6f22966437ba92d407bf9a04d528047ee0ff43ba2f478646f7e9ff26c8ac9076db5a672f8314545709dfd9ea3604e09a18043ba94ef448f45b84773a5bffee677918e29e7409c17801af7ba26151866afbffea6c7009222dcd1395aa5f324f2c207da0ef2b401cdec69538c910f35214e622ac29e1c90c562d5cf6f7ef39b9ffdd80db3db65b76b37cb6ed66ed56e95dda8dda6b55643fd111114d80908bf32c04aac71411708048ad3489e3cd4e03333333e93031675a7a0f6a0970ff26cf6e627b959a4bbf9f30b69017bb24632d12a9e7f9005ff7c595393a712f8e4fb91e7d73e5ae5f3f363922918d1e23c71c64c5defa4fe1cc47958249289c95d864390d0c9173684517ea611ecc5549e71d2fc7c34bf763845b482d344720f412b0259608681a3f9700bb588543c22157f7c45ba2b90ece818c1e93e9c9594dba2450b8c871a32f672be0e1065821eb4ede86efa0c13f9abd14857e48988a2903c6782d074ddcf9f73f67c3920cafc3b4f18d4d3dfd645a6c0defcf29c1fe7cf6f16b13fe7472e640149d3e4a32ec0546c91269a14cc20f1011008443fa56701f441a0eab3bfa0ad2a31c9a10271b0bb122e8321bf7af9da0643be263f1d13bb5143063d8db608e817480aa980e4c7903d88d302925afc0e88321fb4e5483929c455008ee6bb6861c2b2074b3227d939af5390e797bc077afb17e3c1580b70cc1eec01e9c05ff2d73793949c263d265c8eee4e72c9069323831e4b22c70e9016b3dc21337e1db4d7be6236e46b115b807ced21261dc7c7c649c61e7beba7c9247f9a4c756629f1d737c110e0f971ad1043fa5e00f27db9690fda225643ee648df34e8438c938e32dcb51028128f5b38cb74fea93f18619c132de2e9b2be3edba8f9bf166f158196f9688cd78ab4d5333deaa2455c61b3dd18cb71904a254bcc92883371702a2d40f55f572cb40f5f9cc9f5555555c0ffad3c2aabf16b576ca1ef2edf7a0d5f7b0f46be5d4a7cb8f1c903ce2a4495c784205eee101ad12b3bc0f3f2f7f4a036449808cd500479cc459e69043d9af5ff8fdf1dfb9d17f06c43cb9eb09919231870366560f33ade67b1917dfeb57fbf55ac27acc7aba793de4cbdecb1d517d8f5aabea6b05eff7b0d7fee4be3b8bb3b5568ce5fc94eee0fb0a444d1eeed5ce85271e69f495dd8d2500e6abbb282801e792dec98a8fe4c38e2eb11ab357208aacf102dc0fdd71918b5c15178023f940e04021a70aa183bb4884064e30005b4e2e3083452b90c5b3fc6ed61309802c25dd4112a80e19afee68a415a2d0b74164423b92aa03014477f41f9083bd1a65e28f56f1a71f67704c21c40460cb0f7106ad4f7f85be8721a52ba81f62cb911c3f95d3f32d21c7f7e01499564084b880e7cb481363d9f956f53d6845e95bf57b54b5b2d6fd1ef55a5fad65ddfa1ab7ba55d49bdbbcbbbbbdddbbe2217b0a21420e6a865053e36ff95b7fbd356972fcab03b3655996f5d7ba2cdfb27f9763c4ee1676c32daf51801cdfeb25e4685931c624f4ba3f1090255afe9fed6e741371571170f6579ca9134311e633115adc0f22c8595ccf0fdd599f715d8aa9ee6ad0cc70be29a03b7fee82d039699236f8cba64c72aa3ee53819330259b40ff49e94a2e441a0c736af04c4c91b0dca1b38258e90cf531e0151a295599cc96bc5d845fedf65d2741202eef95a857251f6e074178574276f4c7102812cd58ba64c8df2e6460a9138d61254a74994fb13bfc291589ceb90d1a58fee2c8b83d20451228eebe3cbaf55b222f58a24d6fb4b2f64d06b1b4c96b5879b07e234ce3febb5cdb3de3f1eac21e6f6b9da74c46c563eba8bef395ee473a6e308c802821865aa04720530e8719274028ee2479fa51cab98a61cff62f28328f1a70cb0cf5cd12a4ce4386dacb73818cfd5d72ad6c7f722ad9273adcdb7e855e4e83ad247acde92264b9a2c69b2744e32e64f44314c3ae392c454c55e3cfba8d3a7f86df2cee66b443433a622574420d75b31640576ae8a973f0da7d1117bc1e15ceb703b62be3ece4420b979b646d25dacfa14fbd45dfc527757498eff816ca57467fde5d6e5d73521f4bf2e6bd30102a2f8b7f52736c42cb27c6bfe8db83bcd86ee34ece3178bc49cd892439c115fb4c5e82ed2e8ab874d01ddc10e40ae3e572e42a2938fe2578f5b25fbf8269005fbe8a72a451de23cec82f34417e73d8c2d2ede22215f5fdf057c225fdf62bbde64bbfe8392afbf35e4ebfde4a4fb1f8dbb4637db9e874dbee675dd8dfe6733823e113c5ce84becc5daaeff7010f3a17fc817771f66cb88fb44f070b93f378fc7f577a394be5b9ba781127d883500bad0a7d1f2ed76f20ee89899fa356df097db49feee260d07228f796db0a2ca9a15d02ad6ff000411ad023f9a5a853e8425df035402fa7c09fe1e19ce40d99768df036b20fc2526df4333d1405f02babee4413964f829de0dd0a590a15791213c924b36d0a6659ff817f6d7c70dbbae18638cd7f5f1af18af2bde2b7bb8fdeaea8a233a478ee81c39724487094c90623e923c7dc8d81fc893271009fbe372031902d827e83dc853f21e44e54008b1cf7b1027c3ae41528a8fe65f7f18ebf5abc27cb5ad6d36cf575e975e524a77df74ecc02e372ccbde4b90e4f9f74e28f029e64cedbd0845fe2e76237bf9b58a960359a08d0cc7fd1d31e397ef4d7026c016cc93549c8a49ba2b75375f82b036f16d889a3fef037943a110be1abb11446e9d21e6a7ee3e51802c43e07e026cf1c720cb277edad5ae0b897d7645999c6d034a7152e7008ee69fa88022c7896f084e1348982e0c51dbb48dcde79361373eef1fccc6e7f3b95bbcb688cab60878be2eae9946f2a351eeae92fd77a2f793e3b6911f0e4294d38741da8bfa39e88ba73c2fe745548c429edfee1807630899fd61b2ec1c746e081b5c45182572944c04ba13e1ad77bac3a98d6a947ffef99f4439f8f3d3dde2107c90cada67255bec89468028f3a31370469116ad648dea2e95632a85b54d280ad8f3ef142bc6a20ff5d509c3756d318384e4f92ebfc66df2bcf124e08cf978eb1d170041695480a3f93764af512e82f7026e148c9fbce174fa24a22bb023693fe588f22af2a95532ee83ddc8de4fad02df751c0559a08dccbda26afdd820a95760514c77b798af2de66c8b40cc1a3601389a1f65e015729cf96c9eef0c97903c7f86197ae2007fd88b9f5c4709387ebfe755400d05593e7fbd17b969aaaf34d9cfec29973d88cb1e735ecb647f39af53f0c9fe9369dc27d409a42b3cb694b21b5a4bc0b40ad8833c197b815633e94e723427067591b017c971c80c6874137aa23a898b0ae160d795c527bb6e0fc648806f0fe8003138250a903bf45071bda0cafc86247f906f5769bb2d442f2a095dbec1b0a5985a3275d75d4d2975a5983672a5e09281a38edf7f99009276f82e16e47e938bca57241289442f2edf60d0f7e7e172af1fddf17099f3a22e99ee66baeb95ed07c78060ef92b964ae1f48b828c8a2e5f877a72f9226e1b8031cf5c7b701495749250931849321d68092d294534e09c25ea6eceeee06693062c420c30c3f38116e599585bd4c4e012fd1c27cb02e40f8a877709273d71502f6684d0da593d6d81ec812678c716e5e7da151464e39e56c81bdc8b99960587c0c7b99ef1b0f978b613e58175ae3a3beae98ee5a08b426778c2ef292c94e222c60f9de25937bbac8cdab2f710601f7ef401f4e4e8405966f82bdcc195404a104adab8fe0241872c6860a372efd840904342e30c327a63082e3d24804e902ca85b40426705c88582132e4c68503d204342eb071f04c483f4822e57243415e18c1e6a6e4d2790813a50a5986f02e9023b7c3b38428f165073a003316b3cf946105f4698cc6943ac5f4a702f0dca274ce96aa4b36a9ceee56e07f218410c61863f4ee767739318a04094592512408983ab8730b38e212f66a4be04a1999176a05f664cf0bb5023b4ef3344fbc8937915a8161bb9cb4aa1f7a73e18927434f762f345355a1991090d01732125242c3090509d1846e4238a122559dd56f4f0ecdcc7faf1dbe8673796ab555165f28e5592511d97e68e64d96bb3bdd5df9a2c7a1995883660dc70697c749b562ace1d49059c37902f62eea562d4334d97ffa35b98bda6995bb1383070629c0c093dd3f3413a26971440b53f6a754fb2e4ff6af349caacea97d3e4017ed3a66c373e5ee4e773e39ab05d8bba8d0ccc41ae02e960b6eb27b591339bbc9fe97c749f4fd6f8f93e4bb0b0c5564ff09032afb5733898abd4ce812a2a9a1991560efa21aa2c4776f03d546d89fe640eef75b75843e3954ab446e899d5669ee44cad03371086e4b89ca5cb892294c0fd04755097b29b16716d8e3be80a70c191933b172f77a77e48e949727d39f17c9cbdde98e6e3096b8de7af997ca284246077c94ea6184e3233afa7c7a017b239c4c3d1a4ea19a518a869393a28c9203e45bb30bd80bd5643f0247ae07ab91e5aec11e87d393e973384e8a4f9fa3cf39c1e538e9a2381c8cca1ec7e355d5d5fc5c94cbb590a593b48afd8bf211b5db65025c8365d0e48af364cc647af211851c9e5cc08dc1c4e8c91802e6c7dd704d7042be894cff463bc5e87192f5a394cd9e8c52c47cb02ed6fbd61a14b0d38c645068272d676e017b9729d3f7fbe17093290e36456879b32e523bddada765f9005daced32f9887e6f39997e0a7bda295304c0c42c32f6f38328fda1174015aea73b8a05d8d1f7424a64afc50bee7b2d683454a6afdd922b6281abbf1d44c9349a465f186304eca035e1183fc6da6dc264c464fabd83fb3d5ff700f646a9512ab6741199ca28651aa3c794e95f139318234c3a93ebeeee97105d0bf92d640becc5b9ebe27211b38b502886e6a77647fbc59b81baa39427a354613e5897ebb99e3ec261813eb547b07bc2b68b3224d3f7423599bea3e0788c9e4c3f5e38349c9c74bd2593e9df53a6df8076b1a829d3af3232fd4a0099a6648a724761d59b81caf4bd19272a3c194564faa354ab54f0bd2a7b349cb0a985d277957c44ff03d9934564fa5ee644a6de552a39493efdab7d995ea54c47291fd1a7d12f5c803d1a4ef9cb4eab2c656a47b0bb7647bf8328f4abad7e7592484469d0e98ece2c601a50349c46a94cbf93e0f81e0da796d2fc24b9ca5aab5ac9b8b9845d3f5c62ce58f21b5ce8024fbc904df69fc29a22c61863bc74e82e8a9b5681903201a990ec14e73a027bb4c648a65040123d011ce50092beca4e95a02738b2e244f694211e6d22fb4312f4e809b2c708037607b91dba732c3e5226baf3214017608fd6d8501b9b9a09a30bccde85f10b21183be96148618fd6d09a5661821681b4c65626b4a6f2e8976324be4775b27b74277bf42892ec5fb76b939b8e18b2b135b85e6980fd3dd91db7086bb7e0e21660f950bb2e2bd90fa3aa68777777777753d91e73e44e9698ff4e9a9c6c7fc18527f89a57c03fa474837c539586946026a68f7069869400052f1658e4db8385842e1b74b1d57b2bd6d65fc9c13f081dfca14324b5bac11fba3477d2dd12f2df6374e748e23b8fe83e840499fc1bdcddddfdbabbbbfb4f7fe872736f28dcf8714b00ccd72c02bb4793ec28bda1701f0827e570fbeed8bb5737402640f61a2a77ffeacee3ef671c0e982aabcd889b36a824ed76953d0e19cd080000d314000030180c860483d1702c902371f914000d808c4872561f8ae328ca519843c6180280010400000c000008cc8c0200ea0178ab8dd1d8d0ff6a2a4a0c20246905e98da5d6af0f49af9d1d600e474f0a6b683e6f38aa26ed06104b9c101fa922708987456e01c3e296ca9e7d690781554c6150c936b9b7aa343fec57748ea8b0e3e6c2d6b9322567c60c487976e60968ab8fe6fb81bf5e046b1100a2e770ea6b4ef7a071d0d5c8cbfa3134584fcb31cede45f784646e26e961b6efdb570c4a996b0bc81e340d0934098a2e600a03ea3ab7db93edf683bb8637a1b6a086cb9c2f1a5296a1788ccbc30e4dc91d2cf8728f522f38fc7cf4491f882c3a21bdce1e251f5e92f05d20fabe2951f6b153e9f1d0f35947def349e393e17e0288d4766cf7f1d0cf46bd946e49d1809368bd86db451c3d18351c29999fc4a7bbcb4187c30722469afa697bb3506c6ff8df21be74896bd3923c8d82c7d0dbcc98e1dc87c73a8b490b748df167393a44b622425f2f9f06c8af95ff16af0875684bc821b7910ab4f5e8272e5df8a52efceca2d629fb4027dd168672c08c0ad87839a603dd27379c970d5879e505985ceadf5728982b5b56cafa7e1b6927b91386e2cc2e7c41e720456f97f6d2fcdc8f911de33dd392a683124b47e09c0e2de68939ed8846cad52708854f5121ee3a51eeec1dd4cd4f0cf6bb2866658ba0b14ea71f3c57e252a6bebd5d18048e8f6989029119e5592314c6d7f544953493cce23470a55c7b3a8330403bb16ea262504f81a13878ae07d5d83780aecb1edf41bb7ecf3ef0b60a1944382b52f1462fa73de581ba14ff40d1fb5c984c827e0c6a59c65b29ef2c6231e483ffa79ca7b9434b5f62a381f2e3c8b7fb304d54ca0d98c405d5e0e208ff393e94f2d234b9e374a8ec0d8c7534e4dd60b8403f862e89d7b4908e3e8c5a10cd0f8dcb7ceed4c01f73a3958a5e12a7130a343bd42e41d60e3fab21a299cf8a019fc14143416359c20a665e30368de6a7eeb0280a41c26b6c38027c9fd5b93564fb67fd9e396025822da55951bfd886fdd1550a8661388ae839b887b18e22a23cfdf771cfdf8018fa0be486b0161ae5c5aa349605a9a86622954f7d60b5484c26c2bac1efe36caf2c0b5cda0fba33ae4676007ea20c343564ae9f784401dad715e682c3b071ec611c569b8037738c6d6333c3517f143e0fb38ae16b1906632ebe96d760b2b395133a6564ca6b8b55f4632e93b34ce1973155ee72e07bd8381f03dd3ec22aa776c91ef787861b8e93b66081b2c95ab38a541da4d1ff4a098c59a64aac6819babfa98b9656d710e7398952d35cc8dfd07dbd4a535f2b8fd86c4eb0d2a21ad6eb3f13fafb9152f4ac5c696a32ad52087bb00aaabd47e934f40cb98c5f5fa26036c3c519c738f6439b743c21cbf32cf742cce85d98bb21d663e3a8d57004609ad5fefda25d5f3876a9bd06657208ea39ca408321d0aeb1a19e3febf389a54894f4ba42ed14284612bb4f3dcd1c0b8d3339ab0ab13228bb500efe13e792b12c2c91590cce9722be220587ed96f3e0ad76be841f16441a712d571e31cee781df62cdaec5155519d166ad8ca69a32c9098bf630a32dc71c7c4a84a5950c0a053e377144f2bfbac829b04d9c8fdb1543643c6f47d5c1027d7242151c820edb079024d826c139638fbbadbb00b269d0ec74808b7d3b306d7bd221406622cd82c80088102512cee69117e35b5cf5d83c3eab04cb5e19688fd72ae48fa76086eb57d1bc492fcc43f1ad0817025788a265cba568fa79bd163ad055fcf1e4ef060e96817ed3fe106ceb16c5168256b0d0490d33c124d704ea496e7a74ebc46c148869176fe2d8978c36d1784a657a672b8c568332229175306734816c460e5d93407a96230e2af72617fc2c388458a2faf5d72d67418fcd53febc9c928d52809ec1d28b6142aae49fe0db9aecdd77b84a90bda3a30749568d2703d6014c3f34dad5ea8142ab2686e9019fe244c58a1ea048e8e1f002df5255f7f37994590eb0ec46b72770dc474729f749d34101cb0578b9d688b52234653d67989c22f641adcacc3d0c103a1562038f285d3c49ad304d91c691196094d87bc2610b9bfcab7bb184fdd36f985e622c7c05ab448de7deb530b5bc0d6f9497916b79043a4174a90b414fecb0baaa71e611f4438aafb7c0a5ec606ae38812a065d23d01b95c23f109c8bc51e8498f5b982c297df24883b35ad86c26be3e501f2ffbca632d7629acb4ab1fb48b69715121bc870424423442950247cf6179746c710d4fde2a9b34a7a286e2c45276b363f6d63a7140adcc5cf622cc65c6be3b7821d429ee040b83d9ddfd1128174eec14e04ae14630e39c99cadcdcce48366174648ed79a2a3977b51b7c3ad110d4776a9a9ee3eabc5cc40e982bf65f4c7997af2a9040ed3312fe2de9918bbedd00b4d97fe3c60aa83986b0c3895d7912293d473415ffd0f6783bdfd1a1c781b978d6796459f0d1d84b4f8fb9e384830f95aff169b6131b6e55223d687a1fb5c727270845437ced053eeee5b358d34d7b4fc5a30c43d01e919e7460acebabf2f0b1d3427d68a47f10d64e2b3654c64d1b184e59c2a3039fca652b8e88da95733032469adf353203249dc489e7a279e6616d2fe15c5e618d089e14f26a6f892c42e8bd0dd21e80da1f2f4d7ceb44346b54c9a6ae3618fef507314ce337457bc6077aaa93eb01efe61280ef584a89a58b4026c8fb766ed18665c25455bcd474c39e8a89992d6886702e257825752118dd216cd14b21e5295da0e105a39667d1049137ec3066f7519f46a07080dd43bafcd93a709a6eee84e3536623e355491e44e00558cbbe14322703b9dbc4e60d51bbdf5d39f8af82900de660ef9bf9bd99b3ede1e9f27f32f4145bcec46a5ac5b71fe889bac156fc73745936b11ebc40e4d5cb0881a2e500e5fa2f72c1c20231feb52018fb77114cf4540f9207bbe90361d7a4c488c380be1d32ebf2a3f3b0203ed7c30d35c2e5a3570c0bf3566865973fad9a9a582cb5aa0d9121c14b5001de33aebf8e4821ecf34ed22d4cfc44f5a7eded5886df1426e71b503f5515830c7540ecec201c2d673fd174c38b194266806600ecab3e89e203fe12fb4d271683583c2ee3fbbc23e84417cde508b3e30caaadad72ea090be4964957ff9bd3d8edeb737c9f4f1419189c7d5f2c00fce1d16b2399033f0be688ba67576617fdd5cc703cc5cb95f3974bf5f672d3349c82e6180faaa587e7d31c6abc54cd9c151d88299eec1cf9bae29ee218a0a608dec311322eeb1414e372a6d4c994e12e8027f5d3b2eb36f4b008f535d74c4f47b1bfbcb9a44ffda03edbd07baf99f58d5eb2d93210d7ecc1dd0bd4d1067acaae8284cfe3c25cc36417c42cb658fb80af592dd25dd8e586a3b1202c099a61dfdb1a48f97264c82d42262e720306c4c4da4e3c66a725ea337b710f3dddf1f485df98a764ecfe27fbe98c21f5e3dddec887febc90a13542b2b3701b82116d67640aae88c273718a4e01f3a4d811989c55fb664a5002012125bbefd1af85e14231a362efe8c7eb2a5f9d166d8f0a2378e7c70cd960fec198ec94f2af1a16e8a43fd9a34f5abd61fcb9ed3d9f4f7a65e2512364316c4aa045781101cd604cc490ef042d5a41e1c0f549224a1cf3dafc4d9a247351e9f4138fa2c6f0c7e7638acdeb38829ad53183c77483983d7324fceea6e827c3e1c9ce136ea8332576d6935ed7fd05c241c2137ec164ccd025b3471c18b26313fcd0016296668c91595bcaf6fb17e5dd60a6039e45ef3298cece65124de31fe97cfbbbea2cc96fa8e48b4a49951de9aa5298cc56b198315a169fb342b77279f557e99104a15c673fdaa8e1c365d8ad4b377b55ab721a399a7d3cc63920c89210029d9dffac442bbb3a71ce5fe3cd2cf5530b999cabf53786040604dc94037924c7898d84df69759be1630ac18b54afe2bfe22087fd1acb3bc8fd40eced12d9acf9de6e8a3210b50ca8f504683081d01099d63760bf5da65d32df1c07c5a1eaa69458201cf34d0070fabf3a6d16b446e8ce061bf28e6e8c8fa2d565066bc1da9fd85067f5453ecede99dd90a1a5859f5714b48af46c58f4ccbf6b055a3a29858a5a66925a92f219ddc9c5070641532c2362f0a2b1d76a9b905d193b6869a165a7edbcca5ff799331c58a8ec4bca82de6854e0c6bb5da393ae1ac4fecf6cc938c84abcd3c601f8839e763dd5ccef90820c03de0be8ca9bf578559e51c1770962b62c5fb2ac0069a3f0a5b54f62922ff679f5cb9784142eb527ff4294aedafa876dcbd28e768c03be6c50798fce10804ecc53a60707fc9b0ecb288e91add32792a33a193ffb48410e93f33090e129364259df4537d2cb97ec68ed634eaa92d6defb2e8bcf0fb9f299d166033837c9fc984195b40146291c0dbbb9f52bcab5338b4b2ed8d419d4888ea91a67063a2c4a63e00a0c402ec801661c72651d070018d0b6fdf77006048b9f669306f53fcb4cc0160d62580784d7a59c99af028d462322c53ee1926cb44b0df7b60b01dde8c278ef17792f6b56638301fb2b00497a54ac4257c66fc7200d5258910f21978e1d11416d52bb4b8e5bfd0aa9384377bbfc84d483b8100b22a95e22a198d258e275edc65018b735412b488b89b8e2e23a33c3b65cfbb410d2adcfdde978a41021367cb18f60a73e24c4f695a7e3d7f395738f2632ca62cffe43fbc698dbcd23834788cca064441a14750374d225abc276a9410b0e4e20ddb8c2740f5be55061d045e6709ee3152ece0d7b52f84355d4a18da0d00d1d00cdf1ac7e56724602b4e978b6864723473b0b7bbc44c44ad8024071a3913db59747f5695df4633dc679508fc464473a992acb66c30effdfe118aee6e17191a81fbf2258673f584f6e547428f8dc142831311b62a9fa17aee3270502eb09716881b26032270088cd85951fc8cac40a4ab5384bfb21484a2e82cdfcda02600d0bc642ebf79649f90a479aeeb805224b15cb01c8f57bb34da33119c58ecd5ec34c3967fc9df8de8683b2a99ccc55b9d7999d2c6e085c1136a2945a94538dcef0bbb0d686c4efa22d879d2560eb9d7876759fd24f29a20f6c95be0f0524253b0102ec4181c1c3d864dd248360abccf23bdca859109af95d27f831ac8a156b055bd975c517df29a2e8b3ed00985fcfee45707a746e7027e60f13cf5d18708f0d647ba99777370247601cc618d7881a828d8283c2997a15dfe46218c45dec94415bdcbba9bd59f4292d1be96ad2bb77d1536631c8078b48ab34e8de4d332120f828dea5e76a434929a9c3c111b238b08c3c099fabb1ae4d74af0cb152aff686149fabe10c69c581b9c345fa309193f76820e0971835d8b6b2e8d9398e36d1926227cacaf523f143b2f25f08a948eaac5de2b6d490a9c6845b26bbde3304573f7a1c2482ea47d37059c3db5116cad424cef08b9bcdf9efbeb78cf9f4ed0988837d9700cefadc737927a5429a86c1cff04dbb03b27f1e51a7ced613407f4d74eb025ba4810ea29c33608425c1db89ea2d260f1df95c2b22d1e4a89c237a8ed805e91b81ea2c3b33f9a60808f993fd9de3f091b4ffac06ae35049cc31a4a96dd0d764a451abf10c80704319d46b7996fefaecd67d16feee822422902043b5f67dbac3cb7682806cb22ed2a3a2121b551dd6fce0b6747ee5cea4982edbcbcf0a3568b7b8125b2e263e6660616f6aa92c5cfa468b01108e83fa86f59adc0c9f0d3d260a8392bb2f5e7f0a423d99d56bfa1533c6a4f9a33ee153106eb28c9e3e6db478e288f568183a4a0f9e087cc4186ec9c88eec76e62799adfd438c3a690296036f4295dd5918e18444ad94c99fb191fdbaec6b94d971d0dbcd6fddf9a43a84b0f72e0359bc482f5676d885b3a739cd23fb2c7d536c61264a245981c4766f8bbce9dbe4672baf1b8c9df7509ef4822d4a49904e568aa4c407866881519b934369f7e5564fafb22372ed005d0afa05820794ed19753c5b5537bc0cbe6464c82959ebfc64f1f36e1bb941eaa140ab5cf565b56d452a5eae92a89db6627184d4a15890ea5c664b55c3d95a2f4a0169665999e8b4ac9ca97821713954e29c89e4fbd12c69f5f2f493a77810b066c9d082b2c84e2c37ece1b0858fc138f5e2ec969be1d121548130c7b6db49e57892549f0b130b86e9d063991306c12455034bf8bf4036875fa6332995e4bdb337b3514fefb062350f3b4043275a0f34d1559041d01d7bb8a7d8d12a55b31286de1e69ce80a04342829e06bbb8ab1b26757b0db968938477345225042aeac6c067f8add376b9f3fd514ab51435a627913e021cde2ce31b05c99dfb2e1f4bc3c51a764c2bda8383ffcbc54666d876eb7830cc2e1434cba555ec9d7b050479cfa4a0d48730ce703426fd1abf1d42c73b1c7183e311b8cc6d24e84724826eeec587fff7ca750951b2483e74ba84a9de6f1884fc0a8da6148a45edb12a1bb0c2a7d6f0abaa520281885768a151ff97d46ea2b3a8a27fc2456c89581c6d5f9909318ec8467504750654275e1eb995b05b6323e05fce282dae22ae6b436a56019390d74ac73823b6a58bf9e9a99ada1ad1a38131510590bb0294166e7473dd4007b68e37fd54f67fc09b189d19cea702c2ccd81aa307435f15690ccebf9ae290541e910b945ef5610a14940b8c8511a27cd600707686c089c21bd17bc43fd20ad57c422bde4b0088aa7212b0a48c766a1dd060731ffebe5e55c990c1ce8805a80aacc6b4811b239d606af4dd15e24336e4fc1e7b801cd168abd24dcde8061c576b0ce8292bc36c4f5a43a85e9dbb8017da3fd99033bb71abd84be1481f32911ebd20622d83046f49ac06e6be4f28cbfbebf95090298ead252909602f2aa107342f73574a7b3a28e4036062377d55f26e8e5bda0b3ac2480eb709f0d1c6812dfdff1af18fffb7a71a6b7b1dea76dfcf780337ae80d1279f62d8ea9db64ae6c80ce68cd178ba8554acaa4f838b0c3bdf0dfa2ce368215c3a5ea2bdf32ddf8569e3391459b2fd6f621083e397d4592aabbfd8e89adefbe8dafe34c1930091dc5de20bb88def08cdaa93e422005da6d1d3e01769d4bc5a87f228bfdba315d03e52c291c134687aa51c71e31b12b4651f1c50638a75ceb2f08746c57e54d0ae3f47195978f688172c8f596a2ebf1626655e3c0234e460cf15de1706821342a03370369ef1ccc615832976be63854ba2bbaba02b46a39e58a5784a2c32e3215a2e701f673e53591eda4230113e8a8403f69f0053a46b9d2f5d0ad452ca9481ba0c3544bafaad6b28d16b2663941e0da2102c1a8aca2316e656465507ed7f7e86ec95737c9834f16ebd8965450f255fd130940ee366acf9213b9f7898eeff164088aa6dc5644f700f68fd8f5d0914f2e5930f0aae033f1ca49a65918314515043adeb1294edf4e17967a341f0917c4629833974efd240c11504759c30a1a8574a646a6e7133ada624620f03ce51918f49be147554ce812d63e104d6cc5aa50050d996814078446929c2dc8e867a0178321a58d03f834cb2463517965e6a08ae3694916c649520abff8d471204ee97351f2f7ad4a0cefa429f8819363418996a371486ad469f05b9308b573f58688cf4c1419d9dd060976ef648a3ef0ced078b1ed117121d911907da4811f66bd900f4d2acba509f05f305e3090f45f5186838d10e78d4c9160277901bdc3693cc1efe93e18e0d7255c635d40c634c876fadf7087c7bdc2b491f4bd81ee5398d48718765790a85fae74c757f7e7a6c04e2e57551205ca9f74c8ec67d8b2151aa9a9cf0434f36f3f2f9cc4d41e49834f20b1e9514b15aeef4fea61778c09c3ecfaf1769ad7526b3089a7e8d8440598f44c6d4c52dca83a59232c3055fb935dc5ae1f8d3c08394e9a110a854386834a2d650a3e71f4f06ccfc6414c44be3ba5308fb024a2fab52d7b9d3807a938aea55ccd2a2b80b2230b1a9eea3a6eab2af68e73f4a7cdeabcea474de1f85eed47a30ee106bc56433f8d957c9ff3d1264fedddb8a663d0e611e0ea383cc4d478960ffa2cd1ce8e8426946c10288ea8dd5f509778d8ead4eff3cf6f3686bf08e80d8341a3325042b86944aed99094f11fc8677af4711f393f59d27b0580bb4271f577362fd44c0879e924e76e79a6c42037dcbc452aea5c8213a7f16db48ef1ba88a2f64ac90aa53f8e51804e4a25b9eb8c35c7ff4caaac472752151e746283debbd92b9f438c670cbfa0300bd2e274fd08f7b653b429a77092a0a38e44897f79c50965713aeff9decdb0f555f9bf09074e5d49a0d06f2f5ccbbaa2898df4103347c2374cb646cb3c632d12e02aa8993bfa48fc0fed5366e1e053d9112c7aa32713b62ab8b9ec8a88e2a332552f163f0ca19b67bd21cc61f276ddf4a29c027bb8bb5a8e9e43ac84e4a6c3e5464e1237fae849b365eacf908ce74d2b3734f7f32490d4e74a6d0bd10fa0960c59a682d4f2086ba8256d8da0ac00716a687a229e1d43ac15275d25611d7a385375a3d59537468e242ddeea491a7a9e6ac2dd5cd5e1fa511eae32e5c100cfc594bb2f8a106d99b4f327dd24a31dfb2ca3054c30e7f7f9f31ad40960980e0e656fa1bebdfc065486019f1ba4fd111e3aec08b28aa5b544157809a971cfc7b1162d20f8a50c852f4c7ba4e5dade20e142647ff75db64994b40978fcba98501a91cd6ce16ca5aa118f352d20465a130bb68c32ba4b6c52089dc4ddd4742e5c57caee4169297ebc94d32fcd08642fb53e23277e3a2332b160f1708d89d2733a5d15a527b868b169684da8be10ee2dae274879274052364a4fe984786117a33e7e00d2800aa2f438b51fddfccb528424a235b54842e028b290c32583bbcdd2c1ba24fbba4ce532cdc3ec9176267481e621fe258ba2a2f3b96ee262dfd6d3274f0f8ffc23e2a044164a76ba9988fd1273a8a04c1c3bbb3141492bcbfeff74f1f5aa8628f95cc36f7cd34bef990e9019856e5d7cbf89ebbb1e045a558cdd2e4a881416e7400e9d45a6a9f08ba07a38c2efb0548754138b93bb993c22a92dc74e78f41521d3efb69c1d01dd3d3e6e6663e1122777f406c7d9ebdd12998316e7f8442815d59c743b36f7506d659b456071d1dc9fc4142ac2c3ffa25d640db93b8799efb8307384d37aa38b9e1268645d207657ff4da0119fd72eb62ea42b1168c4eb8b638334f4bff0a654a051acad0f0aa3a14b5efd059ab31915c73b6444561ed959798e82811a5a371c33a321e8720d801fbea8bb2471a4bb1a5baec5d54d198de057e7ed7ddd6c6eb805ce0a30f15e91389a51dede8d123750b1f0b22552c034221ad41995611d2ad8d18cb228929ef375e7b3d9aa47653f683c7514e61a2c27d090c644ef3b4753b8eef952f02395519e1eebab6b9a288746f6af26c8062cb2d1251a5f604e049dd93d0d152328962bfccbefe5f4f1f725defe1b0a3fbe54c0af9cf1d3257ee1e45d167b95dd2bf6df635be0ec71ac17f396c3d5161334450c06d64880c02424221e4468e051f538d8f0ea13c6b1a1106eb476d446a7633d46a46e5241e0aed5495de73950a178de977e9bd1ca95dadf784115b583f0a605e8ff2ba2bbaf30d939f8543e9ab37f3a7d0f5aa5fc6cea1c458fa8e9ccb7d8d9d7060721fa3208fe42be16609191e6bbaca0959f430262a6b64e9801613e8202ce1e33e66b46e4e52333375444daff08d959c325a9f1b607eb5193c540dc80372b557e0384dfdaef5310f1c439fddfbda33fad3e0422b626aee97df5405e8c68104902b57bba2313777eef95187e036fed9a7ecf2df5fa0a26aa72f7eb3e7a8d1b8aaa17195e827edbda62633a0d01332652df9d00ae7a2de12801da01ce67c8a63fc37081501492067426608565dd63f733ff76412baff65e33c281d95cbd47318b85dd8ac5a1d97c6154e3b473a3c7049cbfc8347b02e05a9064fb8f0195c6c7643fc935fd0588372a11d66d7c78b96aadc70b1cb82f67108494ab216b30b18075f53de53ebe4462a82d697b5a3e1df1fb505046a72d8ddecc285d7ef9269d05197c1915b80b9b4fa4d83d930d6e9e7d155c3862c37642759782197ac7d32f95184a6610453133c96e523121ee22491915babfa54925d9a0ac1d3efc3c2d8886ff8095898a390d60cee8bc262e87ccdb785f970bef451b6c5b632e49c250eb9698e40c05734b13061ce48a729e3ac073219e6d9a5765442c538328d3e4e92618051644e4946e3b2540da2b4d2cf8cd771e5b037133e37e39b04efb4edbd3decbc78bae81489244c1c1be8888b225a6978a9d94a7ae07ec64fab54f2571a8311edf7e35e6aea0cb460cc562560fe44c55fc664df17d8703f9489a45a2898235c61ce78b0f57061786e01a33bfd6dcbc464b96df3ddb30a7cdcb8e36bd993b8489ec8d5de8ca9c043504c280c28728c4e04e608c7f1339aac8c45d576449642e5745c2df88d80a8b55698b4b3c4cc498b6f0e13101eb62d847a65da4de4223141316a786cea3b876a2709844e53fca4e9970ddab74b64cbe489bc2d6284feb00ceb22c9502bf561cb4361bf293bbf6c8a702f017ce611c851be69d09268ac0a136f6143dcc394a5f25b7ccd3d4404643db2c16fa433508ff64302f1e5a3fbe8a7cdac61e153c4ea6e1a7bad577bcd82e0e0b33e9284e1a89731241f60a53835fd701bc516aa15228ddee1fe3596232fdc64b3f82fb7b9889a360a476a5809003c4716f06090ede093f814df093b6ddd74df3135374479cad9cee8e7290761d16569ff4b6fac725f0d403d6ae8bc79177b473f773e76176b0dee6ee3a5313d598d0814358ee7076377e25d86e3eb643f9ee81fba2a850169d765cf9181f6b74c89f48b1318d2502b4d14c51f4c3dcd8cdf5ffb115afb225195ace4e1d423ee652c0b3bb263a4a80f1d74c9ac730f394d21984bb19375178bbc13f5dfd9628990d8bad992711c79a94a425b809004c2407a035dab8e5db792d0f52cbb460a753056a73101beb1af493b7efa03fd0ef0322a27b90808012ac1abda6fcab2e1a42c4b3235f57a9710c4b5acbdd95eade41856d4f0c879edbbd3037905d58566bfa3896ae7c42051a3541f2d23843b8ebb4335b4b5884f3f6cf955ae91c74f4cacc861370bd8afba07f02665a31add95eb2f39178c9285f0dc820540aa4510302589665c26b608077d636683853bad51f803753b72a8d913abf6863c08afd383a303550a99ced7cf93c20e10bda65acff86cc67b237cc9439c8c89a6216a3e72eb49b06ed445cc0599c3e6ff54d8634c7bf9039cf8fe22753d34dac82eda80e543007c6a44a8528649a0aa8d78549ac0e497f3a4d36703deab180064475d83768c20d51d1f45f5e130b04cbf4d75ea113cc36b26703347f60c99435dbeb847cbb734c47cf59517b0d3e6402d9dfd163b899235e441a8f23bfc6dbb1788e849327d607491083c2cab7c1a5fbf818962c0b7141140e925bf6e3c74ea18ef3f0a47d079fa244161eb4be3ca03a1240e9289683dc2077d817ca1bb4959f1d8750279ee487f8dae19d64cdd44ff8cabe4689088758a2ea08cc7d29dee8ff5a846530d0c303c8664b3cb37c017c9efc1aaced00704675ccc38c01c2288b7e2ed4190fbcec5ce4974a07aceb2e352e2e3a7996d55e59b4d84a99980d2ccbb20f4627cfeb750a1932c901734297a1925b319b44e9cf653a10769f0e87aad15cd73b52e537d9d7cda8b4b4c65f0e85535fb36a76b1ec2114ce67337cfb2505b4c807f424091bd5c4f68551cf5bcbd1cb5a536a4d580e1b86b69b565a0c82a1a6392c25031961b4b5a30e0a1f53ab1d598c8671d99a1591ac44a523dba07565bda892ff2f0ed9eeb5ce05544da43fa51fcee8f3db05f4834a230a8d7267682ebd8d17d80bedbcaee190ea20a2e82c1411bf65a4446f0464308eb507273b60c64e9a5a3636a2853a455ca2ec937732dea90e2091901432a069dae61337203bd607c455bf23ac32e2a0d77cb58ae8387e775ba6741edf34f8a314776069d21e390c120941348933410e9347073d5623898f53b361f7866b5f80612e781bfb932118c4241a30128a4cc0908ef24e8429b248a9c75f1087d22b7bba7faba779218d7353d206e131d1756b754d2debf673ea6f9a81d0a3b30180794b2cfccdc3cf2daec04abb1f572f6fd7130977273b36d04e0f14e720d0058e10085026d7914ecd7799aeb71704106cccc4b162326c9f4393405d4a55934f69978d9da631d9417a968d05536d804ed55199cc986e0606ee69d9c96d3efafd6d8e0f442f183a6ab541d9f4c5487422915fdc8ea52ea19eed62521de42b83872c1974cadb30ea1a001206faedef6ae546c1380de9fd98331e2acfe5a503191d65e9316d9e64e101b0c31c08b16f253f1da982902af9076ff8c03e94011d863103c3c285c1bb2cc0fda41805c8402345373d5da030688151090212ad2f8efdb2aa26cf20462882355ff2957a3742a244dbac3a9fc6b0461f408fba860c50821d8782ae386c4b242b676debd512db04a914f04f203ba0097e0932fd26957422780b15908510d8ae1ff00d6efe5b6a6f70b284e1513a2355f892ee9880326dd57c4c9a9aabf9b15b4143083f1f093c8d550ba1fc7941cd1dd3202253944e0649b33160f769cf7b9133adfa1280364316f160ec99e90240add1a3eff1aae59ac15b6b96345b6c02fcb2700927c7b6c489a3d3c3767973452c2056874e6478614b5c10b44bb7a8e2d83854241f74ba9e4af8cb6f72c75efa9851c165dfac6326279a5e47fe4fa130b8e37f173f1be60ec11cafa53e0d2dc4903f5b8a35e65d51b64a4886986a76c4c7c8910add8b720ed90804fb7779965e4f5b045b94df9d339190166923726b56fbabdb9d56d4dab9bd873a6047f5b3eea6b43b45aab8d2fb79e6828226887daf566f97998b4ff3ce782fd32a39d87c59330148126331116b3cf218ec6cf9c1739ef40020c922c93120a106cc781634bd33e47172533120347fc6bb16ee9365f80f283f0c0bb7a9c94ff89e40f70baef77c6def2fc133c717860702381e172eefa3d700ba25e3b1234c063f9e4c0e3c0214dde724969858936516fad1ae20e7f4d82533798381c1a743b14d4cb0bd9877b4dfa404fa9443f83ec2844183fe8a68b58dcb671b38d265c7e4afc7403849c50e2c25997fa44c9ba85a46654e60d13213d09f162d82ff4b79549d53bc38f36c549717ee4f08f29def568289bb44062ac25d2f08fb83e09d306837f0684ecd83b05b12f807f8e30f3f99aeef1dc71b4a4b1e100bd55a7fdbea049086c81d94003c08b3a437fb44de782cb7c34a2ea4e4d385c939fc87a7ba462477e91dd602ca32b17fd5ef58c1aba6f3b42d76dfa955322841f7774bb903eb2f529d2913d2d45fd633981aec9a3477361692e25cd482c7d70e2ca39d4a8f64eb4d2577b6fcec347c460f4c757057796713f8cc529261108f764fbbb454acc1d02ed588aac0b18b44211a36d6228100513b35e0836be2ebfa589429519479353d72afdda4aed1c262c66a2399dfcc10966db473005495baa7ca6fd4bd88d08de27812d77f65f5be39ff1804be30096ec9e102cc62e21d87b8d0b99811bb1d783a1ea4b8041256d5d0c7e6aa5733c9a7601e60dd6117feb93c9ba002fc4666d284428bb7a70dabb898e81f89e54e13e322e041c892a203b104050408332830e8be1393bd7e3de2d408cb5d18b9f63bc81dabd26337a066e4e44f81ad1e0d40cd97bfdd49372084f5485c18dfad870b27bb48a4ad113ac7fef19cc3b3fb13ca925bd695e61978af6acedd0a3f0c576436a8c8f1febbd8db8750ba44ac5df93c5564389be4c43ba176d60175158570bb40808fdecc61dd57d20cda214a985b4f3f3ce2740ec7a8aa47357456921da7fc9d7027937f2d71ee92b375d4e075d21097667c62a72955a846402ea30094746de52d94b02a83a46c41bd91bd5cb66327cc0d9e446c753b131f57b80102277ed5d2638332f911a9d79473046c41c3526d0fcf676b14260fa886aba327d4aa0a3239767d90b25c06130515e2ab24e5058a81bb01111a507ab72deed79c675763a803c6fe0f0ffd449576902ddfa76df6a4a0a2780e9be54d6e4b86dc513535d6c411dfbca2f6042810d495e8127e54d8dc054e8424811269c4ad5c7496e2be82e54ce9e9e37d2ad2c0509d026642cac8e9fff469646d9cdd776eb8cfb1a716c40cf884c1289c112f23192b297bfb81dfabf68d58b73f48103d1534a6f10340c4d5ddb5fa752bcf8c1b99ad06a2aa4eac5b082dbda902eec764cafd321b4be815998cad774937839ef4ad535265e2bf57fa0ef5454d1bf383500d7e4087607a23619d2acb7686d38d00289fd5db1d8f6e4005e8156ec3be95918467a90b8c366c31495f30d3e36f1cddf864431d00fd4e1ece553cbb5914b435a411ec75ad1c938ee417b496c614a52896e534dae7c876a97644d5a8668d12472d6a3e10094f015c0e8a881c3022d2c9aa816e2c2c25c91c493bea628d5563ea6d68cd0e7b10a6c7697cac328c21ede1a40c408412bc859ef0cdeaf66076679523543c934c32cf8f7c8ab89d27e95f8f58cc64d3e9b84d162a85758561cf4161b60af984f94aa5c255d63df4f630355f7dd051ef12a37750047fb1be586caf52741a45845655b88deca1c3b540f73c0e98362eff038b133624256dce749dea02cba729c5c5b0de59e042937a2db9efd76a3ce751a229a2ae5fb07780508cd2820c4bddbba16e67382066c7a8cbc0b8afb4295b1c96b4952b3d6cc55ba64494967641d84d32f9333eab5e3993f676697c39e958b094df1136d142cdd8aa603825cbc25fc2b6664332fce335dee6fb0d545de30da8976011162381f47cf13a0509aae6bd009750cd0d4fba04219a1dcb6041bcb8eb58bd651d7dcd9a6e99850ee1cb96f0babdb46bb2feefd7b497ed97913d02c82ee06647951f09646ae663a8d4f187b7f6b86b16d782a12dd9e1d48eff0f7d5a037be4b1102bc4b3ba2f04d5152079060375b002c303dec212271e6ddd7b82b333c7bf31db4dfa2e2ccf75a1d0ec386859e32ddf01c67cbd558b0eb0a6c54846261c9985c1a0fa50504ffd7727ef35f1e656b57f11d035f1d4253a13d7ce297cd023bab2c523269e2fc2139516761967ef3fd1626d4d4b99f136a613753884961b8e8715cd2a22ecfda9310835054c7a2160c9f32d2c3c8c0cc9673926647954e3200dc36dcd5d8d39d9252a4aab4c4b7c1ce5e1f4d1158cfa414f2dd0e67c546114386ce743b0311b1edd55d66efe6da558284838c421f0396db5b063234cd7acad2cefaecb43cbf807e05c3014344ba164aa7c45128dde4486651448450b52e7338f98d2cd4dcecba899e429185b046f472b167564a782db134fe2dafe110f7c74e674dc37dd661713cf1a59e7da662c55ff29a2a22a9b6936a720c6bcbac90ba4acc21216010a3f46852557199b3d9e33a0aa8b6f695f44600c0fed83b6b43c0df54ae52916d28123f6b9fdaa2ce7ca890b16598b5784a65b09afb2071a7fed5d56b699a8ffe0b01bd0ad030926ab6e84641233c81d56d342c1611e7cce41e6bac0747715924ca4bfa6506885d12fd4af85f08226b2c9e74a200267c452f3f0e293ff9d5af974f1e45897e84983d4356c1f3f397ef4d2dc90ba7be1e21542c59c7e8c99c4283653ea8b136e74d1f0a84c60d54d4c1ef0f1f56b110fb5cb7e15ab800bb79e87084f2e24dfd1251419d55a5136a4e7cf6005a146554033670c662576d6bc203721f008a40fa21e4ba51d4c1c427080a2dba47e90a6617b0aa24c952c63a6dce4da440a31d8052036663e63d156115ab727d510d735e1323b256e8ba8300ce53054da5ded048880c35354f527ad0f04c49966b1486f1c3b2cef293c722748f32211eb92ec2cd7d589d4219e2e0020a51c2968ecd3c2b40783b692cd82a4f27751946e52adfb55e7b5f8788ad078b7d8ad0c49b40445f802e38bf97f29bb200801e19407848786055ea30c18b58a50ae0d80b115b83bd63ccf68011247b048d52e1d5d465b6f44838c1b7bf0063bda0d51f666b19c7610f2eca680d2c9e1c661ba01aaa26f9bcf53780351b97d031b6502c791e50b87fb53278e52d2330ea138470e7575aa1c7265c91c826d0a98ce21e4237404543a5e57d4f1817510407957ad45d202b61e66dbd132043fc8790d8ffb7e3786866a32f14fded19d6408d1b5fba91129a4b978e641cc1e67bafac508180f61f691c20ad6e33cb3c3e7e5819ae524747c8357040326069097d0bb455a87c20c12eac3a9d3e47c062747d0b7c41dc41c906d7c4323945f7c43e9ad7003115f3fd7b718ea8f8b275a7c1d472cf0bfabddab741ccf4ceea1c112a4193e8799873979844437e09b4bb9ec4a36923f1e27452c93d61103175e7a6e9663d3438503465c7e56794fc9f710b3894900566e6aaf691c638dc5ecd10a640e443bb1b5bb997276b243e03d717bb2d8b657b523420e1403d8cb02dbf26a6b472a6ebd69dbfc47ec2042de16a4a3cf4a619629895fd58e0028934d700e72660c4bf7f35e7ee700aa614704601c6fb8c49f50d7baacf9c2ed3cc01f8ba9aff8ffade8b7bd5d6e9945d94d5f2a192284f6bcb77f0001ac8af44b820aadf03a98ba50426d338405c8bc1dc36254e01266adcefec890e0134408ecdeb5e5c5930427755d53f034dd909610b97e19d5599906af37391f8960cea1279d9544a505562b688dc4a61d4406803a3d9701593a2243bbfae8551b34eb731aaa22f527d73c5d0f7397854107c522a2a87634d599e100f290beaab0448b477740464026a22a5349f7f037f723d7ba5879d7a0d236dd4b2881ee02ab19a63f7b7f2e28e638ec8b2b650ad620ab857588372a429f46ae9c60ce6b812c8cc5f75d41ea8f49eec4c6804cfaaecfa2dc27b0d07e6f9e5d21b8ae1358817061a66c41a10574ebc53469ba5efb35070eaf2454a42558ca934e6f32e67481549c660907520d40f00dc3c4aa290786248240d0908294ce7842ee820d0c135801267614be0e8ce424098f5c401bb433e4989708033c550c1692f0d50e5d264f8f5a3d4189af52940ed90863e8b6a9023299856b59786d36eff4fff28c74c686c464b3bbdd2a9440427aa129efe4e1439a54c03a117943ac9a31c7ad6b6ad033ed91d4e16404d9a434b8cd9b4dd914196b2f2d90b6467c3f2de8c4df8d0d126a4bd3507b0b140fda4e3716787122a7b36294f18bae2240a3cd5b955d8b04f322e0bc2a102caaf983cd272e102bdb329ee3ceecf99f4535602191e3989fc1841662842494b6c55f5375bad08e73ad88fe8e848e12eaf8da644329d4607d6c0c439d88479b53029a90413501142b74d9f6f03cec873d57ff2ee7abd6d583d847cfa3141d8eb395188b79e485ae18ded2d79c7c3ba18d92725e52ab9e83a357b25a63204daf438d985efe3ab1146b12b1d575aaba2b088651e7bd9ed99d75efead8f1987211b544332cfcbbd9dac31bc78dc62dc926faa3ff295c378e4f2421853d41085e8ed6bc43a82b9026c6fad198b1dbdbad5f8db0748cdebd865ca1c69566ce5862e7ac4ad638ca6edea19e98fef5760120ada7fca78373b9e84d55f11edfa209278000fe4be2c100967a502b66a3bd52edb49e84878fbcedd76744e473b10870bb3fca94619fa176dfb71f1c1d785b9009f9cf9a775145b15183351433cb762c53ad5847b5eadf1b897faff6da8cafe89d9fbeb1011253bceb37d507650dab95dd79980d47cb9a0f4ebfed8d8b73b29e905ad74d34128c662ff1675fff298edeeae17db0bb4b2f8c4711b89f1dbfdd4f090ceb71c36590b0cce247d859e1f2318958bff86f3b31039b9814b83f56e22c87cab71216348ee6e36b2248d9885802b8e94981a523baa1a589b99be37c8744c69218e4454c65c218a8f4110f0dcfd745c4b97b31be1f2818ba82848d1d43f31e7be5c16d23c0d5e2088aa6131d733b2bec8647a318860290a737a73cd1235d7425aa1ca1026a0111a55622e02291471051350c14623b2a787412267ce58cd5dc1c31f9bc1573d3a18daa8c445103619d8b3663468de5cc67c139a3fd9bc7b7664bd275e6707824d60a7ad9cb45f54c18a4c87841c21ed2c81a8e3867b9fe1813e7392045b472eceb7e3e1a53504451788dce36564ddf35544d2bab2cb4fd0ae5a4fff79e0434e96fe1b777b754b628c01cf1c4306cbd589936a0c70d4784eba5f97b2f547179a44bb6ac29ba5da319429625ae568447974e4e971f6f4d368e7819268ea0a676e0442a01402cba1721b439d3ed8a54554c59d4c422063af85c0d6f6f776211abd5b056dc9437c841b414e7dac4a2dac3efee14a826779f30bf3e9101234951944c2d8c3f86ee41e3df3c03901c580833173105b64ca10140ff01e4a00e1abc1ec68a582700be8605b62b357844772a38f3e0246c5de37f21460fa9520da080ee2ba84fe9917105ba5a39121af5f84010decc5d14105c7c007e5db664510e42aa1830d186abc17b70c7f91abc9d7be41f2c9c2eca09dc87225f8b5bab97c1046eaa68da092ecb60bc45603a628b8acc3d95e7d4657a1f0ce10fcad322bee945b95bb54adada2e8fa9dcd4ef7ba31d1e1824fb42098ae704c0aaa196604654cfab0b33b064a5ae831e0d0ff095bcf04cbef00c25878556a95d02d68a04c8e4b6969b9026664f79fa544fb2652d1d14d509ad056768091e28346e9a5c199d614ed182df1d5c5b623d47615a1bbcf19c2c54e2c18cd478ecd853eac5cb8e730623d4fbf7f2e7eef4452a553380363e9508edd216965eb688103763306cf0841000a1203f711549792a190c49d6b09600190ca5f3120ae05509b5dc35b302499a5db58249cc8841be44ddff339253c6c9ff4f97d9f8812cda668f13e242ad8638f43a66983967f2ad550af347ef9cc9b16a6cfdfa7633898a7c8bf2ceb757f8bf313d599198ec58816f6a893772ef9944bb420cc4fa2fd7d502675dbc80d817cb69cbdcf0395bd02b37bff0c9bd2d4b450d7c63b1d8b6c2a11952f79b53c41a0ced67551c63a4ce60a8d16479d757f0f2d4ab4458f78319563be7602b7bb307f9ba105168e60689b98373e6a6e47c51ca43e227607fcf4214cfc97cf98336a7f107dc64058679fdf96d12683f18a7f8812cd244470b401cc45fa360a14f50705685f9b13de42f063d7560f9b67279e96382a20a087473584313a84e5030fa406980a75fd72628889b5f8fcea476fb4f8a1e1a6f0115f0713c61919255669c7192259b49feea5dff043cdbf3e2d447f29dc605d5f9bc6a2d9caf7e5b3d403cf443a79b07916aebbfd2ccb2d2b821a717111499701d784431cbcdf7e1ed2914a41ac858ccb0184171e42f1f15044a454a8871dad02ea10c3a4c5391f6b193ecdbc87c1095271753b3fc0fa527bffcd2d7b88fd09509cf04ff818a79fcab93860bbcc59a04058e389ffdef3e99fc6a115ec2e87d8d7f87988e5f5ca7f48a2abc1cb5b02d54fa629344dd10fa4c310335b3df2b1118d992ebaa4386a5618ded82fe8f1dcbcbc59f1e738dd669a63761b16d9499f2dcbc7c4c3c6a3a89d84ab3901283d8b608882adccd0a9513c116099fcd5ca2758bb1e9611cf768a30b351381663e3048e44556b57273e19d59d87eacc8811fbb6d19f21ce08718ff63417167e4a0fdbe19fd3739641e8a50787b07fac58a7605095c5c3f7830061e989d13d4c46d9e99a3e20fa2ec55c7815f877817421c81870a8ffd88c7cd8391f82a99e3fd7c30bc122316ea83a3181d9c6a6c45a28389b4e64c57a8df374dafa278a090c7c66b7be4582c22182db90c1f589111f34f90f83ff3b796bfeac9707da25c60cd3a3d936c7216d92d2dddd19e951187c0a0af5dc496f4c3abf7f3922d6fbcdeddfef1d44366695817491519545097ad41ad0387185a10db0c3244e70f8c031f3037dc2ccf30111003e018ffbaa281d8fbbb15e7bd647417a115a56f2b4c1403fc99c2d3b34cced615e6f13864a69eecb34a0cc142835df359678a0b4c5368c208370cfa9ece700376fe6f443de1c087ed4568ef829039cb63538751083cdcf5547d03d187b3f846f8b86d20476a53e650f0b9ba6b64aabd8b4d9b39f6f949a48734c1f9b26be070d6c2960e8e23880dedc9b844e1b4cdb14d994cb4bb2476d713bc5dbdf8698ad87701552ecaa331c051a5f5867c0dc1ff7237b76d1759e73b80f8bd38ace4c27b18ccc5cce8e20b43c17902ba4130fcdbf80709e81e9ccdcca7e4c622b8f7018418e07a33e06dffde7d5370c0d0c984e8b3495adea5bd1b99c7589e4c8535bc2466e4c25f83ce02c2c0156b8753cdcdc98280cfea48e37dbb3142737b1eef040b7979e70f2b21f5ef30947709f2bf9ca886e81e98fb99d73441fb2822704fddb7de55cadc863ffe2f191a3d6c0b53bc1e4ddadf4e5ae712a49ec86f749c9c60c29d71c98c373179c9dd1e53245de6262e3b79f69b96f89c6d85f9549ebee9327893f79c6440ae9493b02f559af86afe1a8a2e168dfd59d944afb6200de0b03cdcd3a4cc78086c694465e6924c65a737181008fdddc926e630dda700a49adffb7b9659c2b2d113bef2d25d46148cd6a420ee57dd43e23cb761084bafe845d6f4b3154bb5aa8b90f3adc190fdf43fac694cef43b4683e784d6e82773ad3d2dc419b4851b9b61351bd053eb50554095e56df0c9bdb6775ea77fb58ea2b01d1328af49f86b022d0a5e722548b3a09d32134ae9ec43f274830a429f630311e53ca5fa1369adbce0c4f835eddc555688d9a1322699b8bc9e8af6b87b6f2ae157aa0538e483909362b1b11bb6dafa6d312ff3dde3748b0989dc5fd95bc30534b3149723831d5b38a74fa3c77e6f53c8e01d14b5fef9e857a8c0c3eba49907e3bc1ff284e1f0370225322f4278b278ec00e73e4bffd24ede3dfdb37ff2c96069004df3e2d8cf48fbd137fb88a2c348e19cdc2c17a43086c78bf714c8ecc4bb3c037e2f17e4920e4529fc97df78dcc9db9e089f7a3f202cb72dfdd450b899f25884d4c444ecdfcc220d679b568ba03cb5403d4d4a509c9dd95213abc43ad0ada934b68145e6b42dcbc55895bab5a8398103d5f0b616744674280d4632a15b1d91c26495821feca1d2f7e4b577fec7017225c655e37a7d4b70de735972f7cc8a97d1eea0e98baa2ddb6cede9bfd4e5ef2aa13cc28db67531d0163d7f4d5dd57420ce2464854605fd42fd42d3f0b5cd2f6cfa499f3f05b412d43533de4a5aeb22edbba0b342c6b76455380482f7dbe7e3fc792d37ad3bbbe3a609dd51d4a44dee8800878eef044d610074349a512be1dbc3c72f027c2ad30a866eb60489365112e9bfd651dc0e0403648f51b7f4b2c1666e688680c14c73058b39b1cabcdfa6237bce88c1062dab16fe5c2f36fa4480e8819096c8c1c104d55153dc8bee7d5b859a6a157afd5dcd6e8a9028119e0069f657f61db879937076dc04a574ab41c2d8129658f5daea72e7ab7ad7eb73b7cd8fc65e8b87a3cc5d251f110b486718385199f66e896246140f93b42e03b78d027b08ae696968132001bbf6620efaf11af913c0f242c0605a95822dae3908921fb64b9aee81e7e7be602f9cb3aa304187922d7886e2c2c96f59c897bccfbdb155a4db87036d5b429ed16277147a0c42303c67dcc4bc26a1599c86bb284703a267196ada461bed3d89fd515875418a8baf52aa2cf7f1f86f89da3cf9668edc3e05c2e787c0bf78a2ca13a5bef32ad304e1c00c575e8fc7efe1417638a1c013e4c9986f42e6277b291322795d175a978d2788de586d119cb086286eded099a09ff508eb8113826953ac646e21257f524093f6d029684af161b7b9172b9400ea4e10195c0fd09c9c386d6352d8512cf54034b5919693546460a4412225f0628052450a61caacaaa018a6e28be73f51df1290e4a04a587d0540cc2d3ea92101e95e1f23bf3d36bf8dbffa99dc319caf3660efaa63b824bc041d69db11488320539db32470c0e18661224a3d47a1bc716a0d1910cc7b7ec715747761bc053ce5180832cd619f764c031cae29232b754d9154aa2e9af1e8fdbb764677a1437def7cd1bbe65e6bbac930f95e5e2e4cf613a2c9a033a49b4782f1f48cd9d3ef22f93d4e228e592999bb868837f1d58d70323a202cdf4566c56ab390cb86910810cfc3c9680125f24a13a796842cb4e23af6d2b71548f18d63145bd1eeb7484b0e2807b9838a617239c646fd0a37c74e04903bb6a064b21714fee383fb9f9c47b7cd8163a30f792fe3a001909287bcd4fdb24a41089804804491d86062a92ac91e4ef3ac89b04d11db92b02b393a1e9017fe9a71ecf6c2f3f4190018b3c936dbd9046c2cc41df29691ef65e9ded92e783c1646cc34a4cdf0a715f0aa1d4d148ae72d01a31e2520d42cf43491175f492e665babc51ced1fe5e238943f0fda521ef900b12e80cbcb0a228324f7c853e0943425b1ff027bc00d43fea765f076f430f7909ade45866cd6abb1f1cfe2b3df4e2bfcab840697c1ec13a1655476b4b392e00a4141c9a8b72d8dc0224482aec6955326da94c59d23b490891fba077c8cba19410d9863b111678b64428c6840ec37c1405656d67d8c55ce6a6de254f7c3d22560a3be74aeb56a606a3559e2c864f4a57c1fdb37c1a25ee6fe4fb09f367d2fa6b609a1d9d5c2473e1fde5e8942370bf7e433d8a34630ffdff8920fe16d980d9efb937708cedcee742713605009480cecc2b90fa2e83674d34f409590182d2c544d584ef100098ca1268dbabfec80f07aaba8a7e7c0b94227289139ea2d7c60b085dc52e9dfb0e9bb299f84e579c3ef39aad15644f075dbf3b7b13c254861ae376647b8adcbd4c899be4fc4617ba07dd2fa09466931ab68ec5e78d5be6691e744e0f23b8d497c4253fd2e6c1dbeedf360922e313b5c45df7815ef4d1ae930f820260c65cdab58c63e6dc9841cffec6de66bc0e752cbc9ccf04edd212c120188d72686235a6096571a5819d15a610a8b380ba5a54a19683f369c2b1a3dba5eea1ffd16368bef39aa20c78eb9ff10a51878e7660e7d61b891214a20bf8cf4454e5103174c3db66832d455f9b46e4dd3efdd31d997760a18533952a94ed94f547d27f10fa368ab2bb12024f6999452aa2c0217d3a1a3371b8d643b46c3e949a5da9e90b502b5a035b1850b57206ce1468abff980219e0d89f1f72348f91dab75e649d15ff94b2589171582027532cdde23afd9f8507920555ae08ffe459c825002881d1a52526cb91084ebcb6af0980eaf93a210446bf65e9f514becaea3a143af75c55eb2fe21f91f32db198b9d475108541c5f3c4bf4daa0953c7d22c9a595e908dc4056daed2d5a31197b4c1e9235e595175904085a5a34f2284148c4e899027107145ab17d76fd79ba1f4b495f2ff47ae7be45f211e21203ad137e0dd330cceba0c71b1030a2365db29c98ec6b5313cdd93cb3d22f5ffca3a3178fbc5ac7c6e5046595fbe124dd99a918c6898bba85a1c33716f3384d0a2b2a38750e9c2208cc817151a1ff0a833bdf42e5454496e699b724167a5bb29fc522072e6f5243b4785e8069338311140a6d17586141e36022891bb64222bf431bc8cb85932e2e7b6638512554a5ca04cbbff6119e5fef4845acc40f9fdd3ad433fd37b02fc19520af4e954bd130426037ac0f690541058ca461de68f10527a872857541e14bc27ee3200fcbe7d902620b519d2192f23822a9584401324ea8774387f676767f20db4591340740479f443dc4b30f06d3d4f89c22f005effffbf90ad6b89dc5bca94a40cdb080f09af085b3eafcae6949ddc4287fee38ccd18141b3af31bd439e7f4a4aa9ea4aa8e367524c3fa32ac2f7bc873ce89faa1d22bb8ecc83c4736329c52356db0c93f08917bbcf7472d6a446d69836c9a45114a58ebd5eab9909019861c3a6cc28ed174cedca423f7fb0c6aea5437735e25f7d75a7b9f153b25444321ef19b2a529f33cb9b3d31502a25f442a327d10896e4d82a16422159919860e9bc70473ea18992233531a4a43c32377b2e9e5e08e0d736a45a31290b79d6bdd2ec85d32994c2693c97cd8d985ba1c8fbc7eda7198c75ce633a7794dfee755ea3c3c3c3c3c539e30ba31335fcccaeeec44fb5a68dbe99b4e2693c964321f78dc3d74cfe97641de72975769f3f0f0f0f0c0ccec3879ba5d100c0683c160b096eb058b4d990f2deb59d3bad6b6bec9af542693c964b2e983dceda8b9cdd98d76abdd6cb7db0d779bb9dccc4d994c2693d9dd9d41a7d6bbc2d74ec35c2693c96432190a3c423e286bb66030180c0683d97b4fa7ff30dc99b4599bb679cb52ce9c7c6f180c0683c1e6cce572b9e9e3c7efec42dde74eb85bdf60b086350c0683d9d6759d5e0f0b633b53366f0fb3f6de130a067318909197836bfbc1775c9652f6cd4577593b428df7f4a1a3c69d3cf62df7dbd7bda7d37f18eeb8bbdc654328bb4beaa8e12e19923afea4b9002a2d9bafeeeeeeeeeeee5e5bb0f6ded3e979320f0fcf100f83f438597bb233b4451b020e598585c5e8042e0513bafac571c4a590b6cef6f3bcda252e25e151879436e1445030f3723876b2d79737e42079705d76a723fbe7d5d5786ba89e47dfc839b4caa9b83cb691071505150475729b3bf3f2ee5c65cb2ecda7eda8ee1e0e45fe57bfcffbbc2a5d4a4f7e5286f34789e54f3b52357f8854cd0f5b19ca7086f3471f4619f58310b9a9cb014a29a5ff90fee267174e4861ebc13d65f923c5cc24e5eec8f2478a264990e28812d02d05a501049212a238a354e5ce64f9130517a659144f6c7069b2fc894209d7975a6ca7c5adc992c7fb3133f2f8978c180daeb83259fec4a058716f963fb125695c9be54f2c8989764959fe3ca9a388274488f2248e376e28cb9f27b927382874de10acbd77c7144305323b1a5b92181eec66b02801d303988c0f94dc560c932398b2d460987c51a284c3dd26482421fd406752d2c468e6e4035dc942590108242335d1e2299d3969e942c7e8da4089501b31b00c26ec4b8485898f86dc2dd4c60a3c1a7e50b3465041a5c107df4f932e301a80764968504306858b862cae2a9658b2061d5a90dc2c96b8724bab240d6c706995cca3bd272872ae92d9da6fe6d4e3e709387eb8f15d5a698742a1329eb899ac5c2059fe3c9105e9cbfd2c7f9e6862c9fd91e58f13397c704fc8f2c7490f4c3f3f4ecc4072bf2c7f9c84616ae2d664f9e3444a49774d59fe387192e4d22c7f9cc0b293974d18e4c7895a4ca659fe38f1c51ce009bbd018778479c1bc1ce695e9ad8275569992e983bb5ed1d1e27c7203ad3071922928d302da26932e28d82553f096e9d7d9a0ac57230825d359af469817cc0b0a38a55728e411dc82009ec15aa6606e62f0dbe2cedcefb6112c23d3a02b7f94d47253981885f59881a5c79da979b251739b73ce894bd9b52449873dad74d8decdb3b95cb619cfb68414a9129a8b74d25c2ed9cc45fb62cef1949bc74459d605c21d4f28d434c552ad67ed673f8f55b1942a4bddad00a5cde8a395dbfbc76a94e5934a54b6e7a4c33665b047d48fb83e0841cf1766ee0b45ee9fd50383462d704484143d588fd609297edfde14b93f54a5a375176669b7965be7c17a7047cfe54a8282d7242215050fe64d9687e2415fb2bc3fc2b247dcef9fd60e726d30a83e480718c4c6f53c82418e6f271f2d2a406933ca336ad9ce39576865b0ca6a0573754a87f67b643d336fd403f3bc91472222513d4e3ab43f1a490936e14a3945645c2afaf9463ff6e4b2edc902fed873255b181d34a498d2b9cabe55e3fe20a58323db0f05ddd18f7ed4b1a15c5265df5e500ab9b9d823eba1b9187afb5dc5951d4e942cbcb64796edf7c87aa6d82a451d9b54d9aff5c12d72a593f5c19d0e0ea4d803eb81ed20dba72422b5c6499e5fc3a40646c324cfa781f590b9e6d72029f680792ff7602e7ea00edc815d606e352745d3cfafbaba9b320e7a1e2a09cad5a145bd3ab4ef514eaeb24f7272512e5412500c19da1f55d092e7ab9045aec0daab8003cdc8f6c72059f2fc2038e903fe24a96079fab0e5e9a396e7fba04951c7e6e3e60397424cfa90f2fc14643285590a5b48d5cbc5d1cff7117b1fb2aff9c933a5932afba23cbfa6267d64e6f8e5d410e4989223db0f1571c71ed80aadac6373b187c6eaa1b9d803e62afbd6f6d0462232ee47c6f59a444407d353d20ecdf2fe842f7a6059547aafc72cdb87c14264aeb27f85ccb2fd1eb03bf68065fb15ebd8b2092524d6c59de1e522cc5faf25e4968384dc7677eca165530561f06409b9d5205cb69e905cb65f71ae428d3d78ae5c0950bff43fb8e38e5bb630ae6c5bf44d22227a1119d766593245418285e82789c87c1d2badb2df361fe9d83ab4efae63a543fb924424a543a1891e5baf685ff4aaf4f67becf754e995e8e7db4fe94a5884c15aaf7accc400cd20d972dbec220a4e6fba9068ea6a29a59c520a01ab782c8da8bb93403c9fd44373f79f34e9237afb601772d56316a265fb52ae7acc4086760c7561c9b8a289c1ef31eb55bd22c37efb3d66d06ffd4aee1f512edd6d24c2e0c74044b63fa25cd9fe8ca1956d4f2ddb1f53ba6c7b68ae4281071257f43a3617476f472d428d3ab76c7f0cb3d5b1b9cae2e08e3ab66c7b68d99e51eb0f48108cfcfe91479994411f7db89465afa8d3af14a4c3faa60cfaa87577943acf1b70b1fdeb07d12bff418aa154d5af790c37d0ab2fd79c2324d737d2a355d5a686d6863db8467af58957be75b1fffbeb46b0beff9ec7c510eb7bafca104c1138b678a80d745847a983d9512b8e00703e0f6aa7c3daa43f310fdee9b0513caa1dc9717950435cbcd4e9f4665357d35a6fe890ea402938bbe34ea674878787a743fa3b1dd239e5536a45dea6fd6967e87752c2ad21db29bb6fa836a9337f480d1dd20f157143ba11a5b77bd44398a767ea119ddedd2d8b78018233b2c7cca97bc85aadb5d65a6badf50b389496242e973c1f05023c2367133ab818d2a324ee2b8fdedf40e99567d24c07460e6c400319c0c0052c50010a4c40021180c0031cd000061859800212c003027638800e0628808c0070b8a1880d446aa0819587cc20430c30bce0d3c323ae5c6841480e0160616705157482e4e0a8c2d40d006c5240e1077207209bd16ab61b2ea7db050569b9f8819f1fc4e5e20f3fff06d6ab2fcfbf89499ffaf3a50f262ff8602257981ff3e00e4c80a7c1a53fe11d5f83691e057cfa1370cdf7c0283c0f7cc2ffc03dde07e6f103c03efee21f5f71297f0023b93e3c78f10f163847178f582416e8a38b4bac1f30921a2bedc2fc1fde075c63eb70d654e970be8905c40990180a084c087704024362840f20780f3c09f7f0239ef90e60d26f008fcf01dc81cf00dec06b0073e02f8033f018c01af80ae00bbc0530067e02b8024f016c818f009ec04b0053e01f8023f010c012f806e007bc033004de086ec033003be015808dfc0230039e07ac804f005ec0ef8079781326c0230027e075c03bfc013002be00588737003ec00b0017e065b001fe062c80c701cbbc0df8862f8271f81ab00d4f0417f90fe0d2b3700d4f0326f24330eb33a6e165c0437e069c1f062cc3c78067781f0cc3bf8063781eecf33df8855f619e1771cfb78057ef02163f07dcc20bc12e3c0b38870f0016f22b60167e0707e075f00aaf02def91cacf341b00aafc2398f83837c0aab3ec4380f009cfa1b1c7e0a18006f836f1ee3141e856d1e08c6ff18f503c040fee2ff8a47b9e3b82310580602031273f1fe7c20b25e799eaff300aee1d22eccef01d76ce99a1a102030204e6e6e3af0f36f662e8e3fff86e622077efe4dcdc50dfcfc1b9b8b1af8f937371733f0f36f702e62e0e7dfe45cbcc0cfbfd1b968819f7fb373b1023fff26c8450afcfc54cbc509fcfc94cb4509fcfcd4cbc508fcfc14cc4508fcfc54ccc507fcfc94cc4507fcfcd4ccc506fcfc14cd4506fcfc54cd45233f3f65737101af809f9fc2b998809f9fcab9c8c3cf4fe95c44c0cf4fed5cdce1e7a7825c3cc0cf0f5b2eeaf0f343978b06f8f9e1cbc502fcfc10e6a2cccf0f63a12c9c85612db485b71017e6425db80b552d95cbc5185430554c2553cd54aa9acaa6baa970aa9c8b39a090787f8465e5f0f3553ad5ce4516705a382e9c170e0c278623c399e1d0706a38369c1b0e0e2787a3c3d9e104e5b4725c39af1c584e2c479633cba1e5d4726c39b71c9c8b04f8f93939174b3fabe96bbd5f77e0519ff4475860f5723f1ee89350428049d8d558f4411286d9c104c5b4625c31af18584c2c4616338ba1c5d4626c31b7185c4c2e4617b38b099269c9b8645e323099988c4c66264393a9c9d8646e3238999c8c4e66e72a99a099d68c6be635039b89cdc8666633b499da8c6de636839bc9cde866763341342d1a17cd8b064613a391d1cc68685287a64663a3b9d1e06872343a9a1d4dd08ed60ed78ed70ed88ed80ed98ed90eda8eda0edb8edb0edc8edc0edd8edd8ea09a568dabe65503ab89d5c86a663539d8975248e869ba902e7838ca007e06df7400df8cf88603f86603f84603f82603f80603f8e602f82667017ca3ab00bed95100df044d00a75a12c029570470ea05019c7a004ec51c8053b206e0d48c013845338253b505e0944d0138754b004ef180533904e0946e079cda1d00a78274c061cb0038741500872f993016cac25918d6425b780b71612ed4853b554be552bd5430554c2553cd54aa9acaa6baa9702a9d6aa70ac269e1b8705e38309c188e0c678643c3a9e1d8706e38389c1c8e0e67871394d3ca71e5bc726039b11c59ce2c879653cbb1e5dc5c359f0038a78491c8bc1143b458a5cf01bb2c6b002855ce55f349a81bd8934bfaf95eccc5999f343f77fc7c8fe662cdcff76aeed95c3ce1270a3fdfc3b9c8e3e77b39177bfc7c4fe7a28f9fefed5cfcf1f3bd201781fcfcafe5e2fffccfe522fe89faf91fccc5147efe1773d1e6e77f321701f0f3e6e77ff3bf9a8be1cfff6c2eaa5cc4f9f91fcec59c9fffe55c0cf2f33f9d8b3a3fffdbb9a8c2cfff825c5ce1e7db968b3b3fdfba5c64e167007ebe85b928e4e7db988b2dfc7c2b73d1859f6f672eae7ebea5b928fe7c5b7391e7e75b9b8b3d3fdfde5cf4f9f916e7e20b3f61f8f956e7620c3fdfee5c94e1e7db201767f8f9a1968b437ee69f1f7ab9c8faf921988b34fcfc50ccc51a7e7e48e622919f1f9ab968c3cf0fd15c2cf237fcfc90cd451c7e7ee8e6a2007e7e08e7a20a2700acc201ab6ec0aa225865035611c1aa1ab08a06ac626155c6aa215835030e65c0610c388401872fe0d007873d38e4c1a188c3150e5dc0610b381482c300e0f0e5aaf92ce07007872be05490abe6ab80533b57cdd7c1295d2a978353381c9cbaa970ca16e2542d8553b41b9c9a0100a7643638154b01a760289c7a619c723d4eb580e09ba01ff866e703dfe87ae09b1c0f7c834301dfdc4ec037367c5373d5fc1a7c4373d5fc1df866762373d5fc197c13c32739eef7e30d8c86265998303aafe5aaf9177b2e3cd2d880e60637518c415e3014cb1d5138d4965e8d7e3e4ae722f8a82d12a5e57a1e51b8d183d686ee69f4de461b41b8a49c9dabdab8b2bb0475787595744d25d2cc5d3a9c15a583e142da41f414c58307faa427a184a07f8405f6106efdd18385e1a9cbf59cb3738a25eb06e62aca830ba373d1f4d3ea5c34a14ad692ee95aefac19579bc81d129bd54bf256279b01824eefcd46da66c79a66a79a66879a666614a96e7dfc0e6dfc46e64a73cdec06a79ded86e6eb89bdc8d2ecfdc986aa55c797eea95670a36a66237b0b7a8f1f4a13c144bb2bc2ceecf468df70626c64810987da44105d040c992b4081c56bef97dfdf5aca1e55518b53483565cff3a75d7bfca4b51edee3e697737955dd762d2f7f75a8bebb2f6942ee700a9df0eed9c338daf7e9ff779f5091bde676db5f5ab8166cf39b4ea6b97cdd3abb17167187a350e91e77b4fe994520ae43111850a681369ef69ce50690c22ff10449eb69546ac851d77777777b79f37de1ce604a388d4f117d9e08e1237810bf46a946514913eb2768c52598f5a612c51df9bddb37fceef7b4ed427f3aad44285ebac8b23a9ea9752d53da4aaab54f5d7531e81c873ce1eb594e07d9d9f67708e7a8e47269dd9a67ab659f9a3cfc2d0ab8ab259449979b9ae760bb24a9f518bf280d6507368156aa4304c7ac42c484877765a98400f304c99159e1b2effa7d3bd246f34fdf87e0ce5399264982eef6d8df4f9648c9875b0beb7fe6dca5457c79042fa43b8fe4817fa6370e58114d26fc2f58a74a11fec9ad451f5d71ab85c9495964afd9f4ef7ba6aadf53f2c5932d7e115493fde8c3c59de62186964f8660cae677db6468aa6970f44fa945ebe0ad267f4f257902f802f1f46ca107af96047213d88a3981e065735a4aaffe25a86659148df26d3f75b6c2baedaaa19f2053006d1d72c5286efdbd612ae385c6baeea1f992157e8ffa41422428d958ddc5fb34817fa43d875bf97a38a05c7b50cf9a3a6c6fcae5e680a482bb5d67ed466618300cd81e40b142db0020913962d9525b21d830a0bb2458d9f57691431a2d0ac98ad56ab1563cd25cc10421b79ca960300923d5785962c7faab0920390e54f15b07c7231480bd9a3c8b2fc8902cb41b2fca9824bb6fe3d76007e8f0e3d7c0f7bd5d9bfef2a48af648f402db7c749ea5cb96318eeb8e8b19c277b08d3e48ed1643647152315bc7b931638e13e671336cfea592dc62f269b48bed9160fa7a557f6bf776b9f087d5dee2873222ab8f4ca82535891674322cf8648366443d2a3e38e9fec937d242c94c0d2abd1d379b75e7dbdf24c5442f66fab61479fce3d34a259443217698f445946232a6594eb9e6e14c2c19ca823ae4fff66b5c71bb526cc39e79c13f7d1be2f7a45df7be26616e9e3bddb5dae2387c0435554cde18ac268bf5a6b015dd2506e4f082ea77d8f55e9cc77c20996b4837495574f27dfc1864221d20e9f676d91ce53a512917698b94945fcfb3f92251599b98b8448fd6e3f51ff90fec8f0e2f01ef470088a1429d2d3e9b42e62c103fd7e9410588c51fa15308b40e58c567fa76008ff7e8a1ac27553d4c17254ab51d287f059817e149aa02eba33db9035dc50438de7d4a3571e24efd89115b96133bf356aedaeddddeddded8d47d7f5405b07b509724e77f7a6fedf97db8f729fb9992505cd90521441cff3bccaa29e575151a87c57b51b598e7125c985e2d62cc7b8f2921d8654d024dc45291d35ce166900a64cdf310968a6a897d25d6299250b5744717aefbdf3b02f91b82cab7ee8297659d6103c748b553f848fe0615112d0fc3d0f1c662c7f28478d43f0b0281de8cbccc365d51fd2d6a27e30e50f15a5fe10f756ae185f17dcf933bb8a7e61ead93d3b07e009bb3b5f9a1c25ab80b78a3b5fc23c516bad158a4ac52ccb1f2aaa6416b2fc813245ce916ab2481151067352c7bfc6ee38a28da08b965d944197d4f13f81c91d6bcd55abb968933e9ecd6314fab2da50fdf33decd8492f35407c6ab7a152d89400a5cde8d3addf54e081d4dacf7e74092a128944a21015894234446bfde93f43d45a2ab47608457114efbfefede751a942e9676bb2c6339b8573f1fbe9bb3cdf6be867e88fb02cfba86f473f9d7f1606d265ed0bec922f4887bd6c59206190fb9bbe245522454b73c2cebae62fc8451a85ca207790c9e4bdd377d1d36c5ddeaedc31d9fb40a6378b3c5b9788041e09076a5d9ef7fe601db1408aa16f18849614e543de57aaa809a25051beb7f643f6f4a1ae93a2d8495f61dcf9b656e9a706101010101010101010101010101010101010101010101010101010101010101010101090f402b4b85ad09ce93a86f397c5d582e64cd7319cc5d582e64cd7318bab05cd99cee2eefc4a83e6cce26a411657b338f7daa99453b6a88b3b7f1ce5f9f473cfd32bc3658bb6b83b2e22b3e7ea9c73cef9d5b12da35639674f5295b2c4ccdd43b9d7c49b0ca7ee947a95fa74ce399bcc68129fdfdce66af77677cfe61f0e7c51979e29961c029a3de75b5ce73a77d444e1a3d1c2b8a5d9b9dbe5c8ce7147cff99616bf2f3e5a15977e34d92b09bea64d29886decf19afd87e112d42379dfe8036f23dc4cae87ce66170406520eeeb4212ca2db6d742514f2f95e026ba7d7c688da55a886a2ece121231e1e30943d0c929cb443b32a2a7455ff2c824884127d4814923693be5cc25ea6d8e699dcf9e3d74c29a57f975c9a298e52df4a1ac447c2c1668aa237c0b8e0fa8d89a1d4db724769936188e6d07c49ed770b624844a8cccc68e615dcf9437ae605773e4823a50efdd0e8f33046bdf24e61dda255fdfdf2d3c9f642cd598f393b9dfaa52dd5daa464328939dc30d361bfbc235b4eea80445c98965a5c10464a1d225c5b4cb74aecd768e6d5a857ee18dcf963bf644cd7243285ace9f3aa899abc27067af7a308a037618b0cb2c194ddab054d5d3665b97bf9198186280d516a237b62e6d4e304e505c5883cde8f6389d91233e9335ffebf12e035e5dd75dd2c42305ac19d2fb790d00832da418e8f4868e0ec407503ec85467377adfd3c1115151ccd2d28eccea772e7d39cd45bdc39bbf3b59879d2e0ced7e2ceb757aecc13888bf1c3d6608713773e8d048fbc3e372491d9eeeea0112211cd4a1932c2a47934bbf3bb24c6740965764fb97742911e4c115d1579a33adc30111c848b07902aff2c2d9c8374903afed2618ae8b6d8d91940b772b5615c1fd5ae7f1797b66859fea901965eda8c62ec8d7be3c48388f9989fb346f150b5561cf332085407be97192c61b984266fb4401f7da871e6cf0380a7823bdec07272d207e6a718329c5f829772958257e5a44fe84df03730174935b5be5223864ea73e0c0ed530db3f41bd4def4b8047cf29408d396ddcc0546d9c20967ba8b95c670ec2dfb8d53fc649abe65b0fe649b9df5e2c4fcf831de1bee7a47be5550fe6a14e61af6c52800597023cce9c022c8debd06665f6c85f8eea09fa4152e9de4250ca258ca434c483a552a9844bac8b91a0b6dc1f02a37226968f960fd7288742fc7c29c407f12878140e85f36a1ee93dcff49ef779a437c5b4eededc4ce9952de2f97b1f2ee9e3fd54e259042fe5ca87081e88e7e1923e3040bea606cef7c1043f0281c1ec606a7c28f1f122c18f3e98a8725207082c97c23c83f8d0c55209be7402130683f0a8353d2c7784b1c1d85c8491d92b9b0f5345fa68b929f8114696022c730c1e3f21f088a223cf47011eef09b009b00f78a4e9a2a68b3c3f081c2a0126011edd8c11e0d102814737238b008f3e92c8f34380c72b6f60d2c7632275e6535470e4f963dd192128cbba843c671d99a246af956d8714e88e36d98654a473458d3d545ca6289b865371efa9867b09dd4be85edc8bcd1c33d419ea68e45e76dccb8e7bbbbbfba44dbfb8bbf717f7767777f7eea65e66664e27f7efd55fc2fe12366aec9f030618dc0bcebde0be7ce92ffdc5bdb8979d1df712ba9710497f9948d498a1ce504fa7fe323333877b712fd4d5a5cb171e9e2a55be787b4ffbb9173b87cfe901f1bc4c9f5e735fdccb176f8ed148ee6c6ce6701e94a280a0d7c33c8ab8fba4737ee9ef35d3ab0944671cbe3aeaa8e34b7654940f35c474fdfc021e71a7bf24f5cfef80cf892b0388eaeed5bfd2ea40483ab22ed7616eea66aeb10f1dcea62332ee0e0b534d5ef78b1a6bfdecc9518757aeccfffd4e0bfee122ba2f4844bce9cd9e4386f3aba38e3a7277a114251f089be7f775cfeeeeee6e4a29a59436a5b205bbc3d3823d8532db5e755bef15fddbe3bb1f0a972b6e08010d2678e1c49589c20230a8dc501164703f2d5f5c181c989e5cd18f898b6ba36071434dc8a628c15bd385c3e13cdc8713e162b8184e14ab7db19a17abc59888bd8c18d1010e57c3c56ab1346239dd2ea8355d2f23467480c3d570b15a2c8d9848aa6a2501a5b84abdefc33a8c6e50f2e121b5569c87e2318212254a2a6e61a7c94eadb556146dc27bead55a8dbc94d45a714658a04409858112fae13cdc17ab79b15aec27760aeff78fd75689b9680417c3c570b1106ad68218b16008d79c3ddc5255ff13e51a9261b50fd2ef01ad6a2aae4df5bcf69495662a3ea08db8e084abe162b5581131ef5a12ce4811703b1ccec8035aecd887f3705face6c56a3119c428fd8165fedb8441ec67edc7c6a559fe4859c293cbe4867191b8a22c7fa41431830b2ee1881b2a8292fb692959b9303870c18d910112778e8b834bc31da73a00472cb8f33bc80d87926ad891f03433a79ccc29174383b93a9335a344f22f738bd78dc05910ef065e914904c57eded719dca65650f149e5e2baa9c8af4fe60caecd72e78f9766462606e69a4aa41d2577f72a3fe69cbda33aa1f13340108928b41b6171e7b72495ae4eca1c0d33e5721096b872061158f261d8b8738c7c44c604156b2801ca195c9a608db595cb53bef1c50e808c90460ca4708235bfba7a35ab649a3f10e60e7cdf3f56589e5f5fbdfa7eda97a2affffd10af3d84b956d787aaad0e47605c9adb62ca2257d48b172db9df64c584a557d24473b1b65aa6da686ab998c3146bd0aee8dc70c10b58fd2657afa618b9dff4723186d81b4db480ca132ee660f59b60bd9a61e47e53cc459d195071e5870948a0e580d56f92f56afee47ed3acdfb445afa817b9a26698414790f409c2a5457abb75c9fd259c8b3497cbe574b9bf14735192410329b6d04113ac90a3c5ea2fc97a559ab978840836722e183071c51522b0fa4bb45ecd1be4fe52cdc51f5e40b146197070d102c7ea2fd97a35c1c8fda55b7f094b89895c51269449c949ee2fc1a44f4eee2f256991c696643a6b955cb99f5473512e418a0b7448d088000614564f1be47e128e9473f1c89337bc8842441c51c85aac7e92ae5773d74fea42ea42ae3c8e38485fe47e124dfae0e47ed294167de7bb2d5ca42dda22c5684b96fb473917e510ac580389d7cd160513acfe91ae57b306b97fb47351ded4a06209601801664b83d53f0aead56cb928791054f3a20c19184fdc80d54f72f56a7691fb49af7ed2925ed1e4fe5116b9f22ebac8b37f84933e2a2b2d7aad4673d17d54f39117b9f22864383f0a3a723fa8a54597c9b6e4fe51cbc5500b5cb003b2620e3798c0ea1fb97a3561a3988b2658e3270d2dc40882d202564f1ae4fed1ac65ba7009b313e124f7834ce4ca891005b9e82e778132ac7ef0b6c44693f50a267d70ee0793804b7a25b97409923e3f721f69e97220fb424362055258fd2157af2617b915904497a02b9c34a94114567f08d6ab4925f787622e4a2db015d48008276eb4b102567f48d6abd0ac57a12d5a0459fd212b212c38e90324f78774bd92479280316b82134ada782388d51f0aead59c41ee17b55c3cc5f1b30336ae14a189285cbd9a5abc8ac8fda2252d8a58fd229188267d3ef78b6a2e1ec1a2084a2c418422b0409105ab5f64ebd5cce2884b8a969d132b3478e209ab5f84eb9528e7e21118cc600b1359240108510481d52fd2f56aca20f78b765292e47e5197d6e9723a5deeb749ec925ec1a4cf29b7fc29c30744ecb8c8c93105abdfca7a356390bb0449d0a0624a115fb0a185d56f69bd9a58e4de61cacf1a4ac8c031e10a56bfb5f56a4ec9fdf6d66fb1f4ed665b53be2d68d2a7c62677fd5f97a6d1be58bf6745e2a4cf0e9d17e4fa5efddf925ed1dc1d8bd52ebd92d28726f77b2d17354007161a152f8e304612d694925b6ad9428b561b4e56f00a02abe7151207b422d8584309599a90c26a2b72bf376b9c8bed6a572e49af5e2d5e98f499c9fd35e6a20c6a2245125328010d27b660f55759afeacc458986115c5ad0822f4660a18535a3e43e2181055bba88011b54ae607515b9bfde7ad55fb1f40a26f7dcb5284dfa80b99fd65c04a28ace0b32d6b0b2240bab2715b89c8b0220c28a2ba070d1e00a3158134aeea7bb7eda854e6971dab6c83d71381aa338e923cafdaef39d8b3b4138620a2753a03c0962f57b50afe614b99fbefae9925ec5e47eb7d2e28cb96a7e0c4bee779a8bb3367acb4511e8ce98f2841c4d5a6cb0faddd5ab2945eef7978b478818021522b87031060d9ab0561e73512a210c2f8890e40d396ee004ab672c77dbdcdf5ee46a263193c8b3bfb974975e49e9e3b9e50d0baebfb5f48a94fb9b895c31406671d2495a9c80abe6e7964c1f661772d5d393e70a6d88318435809240842bacf904a7dbf5cf2ebd9ad2a265354dfaccdc3017873040ae42329cdf6dea09858b12085196c87104952b47b082d54f481d113ce002466e087170c19a4e8e4ca1828b2e577ec0a58a0d58d71f046154010589131dd4aa38918d80841722b491851b4a58fda75ecd26475e008436befc8c81cbe207acfeb0573b5eee1f2971c7daa2b732ee48733496e78fd0b8239de5f9a4332e898c3bd2569e3fbaddd175704960dcd16f107ae386ba986fbdb81506776c1798eb17f9e6fa36619f7ad49752b210319af820f76417c309180020674a2b89ca1da7ed7a19779cad2baa63d6d64ca382b5d6fa3cf575186b6b35abab15e2c10d61b9a121dc297724e5ca5cb65122c50d1a2be8f08a305d01041a4546ca126811ae6822e4058d1434649a907e447068c9f2a99471efee3e3267b7dabdbbfb6773014a1b71b4d953019bcaa8040c988d82d22944230200000000002316000028140c880482a11c88f244cef614000d6eaa52684c1909444190c3300ac320820c018010420c3004008490b1a1150095461d1784c54f9113ec7a8875519490d647a725b5fe24854080e2c5bf32d989b1b55c8f5be6e7197e00c6a0dcdab23b78b8767d9db2d7452566d9baeb7f6e4af58e4b3f6e7affcc7b6e538b3289507ec83218329330584bedcb649141aaa3468deeb4d6eb2dd93b8a705767080ff9c8c09371bf6a68d4fad4153274eaac39e88acd798a620dc244a322d62d5a7e44a6476ab22275394abfbd559f13f9cc5c5d41e71baeb696dfe7473f02a4f102442d1617c54ed5ca357777d3b3fff8b71e0d5b103946df85a0e719c240f4a53f8eff3ec0ade0e334f4470e6fe03f5731732b1e37affa2a57848f5028752fe999aa2569918a68536bf1da77e5e47db9707c2dee6f9b02e896b2c82b8bb505102fef76b25a546fcfadb56c3d4d3202a12557ed9f1a95ac8febbcbfd10ddecaee82a579bc00ee2127b35b505cbbfb6d79e2d0ee78c5c3fd17d61cb8833456344bb9361d3c3850cd84abaa90fb932b7541f019f13b08103bb5322555b9e8636f8989c0ec5cf95d708a828640a3e374206f7f48b43a2ecf389ef262d028bea1fad92c2dd69172c70640a97acb42b403f86603701d81ae5c0ae395ccea77f51c8193c15ec9f3aa511000f4bb9644b7c26359da78169bff2f4955698f50f2ee983e0f64bbc6f308a0fe973e8b8da4f4f0901af254a7bb8fe38d6b90bbf387493f155adf2224c17f7b559be0ea3f7610dbff5ec66ab0b7d20e7f95569600750a9d7a88f0d3f98a329ecc5603b03677920d4638f36bc891225bbf8902406fabaefe69e8dbdff82bb67a01a236000242f02fd00520d55380a19bf808ecca1e3c4b0a99e66cac491f5b9b90cbbcf227a306d1152597ee9fe16326ec672f90a47318fe8802b02b5c8ea47602b9864c69eb762bde4391aed976a9b4bd8134ad5d48bc2a6160f0accac6665adbdf0eae1f559669adbf436a1752022f90b668a86292027e9ccf41f9d88a26de3451b220e7baafd5b36e116cfde3f53d6b57c5baeb185f2997c8d3905b2c0d830e9b302998a4d098b58b9e194b6b5e007a65448e2498fd19664203502fb8dfc1046558a80c90457a3929cd589afb0f80b7edc2f17f301d2957d6c8ba5f522a7b2c18d84408c30df9900b696380cc2458fd1774fb7bb28290bccd05da24bbb9c61ec8522591a3997a7e9b40c8c1aae8f4e9ed4f443559aacc4fc9087f4d744e8bb3450e4fb929f5ecc418553e75754d3c9ebced82611c5870607cc5c9ec0d04bd49e8534649cef38d2399987129a3de8659d76da8aaa93570c38c670054d5ef3fc3ab36c7fd720b6d67d6d42a3f2dd04c72b075f3d14ecba85db6431b4d71fc0b6e7119148c7ad088fdf08a19cf28b9408521ba764512cb3f3b1817480e38c981813cbd5853d3f215dc7995899ba479942532af8cb17e3fb808cc238edbfba1d4beef89c20237be5bf6fcdf1ef0249caa7014e1edf37f83f9b05ca840437c93e2eb728edfb4d4b2da6c19bb3285d696c1e7f92d9b39b541c74d74da763e720cc648b5600b62458781161ba442474dccb4440b0023f9087abadb3e980db86e2d4fcd04ffe9db7c4a01d2dc79f74d7f3f2ce26c8c12d134d0edad9a1241feea3663ca58e938e4455f3ada4b0d161d63036e4ea06c5c2f8c130ae6789e9b5223a64d47f7c62e127f4446c80df80af81dcb0a3b28214c52b165c137446a6ce39f626e837c90796560c2e531a1b64c9d27fccef6ba80235654682af6c77c229d43976835f4cf6fa22b922cd04061c92b1ad72c3ea6741b04e7b04e8fc862c932172a1664ccab6abc7913ae187b05abb1c6d3580476bd81045c4e57b0fd5dbb1c574433d9917681acc69eddeca77567dbabcdd64821d5cf4543178c520e9738304f6dd789df2ba42c19b97b52335dda23bee2dc0b65a38a7c45ca04deda49a881f117676aeb81f5c5d2575117b21ea0fa98d6765f8dab59b2862cd169f5eb67f3ab10ec224c13ee36c4da700a4b62b38a76088df15759240b69be747bfdffd8f1f4d1315bd8145791171d319b145453c23634001c67db91072c32657dea9845d34035504c6e05d4b072820aaeda6cd1f28644201d02b903d6672f7c449292d39c692c81d6f01cb0d67b8735a8f4bbad4b271dc5d32deb42934ec88520b3a863a6835471c899e106d0018bf7c483759bd4f3641dcaa0b6237567c1ca4ae6512acca97b6bfd7a37587ce922aac2f1d444caec917d9d4519a61d61e2c6b94db480c150a3fcb752aa6f269af84d6977e3f0ea301299b488d8302e91cbbbf3f739daa0735633cb9d0dc7e8c49d3d371338f2aa9f50d94247c6ff023239032d724155161d30b3a5bddb4c3bc1fb276006277bafb07274742f94bb5ec1fb5a9b25112a611862c0a7a0c9de94901a2ce704d28fa6827b9459ac1000a7e16b941c5ff1b764168d67b69b25489e6bdc9f4d95142c72851d04b5991006d0429d471c08f3941f73e18d61a528adfc25cf5037551b9f65c292228be2760f88190c8124f1220719768127e5c97742d3267b54bc027e2c70c1ccefca02d6ae36326fed84d86bafb1abda1ac962b6fdd8b83eea700255055c2ad117bdc38894ac84284587ae03303cd4730fb2813ba47ee26e0892689d8ec3c356266a57398603fc023b9115b90c4d1fd715aed67dffaa42b7ff4af3434e888bd7152d1f07f863effbd62fb9559985243c710f0dc3658fb83c81b69b754e19f92a4026c1256f63dc6a6714687b674ff7f7d77d6739ec0619996f7244ecad41de8a05448f65f0b6ad46c93080475c7868d11eb1357d3c10104e083bf51de25ab38a56dc769d29b8434a704d06db161699c5f34bcae894e5666a96d96156db4c6a166fadd0e0e034b9bacfe75956bea9cba533826cda777f7e6cf0fe5da1e25e2b08c2104a9de497a9557b5fd3b5d56b3ace9e4dd8641703da4388d2cf53852f9ec686b4afe790167a4c5784116268486ce3026d1872ca9af8f0a5b83a38db7e4bdf7133506e36765b7f5d01917780126ad9eb17e500d33d2c86252d8f551c3cb443582a5d855ab6f0414e41d8af2e5c87c195b87e144408e7dab1c40a537396cf550382744b8cd0bc1a9fda5c1f5d8b648d9b0334051823a1b79882565ce6923cb2a6bfaad804ae364f7d7124536c9d79c2dddaafbf0df0866326702dd3864e5863d249daebf6d885a723892b8e2216b3dda02b65e388f46896f4c6a9c57e3282492af664be07344342936740fc6c5f445508c78d4c11c6f8cbde619bbe3977c00c47ab3c4b89690eedb6fc50ce3d61c022a6933477af2c3a7db009e833a347efbe301c2f8ef3c0448bb260328917e4e82eb23278a649970d93a6ccf125e98fe4f03b31b4f2c1345733c2c215faa50a148fe08ec4219d16919b5f6089dea11c3c8a3d0403a572c5f971d34cf51edd8a9e4ce534fd0c0e50864cde2907d4f36f645a94ddee0fe0b8f58085f2b71db809ae614057bf99fa2a78c962c47126189a188ab939187b3bddd9132ec49038b1b4454ebf548bcb7ab1145c0e2a994038068d5433bcd706e026ece69e4f3cf0dfa9bff6efaf9514c51fecc978d7f40dea26e2178e13c1b707864c69d2a2c41e0f85faf50e03fd6987ff721db5440b0234d48f35cef0e1a87e6862100b7f48728c890d8b92d4bf38426f867394a7f2cc99f006cef5c34791fbbe3043e9f360b208a2ac51d9dbf99af3a36308138ec1268d354f744799c1fe251d785ed60b94b642e878138c95a10fd2c3e5baf071b50328be6c27540ed0d3e3a23d703e2d4da225530e4bc503ea5ece935a2e7e4150f264ec85225d40a3355cf0fef08e7e93511c0761eef5468d0da5047a5cd1a98fb4af68b4afaee6eca8b5dd65936542c852296fd3df4dd5961cfcc38f3bae25efb16d5a24fcaca29814a1d87a926923c187d80f7d2bfdf84169115f53d45787b0a8d558f165b4535d33ee5d06d905a087bc48426e8c70c0cdea6314c82454dea1ba35cde773e85ccae6db733b50cd580bfcd288a4c4a0e8e091294c8582c0d87ad24538b620f07f789fb581ec90a9fb6cc3c632264ec36d6b0320ae27cb93d3265ab0f65603d9b4984cabfc622acf838c87167186feb985c6a7e0ac60c2541694768c8514691afebb5f0a70f30923617a45237a8b3471bcac26564d3b452d776dad4a60ea50f7d4d20433c0c4fc024e6e308914609b15956120bbc5205d2e509de206be4f2209ccea27ab4238cea9ab61a53ee477c9e1f6e58401bb5062c442d02935f403dc34a144ef7bdb690fc59fbf4df50c0846c395f271f7789881d88e9226127977b0b18de00f792b0da71137026b01877c76121fe2c04f8dff9f4fe417c670b8163e06e48b88627becd31caead49fd5c36800e2735a053ef162607b839c1b87ca2d150ec8616a0fd03f509ee0c46ec5d3f74c89f88b185efa717aba9c4bddbbe19600e17c9a35067495195336ed37c9e925a6c3dd1140d51fc93cf2bd5aa509dccfb7d48a9494495e2b8062552864ada787ab21e78b986b21dc9118db1a66be4b18bf762ce19633b06b6243cf1e9926a7e08dd1db14b53e332ba99d53bec7a04829c4d382800c2c991599e3d84680971f5fc73812842ee4e91dafaaa7066c7d5db620a3674c68b34fa5638cea1b745e001d960d13515cc7704aa7a90a4c7b6e130fee53fd17c612f7e29322bf0edcde157765b902aa6678e40a1ff0e2ea198ff0310ca78cf58358a826d5f362350bb9fc7cedc8663b7e8b2a08d945260fccea9f61dc7d0e07931f89d0fbb2575d8759ed27c172d76863cb473af4fdfba96f64e35f6de19e97b6eddb6217290ef341b922aa706514ff5d3c97ceb087e093453cac31a97ed86dd5a8f230d1d4a9f4dbe2aa6d22973726433270b57537198ce9a8f28a09e2fb54bf25f4b8d0830a5835cd9541b74b84bf7386c5b6fca7ca27021f82ec4d07d7d514dad64f90b9e9a4a05587d8a94ce2d25e646527db7f0229216671463244eefae386adaae40f08f134226a58aef92cfc7538a47be58c067fd618066f45ddbca7397d783aa213d8a266f5b6f6b70ac07f3653335f6d9c968691311dd19b31d5acb985e415b63aadc101765dd4211b07de73729d426ee9cee4c5b28535ddfda154129532ddfeb1037873b389f86719f6f7ea614de9014211d405a4c9a808b050329747511c089cdd10ddb986706264611af73dd6cd7784d8e651aa8c88e701274b61b2a6ce24ae4ad2021ab7c51c48558cd7bb744250a8bbc40a3935d155585f43faa2032ab60966ea6c4a16cda4ac1dbaa8c681741d372c8f0aa000efc7b4d9a5f3447aaaf708876ff1b2c77c58fa41d820cd77f700e762470d0ecc32348710eb50c5a8249be1bd77659dc9bf3aac0ffd3ba3b00a37a2b0337557ce8704bf59c7aec81ef5892d7c41f7e641f8a2738c10e08af99a961303873b8476490ea9ab0ae15b9952eb58d3052b0ac353b3f6149a9392ae91c3a48c7ba00188f4b090f9f2d7d294a021c907424b129e7721cf38353805bf833f740eb4713e5f110b3687eb685c72de0fc892562a2222010a69b1f3753afbcace8a638d71c3137144857ab11b233ae22d63873f5535e4477be0b8ea21e5868f8f66e7604d96fede72708727fa3b3612929341e25230983f5c79dc27ba2fb4c182c6fe30bbea7b45a701115def3d2ac79c5886d6571c58d2f72d4ce38b1f12ba58aa36aae553b0ea82473a7dec7d8746322fb5efce1a00f575fd5c4a3900302204d406f45c2cc13ca1561a3cf268636ab3ee616f19f1167a2ed001bb5a784ecf8bf20a648996ca1a6b1ad60791f149b0eaa9e1c575b31a886a2a6c1fc5d63c7deaa534f7e8c23660d671efee1d51bb48b2cb61542cd5dc70f9bd901d0fed7e251531e2292f8e1698482c19fa0e25b8a41838ba3bb756800579898f906a24397f53040a89c020e5fd7b17dd943dc8a8691af7985d914718d3a9445013f8cabca372cd346a48fc9362f1b063e020c14188a7df9b4ace0bc3e3e4e9eb083fa68987289380df8e15dc337912029d97a41360ce71a88bcdcbd8706330df0b72b0d1cdcfdcc4627b2733cf96b9baee447e598c587dbf6378a52e455e7d9e1f0af4c0a99af5e88dd46205116f4d33e99f3ca902b5c84f01cb7a8dc4987d3f8a9cc9a2175726ed9f31f7474086582eb7bc3f18ac554b79f95b4b217ac17017cdacc91e4026ca5e2381c768f8225881ab4b290c8f3aea64da61145ee8ae2da9d3f19f484f39f2d3490adc3abed1a605ffda23cf0bd1a90e98bef75c9eb65b6f84c8a9a4596eca6ad079f4d336cd82f80b7fddecfd5665091112d9cc84c3ea5787c112aefd56ffeec27e972bd115d02df7a3e46111b26b0b635f0c5c17347f8f45ff8f46ca90af93f9da8aa9c155f6bbf15f53fb1975ea716028f617fdb728838fec4a3815825f24e5d7ebfabce8681664d4caa1f660380af237ddf3365dae223351eef8390b9b468816b2cb268fe5060dbccf1ac65f4a5aeaa8fb232a091e40219d7567bcb0ab1ddd547b42f768937e8426c47f12cff9d9a84d50766b607b553183f5c20b61b6c1f3db82d2014b573fcbd78c672ff4cbb66d4593e070294600a7e3c513a5b225481f7dad4f5a4c0a08975b4eb01b239d2b2730afd153564448a8237567768a037ac3c6addd42c8623059518cc56c41ce2b96dfe40db66d90ba28c1dd23d0de21ef775c6a4f34ff9f86fe39ab8969d68c92ba13619bb9e859d162034db29e95da4b11539a8de0a559a19bedda6a15d3d95641b0118afbf283dd8153d30c7ba50fd1db70dcc728f223afa5147c53ae5db25c81abb2cd68dc8bfd09da5ab8a70ecbc91dbb3b8463a5be2cd492ca8c98aaccd725ce17af3b0e752ad3228c5ebaddc179e5b8c37eb52696c5a4025a3359fc12a1e497019556b2dba6067395210660ce06782548e09f7ea27e55d5026a99cd3279533f47d243f552a77a3c67cbfbae025b389082a57ef93d4a99c65f15978e363fe03f6e1c7542dc8f47edd0c3566350a20bb64f74e96de1ca4b029a16374ee98d5af4e85736b7947cd5671c5203bd1e50119dcdad42d50e48c2917b652db485f0c3ea1215804a4be681d1d26f29589e9940a80181b5e2f28eaecf28f4214366b6fa84169810ccfe6dc9cb15956849d79cd7267d8cd0217849425d9ad4acead8b4a51a91ce7be87c798852ba1a4e51eea1deaba2273224c6231ad467e2b8bf8f7ccb937e448d5740ac1aa0ba2f59c3bf819d0d07fd1c090f0843c62c4141d904c311214f19dde3691c5c0ef3f684540cbdf20379917aaf4c2b8d5c3d67d5aa4ec1db96b078f69783dc7a1bd42928f24b128d1c319517e71719261f5173b60e982d11c5e8867c9bb7a1f2a59073a56aba5532406807946f48437d35cad7eb39687ccf46d88f3e9d63cde6fe3ff3ec7b9efff121ca9b04943bb51fdf4ba03a61e1304f2d8a720691c9f9b58d073dda5b53e5b8834b6da42e4f93f6b5cb69001cc14c1902235bf38d8fedd44a07c0fd9d6a9e7cd7b212d941f4f6d02a7f8ef8d821fe954b11049622175d5a7432c64156c0d65f453d50cf3dd176a95207ba3489c7574c3b7fb15587404310854b4ecff845d053392d184eeee32a17a1212bac361d0ca514532cb4ce8f40ea8bd631d3706f58530a2ae97bfc77bc1b47b456ddffdd94e6bbd907bb561c0c20a0301899f91873fbb5cc8e2c848c9b7be1d62a9515997a438893ebf7bda377018b353121791351c67fffb188182c52f179162718e72d2a46f2d079f0055434d174de1e8dbe09e4654ea689a5f9c17b097e39292d85b1e0e321c6c3ce9c936e33164181d5edb8520080985f9af5e4e8b5a2fdd3c57a6e11839dd46ae04ddd5303fef72a75832e96ce2cb94f9f59c578c6a122cc417f172fe42520c5a4f9d6f53fbedeaacfed60f0b060953bf1a9171f1043b155236d7528238349a90dfe21e8665b3d507823ddb4ed65a29d51156ad6ca1374311e3dc6659bac8797f4fcf870b818953298286b2b6aaac7e272db2ca95472b7270bb6e04a34680253bc47ba27c0b780ff1b9154b4d3de7a3e36e82a8b8138fdbd7b58d0fe16da20cdf0ef91a0e85ec7ed4b65451d850b8ae93e1075228ecb9c54aac1112fe8afb819f9b8da66e66604ae093536ce2bdcfca20c3e97bc6bef0e02959a507d117a43d737156c0b200f31401de20675a4234494586bb3a00717bd6f0b5cb7756e7a91a5ef381b261e48ccc8550abcb2e9242ff9f0abffcc3d4fb1e6da0c9ed59960b11bb5bdfc03b910c7921c1d9960f9ffdfbb25b15ade4ffc2a7a593825a29a7be80acc42ead343c785688f7878c4c7cb8f1b1d80352d8a936abc92537ae8219a4e6e38c6718b8d1db83dcb61e03ee8deae6d51f5f04ce539b15010aa599abefcff4d07784bb3f3491d466fd9b49c5f0e40c7c2180b593465ac02d3f7499a6fb75ff5dd91a5ba1852514eea1ad403ed69e6d4271ff046b16c067176ce9b40085fa9ecdead8fa9aaba89031ba96783704a78619f9a17ce0861d6183c41b6aba464823f4cf8d2d58aec1c3452b205402bc118a30ba0e8a106a2efafe1beb463ec813a2e84013a2eb440da083d107359d112a214a079a10ad28fa0ee913a275a1095089d11b818e318a208a107ad67b88ca754aeda546caa828c5f715a6d148ce0574599068f2c511f3bea864b84a02a984c8f70dde1c298cb9a9832aa984c8d01fbb6e930c34070fa71222be1f6c87e759b455954a880cfed851ca53bc4243981214966becaa8090e12373dab3325209915dca1e67a63c52cd5242e40c31834eb433ca620e251eda68c415c0ebc0d0047d08bdfaafe7a083ca1cfd0f9fb87fcb7e34fddaab0e31002831c3081d421740cdd0748839fc9e5241f83460c62c59c29f2e2b0bed61af54894dd212c974bcbf913e030b559b91cc512b28c2447873d04db3517a25d4d92936135bbe1d0f4d2ddb104a34b495d15d39c64f743030d4e8e682d59e89d32e2ba8fdf47d7d098da02c543cc280d204968545163a0fe78cd668471fe421ae03976d3048008b57341d9bc249ddf142c2009953c446bc50177bbb3ac330db211c347e8f09205a2264058fd36f5a3280b6d5ad1021ca937f576c168612e75100698a252c0cec5a30f60d01631adc9b39c2a57c24a63b0eaab0d00329be5bed3a45ae149fe8c35d01d5d2b7e559960c1d6030b2c3f0a82c82e72074fb704eaec13640ed36311c1f0b76d126a00dad733f64b9466a89dfa72de2da868241f3be676d283cc3758f794edd19ccdb565d092cf4930ded9f26c199481c0db9f39c6ac9b5aaae224448c6ac8ad89474d530a2ffc8d15752d1456fb25f32b08f47113ec1d5bb26e7ffc283c1e10c3d1ddf6f28249f8bd6c8e09926dc95aac5b99778e47744749335c1d411206516b30a852c22ab6d5d0374b4d5336b23bd3f936cef7a25608e804afccc01eeea802e195a8ad910e56ba17e6988f2b53348da8c10c16611c66c88474c95830460666cdba25f9c562887eaeac579ed2cf8ac2cd119a97eb4d7188ce931ba4f0597ef8848a7df42fd1e741e3ac9049c430d683bc66950b7c36919df997cb5882e910b7976d8ce15ef426a608acb4e7bfd196c37b2b7549eba8edf889aca8fc8a01730a23d8e82dad5a751b87d90992d493274829c167ed48623f34aff308150ba26949c6bdad7c1f544fbf71c7fb533110faa3f689b60e051f5dfe3700d2f9ca2e72238afb47019d3610b9692889cfec678fb946fbb4df68146ec504e12ffe156eab0fb576addc27d5ea10457d4ac04402dd37b3a2952a3daa8ace37a3a10f177c456b41fed7a9da908c09e724e36abf79dd6b687611da9d64d51517fd0e87d25b6ad9a390f9de1f1d0c0b55c4d7aa56e9845694433d0e2ce4b6977d8d75ada6516960c928bc3cc56097289d5fe99bb172b99b0d1b4de0dde64a1b681916045c2bc7ca84f952bc9f093a0e387d0400c58cc35b19da44e04fb974d4db4ef6f811f500f3c8f5d3d37f569a0e66cc7318c3763897aeb1e2581fe65bbbf38a687d0d50ccd0ca0c5f2a034d848a53ad17aba4e6291a92d593f02a2bcb2cb04d169d42c55128d50635fcaa1633da561e27ab10aa1154152594c29a8c62bc4e3a5dab2f4719099724e2b1163fd9e3b38165080eece699d1392f224f1a9bcfd38bda2796066ab163314a9ef809f1cd8244ea6d3d00da0ba0240e2f11a2d12edf7f942f32a8663060cf0d0d70ded7cb23146e9eac577c532352ab2f48166f3a550058b1dd2738905d2c721d931f66cce9d8e570122e429a0067015fe0abbb8f9c7ed7f56df830f22b2f80fb3b7c5420d36c63faef1e0f07728b32e1464a1002553e681c1c1b438057b260c757e5a99f44ffef40e0515d95fc60eac27411e8cd693110999504f663c038c14250c6479e63adbba2e08b543fbb89ff7147d5596d5983bb8da62f97e8053331456a319df67d71acd506f5bc7a0b9f74a7c0bc5dc42cc5175607340fdebdf14fcafdfff42e683df1a980e95e64ca9dcef419cb4957f2de7b0b0c1c8327aa0563aec09495221f6722ba23cd84ab57bc5938b121208733ba9453c8e2f620cb65c04ab421b39f7c2c3c07da9d556b2b2bc4d372ac044e786c082d9ad3913d27b195ba8a4b8e761056a9218efee2ddf2c9c7576fa4521e7b0ff330aa218edb516bb397370595255ece43fdd5444882ac20b086754c5df680284b763c1678d6d8364a99a452c89e8cb406b29bdbc1fdf48acd4ad902100baff283a3d79a2ded0adf2e3ade65c24e5da19b74a9909ce8a0775bf0851f79628b14055c1091a12e81e4ef662b48f51452201f42426b610f8340d95d2fb77de5e0324c6ad549b4dbe4e6d79b5b83fa12abfadf14e982f74c0d8290452719c4d39f9680b287bbd7364bc49a1250021eeed075a4a54b9650a659a7b2363b1e902ca06bc084fcf1bab1481adb8228167444fee6f5abfd882bdfa629b3ea964950e0c8b7d975cd876c77c7f8333b4063a0bca7de2f886dd5b6c20c63f62872eb0f0bc5c1d9bbdae8a07e8a50db2e2662e24493a1c77248ed02619124eeea74b21bd6122a8962c61cf912b140c335c3b352451a52221f5cdc9618bde07d9de7ffe069a7819f3bd15f4b9f4109bb3923a201e640a0e49bd25351dfc589b89c2e3beb05ab333f5831ee1f2d5f9a457ced22cf0d9011db583077f7425a22eb4b883a3890b6b1f8f72713e29d9aacf7211739d1ed4b7bae7e4da827aeb8eec70b86c86775104a73448735e86e0d89629c916c745fe305315a128f8c957fe3b29ef9455c25c72b14231aac9431893cd7b2d3ffea7feb57b74292da80f73162a7f0953e9c582bdc9fa6ff6baec58f222d7493f9cde1071614927b0a63c7aa44b8dfc37458e4a8761c297ac4c7b69b530f9d9854d1e8ead249dfa9934ced2240456076e541c7dca63690e10a5a1f9f361579180bccbce93d2517ca59fc310b65caad3bb03865e63435c97639fad94e113bc81fecdaf675ad5f23c7f406fd3ea123aafee694b608f6ac9c5e573052d66e8630e39318ccd2cb6954adaf92705e4580b874a7f0996f4241e139a8cd63f49ea5de725d5119063e096be8cff9de679f77906fbd9662c4abe07e71e8f15769a8691a1a335b0044fb551c1830c4aa5cf10a2187cbe8aae8092a1a2e5721216a6d9546a30e3e3fd54939ca297f4bad54cfe3cd19e4d18bd03904dd8785e27ebc3238f0ee0e16c65938c4480a4cceb12e09d86d5e7761536fe1408e4c6f0cc8776bea87e4a3764c26ddf6180ece14a6171c0e93637afa3ac2d8b8b9cb9aa2cf17b670318a1ee1c49b86a5052605f90394f9457b958110b6206178abb73e46b9f7a9ffbaf36c6b2bbe05022f1670cd1c9542f8597709f399b9a36a11e0651fb7c9309efb0a1b2dc3e1402196f4ed8767a0ece7559de22522670dfb67605ab103ac35375a54a8a3a5fc872e1ce8c87354f4c21da94705e1b552f641a741bd80ad03d71c523d424b6868a137f48187c1b8e2242a433f7c0361a556c7ce24e7fd387cfea0c2605eb24f42e96c0565a403effe1bccca4f140dad1cffe07ca93c9e588f603828466dcbd6489bad73f2660ebe48cf405fcd75637802f9befbd578fcad2948ae61f59747ee619185342ba543a5d3d4c6238e73f2406063724b2f54e6c47a117ff23e9acf0c0d9ee4a3e548af0ccab64285bef30575a0148a0974a5da996c9efa268ad6a6c28c266eaa4f7005da3cf0b0ac501068b89600cb147f0184fe89d14478293ef7fc956ba56fc6c9a4cc7744db037e045ffb56fd7a71897cdf34ddad61f97b420ec1cc682c410f33a075e508e420c552cb17988fc40ac1193dddf79cf0512dd4d0eed49d79cee5d0380ae4c652631b31b6ce8ff45b49b468d856dd3e0aae0136ebad091864d829fc42a6815d1c17b76f9aa07a877f5701ac7d23ced1309266f9533d79677b11bd4a07d2329108afb4969ca5ccbd575f219de5ccb0a4d5509e3b6d4bc6b80fdddbfd974d36bc4e2a6bc892d6835bc42cc7a97d1aa55d8a71f6a602318f852ca9ee7d690fb476b39c797a1ea3be60a21cd86d80e4dd4da4cc8626b58a78c1868fad2b7106f796e2d63051dd148319e8eee7c2372a8b9f78185d583fa110e0ed1b1727953b51a451908de53f93790d44eb08031681ae3a7cd3239cd9c20ed093ec7d8943adf3f21c02902bd69f681523e05f0631aa78f917c1eb490f1bd34e20cf1b3f197b4dfa4f14eeccf6cff65f323393ed2e9974efeecf227ad7f627f26f32b5b3ff9f54b4f8f74f026b7bcf6a6ac7f663d67fb235f3ee9e9cb2effec7aa7063efd480ec9ed4dd9dfcceea39dd790741a35d13150ed944f5288a12c9a8fcd14fccb9f95d5fe03a4ebad2b9aa8b8f0817d66879070bad369f8979e9442cc6311708e613431a31342e4d0d447952655110058d436849cdc8e02c3148c4fb9efc9772e69ccd2176ddc5938ea8088dbf5d940c59fa19bc2f144d484b1f29d9df34d7ffed39ebcee4c8de694d69b065af3edcee4b93193ea4b296de9d995ef6e4a5eefc98c42b17c1d995b434ae947efedf8a61b396fc6f47b51ee56f4d6894f7923920fc47a783a1a40c1c9d48a0441e0606a149433067a42c027f8cb0cfddab05711f2dce01e76d449c4fc5188afc0fb8cd09688755d90563ece1ca31c3a8c0920fc627c978dee59d88a846c0db8aa14aa5c2d982ef6013687ef74e21ac59d058a0b9be8a80aff6f0958d694389368f04f4d95fbf1d567733eab4a52d2a337de18dad6aaa800eb429999a996e6e6092264c9cc1b7b092adbeeca8fe9d35e9c0a944696df312ea8668f980e59d225d94addabfba398137d9170433ff619d2d1aa8ae0ca8599b5b11ee8a313e314a9dc31c7974be8939a7a501d493dabad9cbf71d88ed62b567b66cda28984afaed281b627fae05a2e6960d91dd5f77c679b58f26ef4d8f1717bfc701d3efefef52dcb49a7e0cb20b4acd80f83bf95ace9370a5976c3ba2319bb621a5cb1966ad808e6ae280d2a89177e1cefee841b025c784d8d97cd9ad0358766a314f5334017ec04698c0748fddf74e4cc0b7c00a13f0a02022743c07dffcb9cddbf2b1f93f417bb6a8ff9fb83aedb97e1c6b4f4bd816bc108865c5bfdcf95f915e024262981944db2002f897409842cc985853bb0684a3fb93419ab8ab00d828482c5c43c02d43efc0a4e3378093deb780f1deb79173aeb0b5dce73df7c6d508fd5c4f447584a4437728e8b1c67b312e4f147646f8150b0c4fa66ed45b85e564e2c890b19d1f84e35357a0a84a74d09321b437a7754045465e24456709f783a5ff9809302f5cb402c8fbdab23bb83fe8c0bc23a93090ad4f820dd9032dcdcf12c8b1af62803a5076f1f745873fa7b19ffea9a608811e8ced4f1d6be61c2de5a84cd22ed62b89292202bd6151d706ad0582963e10fea86b412316ac8a117ac7313e0ea000b2ea340e19a7faff5ca09a590d620b587949328db457a52896f488839d93e4ee13d5c8015b8a49079c895f4d93492bbbc95aa908fe0562cbcca22e84026ee9accd68c04f01235d97ff6f8881fa1883345a2c405369880254b7d6481a8ae864cea372987fbc68a078f6bafe7d347131af153508a56d2373079f1c926c7107abd5ea0f07db2afb5f1b28773d90ca153d1c123b53d012d1132c392dd1914677ae9257b97822e74448ad237f312a83159e6958657d2f5226fa5cac074b6d8120c736bf4d1dd9938b95ec6cadd1d5baeb704e3da821dc38e98ab15c1a578c64f701ff5dc2160e1e22259c1cc6f466291979a61222aec67cc65661a478c2c6c6be10e68f7932e7dd05a06395ee632808921ac42189265c22e3d1a6364816e7a571f9e8440766d28943894e9d9c078a27c416a4968cb8e882c673ced55125a34d950b4f8f41e0f5c0fa9fae295cdc7e3413867adfa3e5f3fab081abc01865537c75dbd9d4e1f1645a323f5b1696f70894dc1f0105c608728915c16327a279afb937eaaa441e8acc281228c20b591591cd3267b970eff719d4d844a828db1875d77650596f8af3df66e4a5ee01384170ed93d5247e372a503cadf82e29febb68142c558b344379cbfd0e3d5757788ddf0965d63d34a3fa9e17cd4f868117490d8a4f9bec1f20160f157b298526259360e023dac675f344f178f586b6c945da4681d84e221e5fd44b9c37e0e78ee61f129131dc2ca26829beb29bf5dee0586f32f81e2a30c6614b8cfe13490fd6a8b8f94e82a12a97e8634c045046bd06a27412d8912373ce0e2a30060a47e43ff5a97aab3a3a7d879f83266e0ab5943bd81a68f7aa6859ba87be1f9c99259de5d51f88e0b5c2c11bd9c4d095199a45cbe4d38b8bcb73a26642839997002ca798d6e3d02189aedc909617c41032896899870c6da146033946796763db9f6ac9ded401739f98fc441f918a3235b9465021f3f9a2e6a2943ef0a9b23543b9612d30118dc18fd8c9df3ef37d381e586af1d336c2e11a5856402b0c1c41293308df5448f58135adb0e792c0efba053042eb52f0342ba9a1ff1ca12e15e1e8d30904277f233e1baf94185b7868e4ed05096d8ad5b4f1d65aa18fcfdb03f266cfe203b929e1d378b3a1cd116f8aa3be4bff89e5a856dc638dec7c09f8b2d43a1fe9149190a31f20f65f000eed5c50712d01b7078e2bdcbb78816cc9f8392e0d9535e5ae0da29b72c6562de75ffce4b216c5f96430adf9968ffdab45701aa46e2f25c25532a8943a5a7630f56df342e00d1a68aac0f28a64e2e9246b279ccefe345b64c2e628a4af16ccf467ccbf9a30c33123870bfb726e19cb56cd5f03c9d40706eec540c282c0ab913041c183ea9ddebadcd4d04bffcdedca86ea03624af45e978e128008bccb9bd4004091a7c171cae5e47e5e62149b5f2d76b5e07d075bd244050c34bfe9ca2076848afe7f3882b0fd735f003539b1ac44a84febea0582ce019b25ec078c514f05b89846fedbc1b9a57371d0073a4b78eb870efcc5cf63860ccc43c1e49e839f83c164161cbeb0c7f33949b5cf78652c6f6f86d7f46b6c61100dc151ec728ee6f3d0495e32c5fe5de912c5e9350e5e5f10619e6a62c4bb1ab28905a0093ca38789c3c68f30519450f5f813d9d7a770490f5f4850276453e4080d06bec7a186bd8cf004470e83adcf46fcb2d4f56c251d738a276a2480def2d4f402248e89da4ec4fe029f60c39f0836d39eabfbacb15d79bdbcc61e795c614fcf3252bb659f4ea80175d0091e230536b7b31c034aa269cd7dadecae996069879d501358c8d40f5019d46ff37fd08f99206d7347688abb1a1364f574c356e94e2692d96aa9ffe11307ff40581517fa2d77bac1fd8eb589145c95e7d64b9bf31dd7ed481662037462d5733efaf2103870b83a53f83200b099afd2ff7035d48121ad539aa3e0834a036875ecacc6043f615edd4b549145288de2d60d01e2151b36a9966e181a3376404d277778cdf3a5577c21f043fbb9aa09cbc087fbf8d45319a3acfed9dda958a27f74ed85797e281a79816967335feda7b0067f2ccd6a6484eb184bf9373396d487f5ec4453da5f376a483f9c8c23f79f12b3e81af5faa465a07ee4807327780a0e9231a3f49db5bdc5963229edc5e0d6cd604307773c2f607941ea51c993b17515e347796abd12416e86cdfa47c06f0020a39021e0ae13079d9296778f3690ac972b798fc02a129d5e9d809838ff03f4bbe1b219652933b3a5faedbc37937201ca1dd44b81f9a3b28c9798884415234c9950fed7d1d893479caf0653bc04c9c8c46563ccf4fc18ff4d0215a80904f473fd6c96eb8619c1b1b96057646b6868e15b290c998edc5dc84e4629c3e46da33dfe38776d1c6430e0a9cba9b27903af83b291004cb9138a87d29401c40439dc0dee19d97efc9488726028884b515ca1e899bada646d8060a9a4a29ea4c418925e5d80a5e4dea91224c9136a8ef1445ecb88f0fbe3e547af2ff435b8065a77934dae1dba44a5c9e65edd98303887fb46b905dc9978639fbf382c711de7457488fbf91923ab0e7327a34e226d52136aa8ec566a93482900b3cd7455e486c8a98b8d88c6d9e46d1c111bbe643804bd34d50b1ef14e890c925510cd007002a86e0e803c4f3e652622c8210ffd3f3a08a8db75ace51d09309282abc9d3f917c1807b1b6e4cd5d6d90f1834ed6c66584259d25045bb2f17fa95fe4006586800e4cc03f2a206bcbc8df2ebe7226dbc4980aca62384ec2d54b97855de03276b728fb9b55be04df6d49144eda91e186ee37f08832dd8b60d47d359d40fca5d47d2fc85a617fb4fe320c6268c4ecacab5422fa4c97ecf8dbe644ee71c3f41ee78580afd203aba7a562cecef9d0db37e47b1b9d56af1a5b1b18a9b15a21950a6312de047f4249b0b4f7fefefdd2e72795ba7cee801f954031ef7eea3e0995e1c6c378151f5b45b4a16c47089a0c75821da2f4d3c8cbefae19c73e85d7e51b571693d8d5663daa2718af130f88d66347b4c28d8b19de60e7ed894bebff442d9fbddafbef85ba8223f62861b4d59e4569a5acde1e51a0d69d1b0a51ccdb5348deeaf971951ce9aa7c5dddbd3d18d3d0bd47bf418915beab706ac5cec4ecde985aaa02e5e89dab3ff83c082461cea3a336a4cdabb0b73956acf6dd923ede2dbef3d401c7a693d0c4d933472d06a526102d1e40f0a31f867aacae77bd9efb99ed536a545cdff554f96765d633f3a7a0c53c6f2eb0f279392bc0537705f887f36bb1992a8e59998d5081d1f07f9b5937225e7b532116873595378361a6da00c286e9537a4397eafec22fa7b6427d399a817cd947b081307dece7682dc7b387ab859e71d4da653285df5ece579832b5e92d434f25310ba272bffa36969fe3ae61e69ac2ef4b73a4f0e594f03aff133290c6635e7fc2567962c5e3b97fb118cf11954c3cedabdaf03c52484f307d1c70574666c31c1e7ea9b1b3c0cbab67d193d59083b2d888e62e952331105d0cba6b95846c0e0cb9dfcd62435914619234811e3fd0e54db6287e593566b41ba49ac3f3de006a9d1381031ec055f900f9535488113b65452e5251459602118c845881da6cd51e282cfa6b78a4401f53b2528f37f5453433860eec89266b12d71a93aeb5a45eead1ec161746a46db1eeacc4b388cde615b0db8cc17265be595873c9d168ef5a500c2e2d0f617ba6ec1ba29b858b47c9d0cca0101c521dcfa4beae978a4350154d1e8caddca2ed4d839c03672b358a908c602d9feba1541de58b8e7f2c527400c892827e27b52fc5ef9ceb2fad783ddbb27cdf7145b8edc93afd641eea86386f4a4209a0815fcb03dd82a43391f814f419c33315264032611c8e8ad209969c9d4cf4a2435a6aa5365a2069f00d069e6fc3adac1781b455367a0c3c02c91a90f96236ed23b4f986022b9b829366ca8caa41355de516bfceac6bcbc70019292ad764218b22031fc7d7c2802de4ec465079fd9de6309f36f93f1cefb746e5ccc4338ab76c82afc3a79aee91ca5bfd2ecc1b8356f5c1cabf1033c9a9ad45c23fbcecd0048c54dd656e1a83015fbb513a37025e61546490afc831f3d52f62ce2f1b92a85824a8d43f21cc4b7644f130a834d43deb8d87778804f204de4e8c42553a1ab8992093b3777dd88266d5a22455ee365a20f58495f2610b47a29e00ad52c73ffd21bf8956630805b96540fc9560c7be2394f645c02c70afb1bdc4461bcb9d4005f9d84f98899a155d9483d50b73fd1922cbbf83cbe5b1cf0325ce61064448b323feb00bfd9491c8b436bd5c559253b9d5e918619917372ba8a6f92c420b043f8443332cd9054d232d99d0947650774707b01288c53270e88ae27590ff46d54dade5dd42b59cdb14c76ec536cddc37ff87f38cfb8ef2ceb29f2ed62e9bcb7b20450d1d95c00b2b40ed40b0679aa5788980f47a40d72f4db4b69939112ce6fd92a54222f71421cd046fd01851b51ff628c651e353f9a30c9b59420010959e3a547b17c8b6300b49c8c34e2140d8b16aedc701eb1b1e49780dec8fc108c36a605e5780b51a2df5247f09ac93b640300b1d089366963c85f4238856682f2a0199a966d3dd69177b899af7171dcac58e965c5f071ddbd9578767156f30b87218399e87271d2d391e482135de296111062f605f3db38aa0fae00d6a0147f6a47ad2020d809046c2214caf8b887d91f07143d374ee9ac832df7a6caa340759d3b20b505faea5e4701461cd7f8786840e373823a132c059a195003af01894c0dcbd7e020d6186edf3da1c0e2b673b158466d1d770b2e90f9b9ac76d540002cee7b742732417820f524ccc25ba1bb23d4bf153f24ac702588e34e0191e506f6c421019212fa480da1ff6e397f3404b1372f522a4057fced0bbc9e36ce091477bcdd816577fda072a3e81b786b587751aca1d2fb0dc0058e27f5a7385d1f399a6617dbca536900764fe502dda6ca9ada40f56aec50c1a6c0cb9454a5bebb4cd855e33253eb519549c9b90f52ad59aabfa7051a4ce8b85d04009309aadb1cb507b4a5c8eb215f3e3883e11fbc8b406af7f0ec303ca3ad879075c2ec1d7630e8fcae7765d38be4176e8321a399de81283fece276a84556ac696c302f19e6bce7b4234e3efbab28d0377ed9af70cff65686466f66af780661956a6dbdbb242b2af32e860fe5a423f1022f399b20036102bf462a665e293ac32b5d09b2ad28e5ad0621c9641a582464de0ffce2a49dbcb919f366d744d904b9c8b3f66080805ced6a8ddf5e76b2c9fd078817eb964391d885e01ca882913f8c7687aaef1830141482d38187d678769220f9f8189fe7b6e1decac58917c02992fa7d922c091956ed4d0913bad35847efee394e55144598cdb71c133b8a08a64ab15dda293610b24d30be3c9d05a08b51819b60a63782165e4d42de80b5a11b89f6892dfdd19c149ff98149e0c924b893047f462e1ecc2ea32b53c7e68a5b496b44a2cc70eece57771ad4bae9bf5c563377e268835da6582d7c9b1aa5af7581b88394591394c9cea7dd4524b70c4ac55b53e6a6b4ec7c5ac4f351f6b613125f2fea477ed09b290ebceb17987eb054af1f194a6e3462ba72af118f8c4a170c3dccd30cee7d277f5600e4926e908a52aa5bca4b15d5f1f001762ec3152f379e56aa52d4f74296be95f0f96f33befb71cd2a13784861a77575daf58a9f8c1c99663410a5c7bbb43364a8563e49665b4e61cd5886cffd295c6ea48409d5ab84ef9dc3a69c71cdc2f5bdd8330ecfd9e0ac84236f7b6ff61d9365a1b0c52ca2ec4ce329a2fdeaaff6eb1afacad344154436f42698a35ba8511f36fbc9a808d2f569918c1b73a955124bd81122349f0b654207fa5fd46b2b213af66d866dbfb946c0c8381db881584d429924617f0c261c81ec769eeaacd73a257fbdeedaed4438a2452cf917a9a445e9c9454a7d9a3ac9d5781ff5b44fa3d7e605e3b0562d4cfb591a21f0118d5dae8b373514faeca7d34c4c6c07a49d8e5490dddbff3ac5c3223a8b33d06101010b2317fea8e7d5342085e064be6009c5c22caae253e532ea8335e5d1decb63f492d36a48f42572d38f8bc4cf63b551885612e05892696566873e4fe02a5e5f0c0ff54c8a9548ac771fcdaab787bd974bb6df42ed378eb6cc7bbb6a7b4b3d279fd847acee120f6f8cf3f23a6680de8ef8dd21bf37ccac07377cbd165e91176c839a900c10f44cf555a0495f48572942ea506be0eae3da7835307633019cb069c82521e5514dfea9a24556d5d2d31481089637bd388b3562624c7a8830fc9498de0908612fe4427b92c081b2de038f6854e92b92df2ae1f5e8462002c569e0878135b35561262a688654404a96ad5fee4739255c20e71705bc03459959eb7e06b477a883a45b7001ab3fa49f09084e7f421724896b7c0bf81b77eaa389f136fc1e9f729c24c19ea7cdd62ff83708e95bc099a81e8dfc66d1a3369798f620f7ba59a7de3952aab5a1fccb141d176e410b2f3fbb1f5629ca0e1ac4fdc00def13493c6a90e7673b39eaf1b976196284aecb72dc19d26da124a21b9fe0a3a4c7e04ec48070596180d42f23d2f557fca33896183b12ed3ff813a4080789dbdc7e6287c6dcab7211058c02cc4605a00d484da47c90a0bec4c9c6eb41c53226b5a22e94cc39668250b6ac337e18193d0b3c1158f02fa900a714d30a88d29950394a09f6199b72b6b4e8b71f26275a47a510cc6a8dfb47c62643595776df823db86e3a8cf00115e745967c6507cadc4c284cfed7d535d7fa3a23060c6c736fdfadac78a51eb90b599dc28d5bdff178c52d8d5a48fbf34861499f20691e409f0acd0c579918a6fedb646efe39fb3077c5e70bd4d6234c1c965d32789b191dbc568389edff84b48e0c932618e0b7d94180f41e20df47c4898ea98c6310ca8c7b9930c3588e43d1a0c419c3bcd48430cd8630890c4858a2e4ee0a8b06f74dc0f8a1666da0981811e6f861e020f52c8f8b078e0e7ed1e119a0ce4a3a148c82c4d83ef67aa2b57e79e6572cd129c2427522eff76f157549b7dc1d47a48783a9798098c2f7e15a42501092be102b2339c1397b7da18275498b6667583b78e3fb17dcaa2a37afe0102935373a9d255b0fcc5cb60d0cbc29b61cbbdda5a61eee64c07902f7e9bd99264e2e25aed9304442ecbc899379728b5d1c22655baffb051bd7c6721d786324d16db6862fea79820514831807afa8d67f97533f7ae69d48f6e8275ea10e7fe2e3fcf7b2cb6d7515a0c6194d4ae6391159b4ccdddb822219ab2d5d054db20ce71e2a6a1d6667a0a3e784af2b2cfb492143a6a53cc15f7629c87022d84bd8eb93948c9208418d7c8ffd650c6b86aa4980f2baaed4b4e49a489ef1d1cae1d8d0ea928a13e4ecc7a170851aaaf881cb52298929ebcdadf1575abdfd0c2d956a0b4c664216e164d86178fdff1950096903da672eea4a59ca362e28ad78d68ba8b08bd648c079cf5a33ea1f5e2bb00db1f4b29d0bbb3e3748a87d33891bfaa073bda5b0dd43d43ce36a153c1ebacebe279705d6d4a045366088832ff5db0bc49a1a286f0a4f8cae9ca4fcf6a37fa6e2a888be79f06502faf3bdf89aa5723fadbc7449c56b84c2998c170728f63ad075720da8cbb21d875259de95f28b75596cea5129c9b281f4b9a774c22d388d0c98f5c2c5b9f05a23a89d8828d6b429f8fbf64bfb6da2ead6770ccb0927e3d2e0e817ea8a7ea06863de58701d36ce12ea3511e807befe3fa72cfb7a8470d8597d9d90d54756b5dafe870344db9dcf8626cf0fab2b842ffce52313f7bceb721b0b8761c0fb374c454a9444daf12d64041fb3409a2a872649237d1c80e91d0a58078ce84b9586fb846f2a9f86a2eb6d811ad60f5ad87878a1d3f9d98de35c3cbb5ca5d423200bde0eb007fb7acd26df8612f24353e95566917c181ce0a401ccb3976697c0d4084551b6d9433246285e439bd16b3c3bb9abaf02bb1a14f48721998fee6244c7b065d1259e343095422bcca4e4159d98e57f0fc0d5e8c39fbd8e7bec1bccd77d29b7b361a6dcc1a7034f5d1930dcdd906ea2a46427fdb72ba9d8de23d935a9eb8681ee2b8cf3dd638db9cb97702109f135935c10765c76f92f502c8d92995c9fd8fa12e0e76bbbf8992e442cec55bd8f5420496de53dacf0624bab62e7c787c44bd3c3b4eed79bddeed131021b4f50123de9ae873901eb3c5fd3842898f8cd138658d0b1b5f96cd45dce5859e3c7329ece240fae25832e44becfc52ebb4bf8a02ec4d5d519d0b19c40405638212230d27dc3888bb84e7bd28d14aa440ef097091e46dca188a393c377f93fe4d43cba7ace6b5b5383137980e1476d0f1296f2df6d9f801e7db1057714a6a3c7fd16fe4ebd484146ec32cfbf4f0d88285dad2f336155520a260924b35296b4e73cefa789e410d81b7ad173e0f7426bbf51e9df2b501bdc9cf430a348b3f7c91605e7d0d42403da44663295fc3c13dc69632f8aeac1a5092bc355e322a7b9a8c8f292c685188169aa66ac3f66d5bf23111b051ccd0a3ff83084511503467477ba7dab8eda2742fcbf2f245b5abc183322758cefb64526e2dcb188de2c207ad30c3c6edfad06e1a1acaf905fc3b7c7e7e7d2339c63a21d95d9a8364991a73de448ee740d49f217931c4e06421f42aebacebcd32d6ac3f510d2aebb8127dfadacd0a18ae5160075758fc8087e549b7734f2275d09598da1d96b19aae30a859cc3768c7302095b2468132c968dfdffdf5d58a1d35daa5f01fd119ac8a503bb49ea2ef219280f3808e8a17a7027e3f3accc77871d8101776af4e8905960e8f859c3b5e226d5a578fb7c4ae5088317924767c5b717a2a0df3010c451e457be305544499cf875577043f1667d68c711aa67fc388752622493c5489b89d6e591db02f025c00316c4055206ca7310a82125028c404acd3e67f22d009b28d5fd419fc20392622115c3153e73b4cafb0f5d22aafe187c204c80b706762fd20a2079b53b65ec14c1943dd9e9d1073b1eba17f0015371058be758bffde93311e024cdcad09458b94159f5d2299b9e75801819b3658e70928874a29704aa0eba7ae670b2c3573af6560a6105000c8dfc685b5d2483ad039c365f4c0d8133b7007c74e49a108b944051589e73583d841e498e9d55737c6a5d81a3900b2d5ae230fca4380eaeb59ef08cd89fce03528a42005f8e8b96952af3203d763e9673d630284bda7259734442c918554a7afda84ff654eccca024add295f5b3096aa4d07522fb4e0efc1b3d0604c4602d2903bbd96b264b30cf7ff4390c4f194205941e6f0254914d6e19f7ff6c38360500003d3f18a0fb3f319c002e6ee4e4dfca157307483ea3e0ee3211634fb2da4edbdf7965b4a29534a012b069a05d7056e135941b833b56addb0955c9f5dc3b8eedc102c768655830637ec530ed6bfbb0d0d1bf26b474a8d93423a7bba1ceca75b8f48c4831ae4c06bb06554d8a6e190ad75312696d2cf035bebceaf033276c03de0d1a3944d3ef3d89cdd1be70966c241d22257385a742d1c36bc59562b92f556f6c1ec2090be53873d1d0e3677cb00e03841a525d7fa587d8e9d0cf48b23ace662405a6e168207842770d590ba43b6d6adaf8bd56a32ade0d6907d5fee2220c2dec1597b70e0eaaf9e5fecb99b417bfad9405ffb3c153c308ee6bf64d9059c342fe7306163da00cf9d323aba29a7b39810629a729850e2661593991fc449429c1400306ec84feee4d1b9a18e1bca4c403ef5e2eb781e4e025598a90e8a8ded0e96a5f042c9cd71590a2f6eae92c88266ba1cbd3acac1d57a1236c88ce8be8e7e91159b7d49e687a219406fbcf1861b34f2b34105f2e913c19e7e2f33501bb06fc9cbe5a6d7c5c034f2ab71d492e348e4626decc4c1a31c62fb928c93982fa6822fa09f87a7615f80c438a9336722f6c974cced56c149d86f1cf7f570b07ffb7838d835c8aa02e64dfa552d783829ca2ba78be8afac61084070e753ec37a6d81782e07ab3c37a38897ef2e5d7e347e46169cc8a989acadee6b6d5adce399b9b23ea19a047f4f0bdef328f0f7bdc1f7230c94f0cc6cffcb7e9a739fde424bfb1545b6e0427741c96c2054d5c75e3ca0549b8a13b7154e8486efc29392dc44270d9dc8d7a97a65388af7999c76d18076ea04bd9e745fc883512af08240b410c29d73ded6efecdb08ff85a1856ce4e4a29dfbdec5279b2444c350e167112e96f4d1cc5af63dc18c4abbd07f611bf2696dc871ec58f41c4807b147a0b6efcedf313fba08d6a22b8d439285e350ab5cad1544a9a515abbf99d5d1a869d3704af9f61c3daa1d8475411d13ab0fdbe72afd531fa6a268eba1feef54a87e78983f1757ae560abe572d2df887aa15e7a58750bab82bdb01e075d0d1bbf9f384dc27d0d1bf2aae6f543042fd84b5f48f633ee34bedfd87675cbc1b0ce700bdac7bf5cca2eafb4afa7c46e59356e7cf7bcf5021bfa09c3a8b4b41010979fdcb8f50b55b12d97a568c1eacef86c4d9e6aba8c028d01d062631eccdc7dc218d6c5499cf37521c5cfa7d46a040e86cd5c49638cb171628c31366993f4d9f445cce238628974b80607410b58088bc58a898989f16aa595524e289be434eeb4e7ca75f782fd06bd474cfcc0df346eabcf0c825efb689a07facdeb326ed33ad0a70369daa7036d3e3f5de6b17490bb2178a5e47c1ab16d432e75694de4a094a06f50661d94d2bf9a042b9fbb03d2dd0fef7e682e641ef79ad77d945cecfc33eff39ac7fd16fb725ca675356a33b10e719148f0212e373e2686cbe59d964b88d715ebc4b4b603db18f68d891eb4532cc5a60e94fe00a27504151a1c41630834f48b4bdfb2a08b4be75719073910b11c5dcec14191fbb99cb3a465e716a11964d1839e1cdcb0408b26011764b09193440ea9d7949a18ca43111b29365e806aa0b97868934d1436556c45389b2b6c78b0dd20088a831360380183249280e34242b76e30847cb99cb3a4ca12332a0fbcb86209124690a28a828d2e4e80aae132524220c1c31347ac80870b23b4604207339cdc600729493801106c64a1051157d06089152de5040ff0018b9e1d50d1a30437905033460f4e1a55b0b858a2091a4738c186c304382811c492225e66b4c103ce1648be18828c14122c6ef0c58e135c3869830b233990d1032d38138862783c468306165010418933aeb0d259e048813304ee079fc7db67014e936a5d44cfc3890c6ef831730a00cce9b95807e31326d2a09452ea42603836383c0c2963075d5c79030823a6682c9e5823e4e40719f344194fb48800a4073e3cf14209274ef810c5a586a15841174520e18410ac00c2083698010f6b14d1841e3ca14411fa0a31462c88428b292fe0618d359a858303415451440c96e4000952d0b8153da8c1b171f1a30aa1901bd21f22e44e66ecf962fc98b7c3c65d0c1ff6edf1d7f4b1a691c6e766815bad58759ab5e3e0ccc1da389968a0258780fd8057f02675a97e12cfc547d2fe60fbbb9b407ce38d6670a6e0f20a7db11fd89047ec5e9037f6537e215eda8f7d22885f43bd5262f17329636c29e51656bed5e1c28446fa113bb3154bf5b9efd4b9fd980358f2a39c5af0a8bf0bd70ceeb72ffdeeb70cd3344dd3de453b41a469dceca3458bb42a1058301586b56a8787094b098e8e942618b74ad5edcca2288aa55aadfd678eda116cc83d45b8828944313120c82ab144a2fe30ec5b2ef61bc6a4f8d8c59ee717ce3198d4fdc0542a11a77212e7a49f5cf717f10a7d45d8c7d59a75411a88df89611886613d422ed0dc528a62a997e0917f139d439350b561c58722ecb3267b8aa51ca353ab4e459c44552ad56c39c8c33e704058b0a41ad5a8d6e9288de448a7ba4943695533696666aed54aff19978925a6c1bef935ca49f47a3d92fa1ae560a7e248cade62db7943de11d2282796e308fb2cfbda646ad3106cd8d708cac123de29073b89b532333b5c132f7aa5518d6a1486b9a8b0a48a20453c64ad120d1b9fd96bad953a2159ca96cd5592713907073ab8cdee94d2226ccc1ca4638c52ca3929a518876118884aadb5fbd11f3b986599a669dbb6498ee36a746116b97f5d578dd8fe4f5b0b0239c825c6211d45cc6cad0d85422e2c2c2ca2161d3fd85d5cda657e5858585a5abca5fb21dd71b8e0c081a38505078e90c581030708c7a7c38103079d04685162ebee172865965f8c3d975e97108e230b860da39539e7a494d22ae3cb621856c1b0b5d6ca499308b3622b1836ac9aadd588e5e73a1740f6765dd7fde0aa032bbfff709815cb201710082472d101b2d6da5028146261a12c2da594f2636909d916d0a76b696969f9f4e78fb14e984255cef1704c381687c3e97052b8261c77add68a44fff454abb54d4fb15427edffcaf5609b5081dc145cce758e07d8d320b8b4bde2c8bfc2c01a713dc98b0cf5b3cfeae340b37dda17bff8c95eb63e4ab5d56a6dd3d3112f655ce6d1223eea6ee6962cdd6928ddb796533aa4e98c180b6bc3ce094a61b5fee8899e62899d52c1a96260a5d38f9ee2a80af76739b11471d2e4549c4a85613c2e2a3038d12a819b59b2ecaa5dcaf56043ef7d6cef8fbbbbebd7eb86f60d4c76b556d9d5cf3cbfb452daaa5d936c14a414bcc00654e8214586728360778cc7a5f80d82a01829a59436184b5dd26e7fcc3efc3326d85034df1d1041b0d61eba84dab572e5d6c21e2540818797bae7f79885fadc7920af04f56a9f04620cecce5b806e643acf860ef4f629adb5d66a1fd4d907bdbf77a0b7dfdd7f7e86ee6700bdfd64648f0df63f2c1f621d1b96c8107a96b7e1f3f66de897c1fee7656079e92f06fb9f9fc17e9f1958be50e70561c38632a5ee69401a7f114264890cdd839e88fd388eae0ca08fc8e7eb3ed6684b83b8599097bf7056b9fde1cb9caf797e378c6a5c3d7e30ee374eabdb56ebc681f5bddbe618b7dfb75906e7f10db26d9957bdd99a2e5a84def4dc1b280e9b7fb2902c65cbea4e1b8818511ce4fbc9af63a4b585a91162e904a9b1914102cc62d76c0ef231b0a8871cd8c70859277c8f47fefe22f6e1cfbd404595b04e1096136ee6dad2f1320c0554618a443176f7fba75f5cd02ec93b1d6c287acd2b22d11a2ef125c64db5ed6c3c1b934d896f38d7379deb9b94eb5b93ebdb93eb1fe5a692efbf6952d81717b0db9fddeeba7d39b8833d834922db63ab5cfff46c38b335bf60d0dfddbe3ecc8a94524a29ec567fb6e6173ec78856ec534fbefbb77bf5cf6184f5d5ebfadb572cd5f7ef2a7fc3bc9392caaf1da59b8a7db45c569c0ea19bc973f66c6676771a19d6e7e25629e5047382d0739303054f0e143c5e4e1076c851e244cee68e6bc286d549f3650ddd606c6cfcc25c655cbc0511d3ae4e0abbb5dbd91936940e88efddfdcdeddd8fcc03c277738e6bad5c6badec634e2a8d0141b9a7ca10bcf42fa5b5c563f1421ef63311f3aa9c5ebdd1e360a05f12fb5e85788963bcf4a5190dc4917f6dc3c6ccc4efbed07222ed93a95f7bee0d12878f539b8d7984b435794e27e2cdce2e1bee372dab58f771b56ed666ff154362eb2da388eff09c3cbf76cc9916022286146cab598c1613938160fd56abb0ba94f36b8828b9ee3a52be42d2466e0be0a122ca7739870a9c6b553855b4147039a70a2515055acd2fb4556af685f68a0dd95c07ad6ea80828444417f6882df241e948b91f1415292a1a8acf9f97a1b899c1dd30cf2af151373faba24a2c103178e6d7836ddb73fdd308e386f6358593cb393a57047039678a23d7aa62c952f12a2ae9760cf6112ac2861fd475a9aecbba1c7418d8d0aa9e792e14abd33dc065287cb0ba5b7356899798c6a33063270a339044a184158dc2140a3333531dfa843a71d0e9ea9e6891ee5837966d55fc0f2a96ba283ef29792e2f9a03a2a4e5d2c1bf683bafe4e9a4f3b28dd04d0b0392eafd4e8e1ce0e4a66850d3b218461e47e4eb1d49d3a55ec5e1de78a23ff9ecabdac69a75b75379dabeb7ab4276cd831b93e95c8b00639ed9d933b9cdba91cb4aed00989a5d2732564ea8e74291747857374a0dcd0ba9cc4393aa8eb6fa9b87dd92b50eefc424b3bc6f81efbf08899ac4b7a7cad2b46bafa2f4e798029ddd6a758cb63eb0e16b342c3ed771f22bafddef58b197345cbcdf4d39ceeeeeeeeee3e9bdd99ab157daa5331f137b62230a653319502638c959f9f780516824b98fc53af309f629ffb0974c34ef5e90a711d0442688823d72e35c35ab121bb68e015f87a4ca562c97be091bf102a228e48a53af5a383f82f71753ae5c2a838a53c2d375d1856d4e0d6faa469fda48d7491be5139d14e27f719d7344dd3b21e818e118077000cfa67ac4ab310a861922038f9e082fe8149413029f6e853942c3b7eeaa54005d8028c012ec508691624d02978cbdd3afbcf6f1f4eeb3a4debb4f7ae7bee66bc070522a6caed6810d4e67633943ef5a9ebb84dcb280e8d5d774c5a4c2f77266ee3a92b6e72942bf1931b79bd401004411004634c0c08fe8b449b066878b5a9afb749822db0055a9048376132b5686ee07ce8a15992033b493a1142448fc1e91d4aa92d02f64082ab504a9c14f68e9f5e6e8a25fafefa2afd5e94f0621f3d629a949a081ef98b44a1261aa0e18579e1f763dfddfc5ce88f98f722bfefbc2ff203690bc0d8c1f0227a898f86f4781cf192df6b94801408be3029afd7abc5741905c92203ee666630c6c44422bd43164b4c34376445ecd02c49f1f411940df9158179bb09ccdbcf31344b681dc2fe5d0349a3321ef99b6a6d948c73cbd981b2aaab561a50486a18c23da723d72b77ddb49958dabe5a79d5b5d65a2b00ae7fcfa461fb8623607f1afaeb007f8049b1566623c648a59c3c3d7eee1533ecc25ceca2c14959e561659ed9c08442e94441b23a926a0245c504859aaf5965f6984c2613f64ec30ebe0975e795d95365be1c44a1e6cbe403e5677e2119e8c1035c458969e1bf6bed6cec74f1a8fb9d9e64fcffd9e2c1d142b294ed1058ed50d1892223d3cf3a324d3f7e0f6a817d348047fe50a2f85862008f3c472785eb1fe327539b7d7856c5cac8c8ccd9d5e486e33073c61933cfd9594dcb34add33aadd3baaefb743341a2129447e5b26f2febeee69971b7d619842174ae022e07c148eb6eb355636240f0bf0b85be1b5c669665bf65d9fc7c97755da78475da1ea5fc30fbe8be69f7a3bdee33acfbf109c2913be9843e220028638a8986c80d2f3ed02c798940075abe9fef0ddd0510a260d184deb7f798aa5f1347feb5d6c00de1aecb73f7c3c50b63bc1edfdfcb0bfddc735f88c39d89da950b038fdbef857d3dc0ddfee6b7782f2ff467a6dbdfcb2761f96e88269ad0b7243e0e1787ebc2f6fde9be7624acdf38f8860d6bfdafe1beb056ed0bedcdfa7b6d5108fbf705c6a56dd8eeeaa04887e3780e34a4941285aab5d65a6b8da55aadfdc7bcc6d20c0856d156e3a01391f23a12ff99204b582d870847eef61b16312ce459ef35d9b9d03fb7efadd66cef1d90edb7af4d3c36e417aa8883de375f9f6a7dd7409fee9959a0cff56f5f530873f4c70bedd5bef3386ff3c21d57abb3868897a4e4c84df92178c00cd2dac1280f7743e3d0d216c1dd504a29a594524a29d71dac0d3531774f9783cef486de501b0b56ea4f55b4c8ecf1a889da3878a22aaa8ab1b99e14890dc3ba31ed62da1a7606e004145e04206a165c620e7063c4300cc330f70e4852740aa618615e3b543b98ecb0d991bafe305776ecd8b1634794cdc26261b1b05858cc94526fc8d6bad84b7fdf1717e8edcf46b92c376461b1b078056db4c448c9f591aa004b144015a2e0bafe286ce111052928f88e1da6eb3b6caeef385ddf5164c70d8b7dd01e6c08e382e999210cf6d4e31ec6054365767f493e2f3f302e98294cda4e0ed3e215b6df7e60d0ff65caf597d6f5a9c3c2ea3cd191fe4428110ae6d5dbc949302e077f60124c199625040bebe6856cd26e29a58c32561d44295958bc82b3b03a0a64f4615cf2fd61ca90eff243cb6261498f7a399675fd356f05c3927d302f110ae62542c552f62ee784695dff08c373fd61a688522214cc0b75fd5140418a9326cc0b06e6958289ca96269040a28a571651a4b841af2071058933584c94296ddacc9c84d7fa3f13845fd98b88a9c6db302561fd0ae960606d6ae2287a5324d6566ec3721ccd0469edecccd7ab06e3316d679871aa5d2b57eeee6ac44e3a699db46aef97462dd25a3bc0011ee29d83f3a87e2e2b8ab55315731013908042af5439f9b031016b0c638c6b5a84611886896229862dbe5580ec13f2835f28a851c2d5814bcc29601c98bb70be7ea8f3b34c638265f681d10f6362e091ff1831425e548a3b80627deb884aa452562b9a56dab1d68f91d8a969d5f3ab69599326d055ec186bac22512a954a3173cf0f239b0bacdf90a6308c7b5ae3b8da5aac35d68f59c462ac51fbac56eda3b41be33a272ce7b57a7711a9348e70438a4353760d1b5a9ae2157c73818d1a3575777757ece9a47245e0371469ef9f8357c87edb6615bad23c1445940dc4cd2c5976b79440d80102ab8a77f2630f6364b6ef8fc36a958108027a68b4dfbe1ff4dc50e672ce0f8af8278a23e7c17e1a3cb27f3598b821ebf4906ed810bcee52d62a23d3d2dee02dc794921811d238a1c53c774e46bc545b3c3ea5cc49e79c3d77bae79c734ed68ecf168fedaefe56638761d86f3e27864d6cee787ce74e37fb685139a1850d61e525462ed516d7b25b85db1d59764b2de4697dc3b984feb9848f19dd9cf4fc76ff6c4d9e8f670f10f446b2942df3a41904991b65252982374a9abb6347666e80cd8a26b437b633f3c7ee363a63ec269d9392f1e19d0b9c8c6c2003ad57177155b9fe4c5b4747474747876594524a29e5373fba7210932d256d971e28a238ddb9fe92f607c749b4fb2736b189cd39a54e2cc5c480e07f5cc9a6dd4dbb25c5300cc32a9512e5fa334f4104e77c8ef804216b9493ca0f7bb5fb118296ff11ccc979dd789d147e709838a95d2e2a850a79c5b647a7a03a5ad8fa74e5a07740b4dbdf0785c507553b57e792b53ee95c5cd8b073c1d0996c3a2a5d4fe772f08343a1509d0fce47071785ab1d72ac2a579cc41ceb08fb972573b0651c21e3ca4b63f385d978a905274a8e12ee45792675d11e7a25c3b82b4e6a31d8460776614a3972e4c84c3f4a29a534de9057de45407238b144756aed8fc3c1a12bee8a83de991c049d7b75a6576c8fae60e0d544c3a52da594769482b1c4341ac63df8a23aafeb3fa5a43a74478a4291939593e41327c59d9d0b60988bca87d50530712be8c1838b1f9f3b498f104a4d9eb367b34874bfdad89fe875452412a5a89052b2e4ee0f8788ae7c2abad405a4f463af768cd5c9d6842449922499c20d6c0715c48f3f7f7a610b77b2782e1fe6b8fd2d6fbb7ccbf7852ddcbe1f455fa9fd789dc739e8b93848b5ee5c989ffd4cd4bcf873480b618e4b317961e81624af3aa9e4bb91d625657ceb0aa142486c95d08ded099d4247ec4b46ebea0f2a8efc49d60656e5200d4d5b1b502f78e44fd560d09f7af14179516fb7ea9e38e811d17efb8f0e9426dde4a49c53ca18a393ebf3f3e4d30404aae2654db01d14073fa01e071de4caac5897bf1161d908e25a15a85691bdd2e3a0b750252261ff865ecf099757693cb9a1bd5e8f4be552599775c9c412f62e3704b9ea0b6c586f0872815c71e4a73802b942ab02b9402e99d93d2d8ecbaa1c54c275e1f55c7fafc74954e5568993709cf4c4aa9c14762b90ab7e287219b93982ba2124a92a8e049b02a8b4ad1716b57468060000003315000020100c0544429150481e8a8ae80314000d6e8a3e765236170964498e43210832c818838c01060043088081991aaa4100040a6d09274507de4cb5ffa00b0881605f11a96660b1b780bd9188ca25653be6bbcde3fb64672829fd2d6008ba4e1b9f653ca05ce82aecb1ac227ab7c42185b6cda4283801e3a918d3a8c2838210813acace0ceca3e63abe3aec0140a262b3ca73e9a7442ad454f3682c3f120f616b680a3f652ac4c5c483ad04b91e76e5a495d093d23d44644dc2ad66fba310793a76788cc6fc041e11eac0bc7228aa7ac8b9d919db8203a5ceee42884a0e549400b251d26e1765af5039da0de59c225b8a76ab51f933d3fce42c97cf3c877a349f95bf868e8ecd38e5cc9f38494f42716578b20d40f9ab737b2e264e5e369a89f932bdea73e62a707965400104a7cff570fd3339c8ec69c99fd69e4f233a38f764d09831e3d71d0153d759f526fde488a1f32998af4ef13ba8eed2720a51db6ad76956e4abd6b0260447140f24eda6c5af8c92bc0391bccb06496d452e3f45328e7dbc2618b2e36879c6a1514283e4fcfaaa523976561410aa84649a61ca66ead0fe88a0e4ff811031ae6d44f61899a6e059f3c98603bd19d8cdba9bfa48bfc48fe87c440263df7911fa2ae4b42b51ed59a5d5d2c97f625470d5003187835a1ca97f96b4ae40356421520a495c6fd6b91eb2235b6eb1b5fd17f2ddf0ea2101b462ce1d8ac86d14a6d5558685ee4790bdc4312e0db69c55d172c7642475c83f97e018e935823e58723974880668a97c6c085a3f68d8424bfc2f95b3ba510e2e527695c87f9f8336dcf8e1ef6022351557fa05c0bf49e0ddc4222dcf14565ceead95a5a8b7cdf38bb1be720715e976b757ccf592e68ebac19b5e363a89d11047c97051a9d2fbfcab13be9ec7fac5715e6fd2b19ade23e65e6c00a8a74f56d75e7ca57100deee9993ff3e1a74874f91b4ed187da004b1b163c841ead976a604bc54645ab9320ed0fb82d36bfe6bb163305b6ac7d8769d209d034b0528778c76f3d3b8c698bc3b9df0828c195e8b1aa475375b492d6efa0af3b57f352d5ca95432a2d9cdd1cc483e52b90fab5c1fe6f91533ea063c492c07cc3fade32587d6ba4d9307662a2ef88a30291e5d341404fa82575301a6119a17a7a5de79d5c25ea838dfd21880d8d757c3b1393425f9f901b3b28976cdfae877891450234b172576081285e3118bdef6207c66ffdd21bc850a66ec567054fd59e23bc649754de6589747992e36397fb334ab8c806f66dab052aa0b78744224c71470a86cd074eb9824762ba66011480138cb1a44603200edfac0dd28e2331eb6ac08aff9aa57b38805c5d601460392b6c1ef57775a6d1000a70ff3bbba95d800c5ed89461070ba15c62873fdd1819e81ba9f5bc515e3e7b1861ba914afae979a57ede020e03ddb4249fd6ad8f461fe52c9e726a232831a875e2a967fa27f76106eb5be6eedb9b4abbb405676f273868bd9f118fc20ef6f56ab7905ae7748dae8a220856284a31cc1f02f88086e3269bcd32292edb33c6b7de12bc8e29559fc1492801901383982b415d96b7d0ff9944a0d6484e3e30668ba7336dae58dc6b64546e09f8c69e2e5e24637fab0f0140a6aa2439878673e995c9012d86f3855ec782ef0f6f4e21e5125f0bfcf30c66fc2599cb032d766f56b57f146f6f6184900174f43c5a56b85917b44a60b63ac28ee6cbcc9c107e9670792e41052f704442749b6e6854179e7ed31186f43d9f85fd1699f0c3dba6052fef1bfade1416256571edf75990eb7ef4e7d59d112624848960abfa84aea16df8ff45d80c25397ee66469da9520abcdf3d8271a2a5521c06235aa4c4682f992782867bede71c0d8a91968fea11b9657ee09bf6f492e77d754c02be480a82056d027aa38508a704d2c9fd0e2284f6828d66c89f8200446e04287c683df5e4fe475fcdc0e565f54eb74a3307b13f4611e24925be8d446be1ce140b22c3ed6640a558bd46ad8af56314358c900c0d80922ea9b1d5ca991ad3d1a1425a89fc9b3680af8dc43bb0a12aa8f78b8410216ad6581685b7877a50c720b9e5240fbbe671e2ae8b8a163400525e245bc4bdee6adcfb5cc8b1cf7d2c9746d02c95f8a8442e543fef260fc420202e4c69bf1b4335482b464a1646e00f18a0a01389dcd1928baac3da98035cb11b477d0c6bbcde3274c996efae0855644acebcdfceb7d55249d6d078cc3689e6dcae3d272cd156a46b16b4c45f8ad5482ff7516f9bb35ea3ffb227e57f7bf176a03fc80a633ffa2c8f184ba536f35dee292c78ef8456ead10ee2f7b0b0ab94fbd217c516a4becc077f148a53ee0ba059eda21c671a199f19f181a90c944568dd77092f1655bbd841fd5df7daa157ae93b0cd8e8a11d02f7198101c6f6e88a3f3ed816284400624e99ee918e0f86f26fccdc6412fab18649d459e0e796850d038f8ff6a73bfb6e5583d6afb7fed264c5cbe2190dbf928a498cca290206bb3f1aa29335f252993ba9e6858cbc5a06622ae229246d679c24b1d0458dc7cdcc9466332dcdbcd26a2d5269ba0dab3fb41168f94ff5a55f3165f62561d3a3ee8f731e049bce3ea1e4ab9c14e57bd8147a528aaad94735aa1d4bd7a25f4c618c0d673767a6811b4a4bdfae144448477b1ce977dd407561df797411f9497d2c025a4242ab6fd48eab5404b2edd4dbd68b3d7bca9c02d9afded48a9e12744f309e810cfca0ca3776679cf8cd77ca4dcc57160b9e5d3d9321eb20f9d4cf4bb3aceee239ded35d9ccfa14ee2948f0ff2cff7a11fa1aff55a34b570690a8d5cad73f1f140229e2bd0b1c3dbc48d79bb463b8ba701bc3a43db1c9f95aeb9bf844b67ae7f0f219bfd388c6b41894c5a44da816d20373a0571742747c720535b06dbc81f93f742e970f5fba81290dfe977be04450c04b89435668c3f9c59f0a66e04bd595012273daf35579f7b4e5e15a4845e2d8b0d241e0de3e64414caa53c96ededbee365ba28dc984d299422fd78b4a79d10a5ac02fc4e473a7e0927a9e9653c3a482f87a926070a1a82a58c29815cf5abb733afdae18039b0aea0f4bd1f269a0ad2efdd32b8a282597451223069ba4b5a6634f379e3df5940d6ca8ab87388b53823b855284bbd44bba4e545635d555635fe8d157283f63915f17f51517d899d5deee10334144b1d9a8deca1b62f1b9f2de0cd03309d21987c2cba564f557bec268f4589b470f82090b3a795d6103640b8ac9fc1f791e39a4ad717f15416aa70c867070308c1e520e048b278993750289c9382fbf1098e30ef91d29bc9008cfbb8734adee548dd550505ef6dcec78cc7cba5c7318c81be3617a004f148a5fac70754dfed111e66ed7217ba20fd1aa3f7b9bfc11786b262b043b344e39e72ef3e28b0938ba3601a0155e5f281afaa2c1c30ea144efaac6bf6048f79ac6716694f8bb8c456599433f9d0ee3b8200aae75d0c6576e74e6e41cfcee51f44450acb9780b992122df1015ed3a5506c82b50cc225dc0e5a558a1cf5228d52aa2c3b1100bf452ed966e68ec5c96609445077290eba02374c246aada12077df6ba2931a213db11651f11b99d8cbac16043e281eec9e425b6bdbb003a934542f403700ba2615e24ce449e43254613df351964bbe2adf5648b8b740b44f2632caa7297abb19a1539c920f80fc27f115b1664d7e387e5d8cd05f9423780905bc85d235973d1a63f1558872eef30d2d903d1ece26bb36f517ab9510ab2503b0f10fbb68a14c88263b621c5060a68b2a8ddb0c6b51e456e5142a64bcd0beedd1163fe8e032c68ae46e41be2c2210a175e6c567a1a8f0f8165e3edf68360e37e677d96cde158474fcb2d92ca1afe82b48e8fea66155810b6f14e69c08c00ebb84a335163b681ffa5720c80bdabae5fd687d58306917ee0cd616731ad8379f7da2f4e86ed201f5ab0b76d7549ccf3d1f18dd2d84a86d5a098aeedc1dfe292f5fa1b908769d170de21e01ce1ac2fd31c4994238fc130bd41107a7cc9798607a2be880e7e31411ab0546b3861a608b9069728f7b843681dd2cd0797f59aa43fa53378b890d4c3c8dc2fa9ac0286ba2481c8aa34452e2c915c1fa79dd063e9490b906af5d9752234884c40c1d8cf6e82a8d626208107003b05a105c577a4ae6e57fe7a0c8cbb9fc0f3bb46b78479c85e7df1a99fe52ed85e7c64bf4755536352442a9db985db6e930b96d04f3ee99d76a6f65c2bba6a2f24591018979067b8fe5c8601da562b3c28d4b08ff168dce8cb4fb37f76ed9176d58ece61bcdea95d144a3b652a6876caf69447b375a764d7d54a0596537b2ca36b773e05c700dcdd41b7276bd4ffdd02ad84ede2ca82836620ca8f8de2bf0bc27cdeb8c7f656e2541b4c9f0baae9de3f32909517669c107fc50658c630665e37f893c186e52daf9c4221e08547c3f679aae1b82f4f0b57cdef7a484c2c0eb8bc8dcb724d620dd986da43a5ff424520a4afe7d0e1bc5361170a2c659d1909f6acc96ce68c7898da98d37c210fda61ca3157f189c6af9f65ce697666023369dcd6ff856081306f6b28917073b1e69cbe2b0e380cbe2997dd739d7ac1b8826a6a76712802cfb67cba2269c86f86a87ef4700a06e8b9cd087984d6251ca591e3fb5c3ee53550c5699c1f6e3fd4b46217d2c394d77a9a842b8286d6bb270373b33a3481986c11ccd0e8bdf83e1133467af9410f48bc76c85f09c6065a27492e5f61d5f951fdf04a070548f05f376b163f021139d696f6651b5924ef26b3c8e9cdb431107708f8edb0fa5c2d5cb88e9c4e282e0b5d24fa85e072085603350207adc75766633524c8c2d6e522bed0fb4159872b75388ca3099b61d5cf0ba5ba0aabf7c758e39acea1df301581baad2ea5038bf49c435f22e681f76f4124cdb7fd6e8a8dc9919c612090f3dd0515fa21ddf07c3976fa059e76aef85dffc9e63aa48d16dcf6d3f3fd0379bb1b77aabca086639a461ff16ae39afcaaa576f8bf72c715ce59c06e91fb14b398601be44495c5567020c758bd35f95f04bc47261bdfd0acb8fda0fee6ab44614913827dd6c46e051009bb13cf6b9fc30e3165bd1cb85dde4d5230d98d4830387ffaf23987db3995a72b2e47a80453baadb0b13e0aa8a438bfeb94db4302c0580249d3bc5c3591b6ee7cbef1bbe46ef84055b46369ee1ac5351fa2e2f2c6cc24401f2c2e954a9e7a6d6c1a7ac513bffbdcb6f778bd6ff4a391f73c629fb07062799bde6570ea445bea2c3c7884a87f9b790be24959fb0c66f5e9aa6dea1c5b9cfd79e65cb0736a8651c54215b66099e1e03892557599c024eff338d471f28faa43d98e973bbcd7ace63a0c4da5d0bd691244aef9662acd41898e19768ba076d6bd302347e46e9f7fab90d0cbd2d175dc45a18445de0c46076e1d9b43571a1e0c9f0b8e1c612aba716e3dbed50214567a102d926937b877b27d77ab54ae844408c779cb29699b24e26fe7da59beb57bd17c192e4250525b98de14b6dc8e5f66386b85ac5212302fa3db97cdd313616e14c7d3ed8006980dd06172883a492798e9785d4a1a66e90349dae6ea6c80abb606c3999be76043796c4fb6406e5a9d9304419b6c9d9731bb6580f0513fc0faef60fc72d11bb0eaee373095b8c7c2dfd8513fc00f81fead68ebc8e92755ec79f150a5ae8226d2ea30c16603c62e041447325cedf50437af02625e21978f84e8e3354a5f1c00bd8e0fd84e54ece289ac41135ff9d18f3b1f487556b255e74a0177a6a785a09587b81c50951a249450e39f7785994692020594aa2b26eb6c6f49764c47aac4f9cb9b2749b4315f0ce2843453b72a53b423b00cef9887951d7355233bb8381f742555371ea50d913a96e085ac350c35b4817ae440a19f23dab9d8e359e5de76de1182735d0d760a0a4fca780974fed3c11dde70c06053785afc5749d254489b997ed314e017bd633798a907352f5feee15bda375ccd104a2fa15372de698ea04e3372b9781965494550189aea5303caba51cc10ecbdc47d932b1a00c7953d7b31279585d37ad44a63836d6de636f38d801a686149a3de23c298a3e8fb9c9a384c452c7b396d1c143386b3c62ab93c1090bc8ec1d1df69204794190f1c300e14092b1dddc86c399a00f7835a3d1a7161679105950eb7255786389f83fbc084774a1995dbdd68f34349c3d900b58f057d0b34278b22505cbe3dce088b824fd8a13a6b2072294e8c4d251b6356feac47d92801a4d86579a25f0e55fd880b338badca19990b558cdab1b9a9c5a1642a2e25fe6002f10b2e9e9a2570d02a1da5065a34abb299cf5da279d11d17b877e4a7002158855eaa48680a2a23cbb13aa7e2d4b4ec6a6e18298e89de0848b93e85b22901ca789b0e20f491b6eaf7e52dce43d33125f2d8b38758c05dbfaf8aca7a3cf30640288feec3044a5cb2eef6a8d40cd36c3af40acf23e3782269a9b4581415c1da24fae30d4236701ce659f13e5247868a0b562a37abb6001e7a965535de7899f3a842cea8db44ce2cb7c5bc95c0f3d2dd0d0b02dea85bdfcb85a6358883f24160f5a2eb6cae9193787de1fb1c84173dd7159b7be6e0c0c274769217487d1193d1a56db3e38234e293b7c01a8e1a4479f05e1a7b93b1b725c17d44596e031f75508487f85952f1dbbfd849de2cb6ea945c8016d0c0c4757f5a1564bb4a7c88bcd9670fc2a51f7bd4a7207bb180da1fd889127ea02a7ef410e76bb1f92e45124cfd1f4829092c3cdee4d9b41f35be86c1d9c95d38cd08762f496613a4b16b9ff04223cacda6e2206f7b8a646598ac22c4602a9ad7db856b157c9eadfe1285923c857d4c71b7b1ed4d3208f4a635008e1ccddcb0a4ebfc1551f3c1c84c0363eb972db0710fc2f736f29020fc1d4f2057556acc92c92cd7fae6a0a8fab8791d6e425b425c04c15396c2d9d81e33217c024c9148c5472f56447b4cea88e03dddbdf58afad81958cfd74c36189a706951e6e3722ecbe639b9eec6b98865d33e4f353ed21a80b19b385f99662994f89931174ef7707c8f71300791035cd38a355e25af9757c1d238f36eb0bf65d48ec0634e1a2120b9da0ce92e0e9c5d646da231a1a8338bedf03599e3b661d2ee23dbe42a31bee5e8eeca78bbb6cbc811a99a3f60c3e55a83540cac430c1fd13cb184d812e4b998733f771c7d3ce768f50967d1b3fdd08c57473d7119e94b48d16fef2665c16477c8e45d81de9ecc6da2c7bc6c12a80c3347c33d1053a62c72fd4afd6d544efcd4841998adeced075ea0ce84a6cee2ed6d8000a6acd98360ad85f6f4139b71a02f299eaccce6312034bf074772f68fac3e690cd152cd8b70d0ae6a0d5e5525b3953ac0620db29d74605413f7e76630ab63c132b726eb1253f63ee75328e3fffcda69b85bfae6920ad24fdf01f5b5e7f66462004f54f735686e9d4bb0e7f21095dc93c82b91fd4ef75c8e31cc434f31cc1a1be80caf4a902a6953db6f08e7703d82bb12db4ae76c1af720e8f2a89d3be00e0ed14dc79398c022b258de8c215789dffddf8946ec99502c97e25450a77324836731619fa5f1a29ca3bf814d60c09115680c5b0f580a5bf7082551de37163be303f17e4fa4f71428c30098bec1c7b167d591cb40467c5c550522a0c2c590605220d7dc2ae8faa11cba621e44a6cb35f18b873f2e3fa035b3edf583f79313a6b284d3515be0dbcffd28b7cf2e70125a3d4dd379870efb14dc9402448bd45f7e09202686ee9b178a745e2a9af5d89b0958f0785c7a2ae1aca3cc78cf403b5aeecc1ec918039185bc67d26825493ad96408ef7f178de93eb57ac9004c904f6e59fb89099053de52e8bcdaec50a917395d0edd763b7b1c6d654113aa4d5d3fb2894588ed5e5cbcd9d667100a2dc7e2fd062515995c4080c745deaac229375d218c896b5616b5bb4c7356e62e00fe9b85396ff798bba649e06e807b341c1f0ee42ade861ce3718afbef5b9ea0640ca59eb416fe2b36ac092d5ea5057af36eae97f6f4cad3d2ea4d6de5930ee9b5c1844a713574883313a47b63a6bd290f439a805a334446999d3834e6ca637a23a86df2c4e1bbe97da2016570c85d1e0c5518e014ea5c7596e3a4dc0de966ed4fcb1ffd31ad3e38f4c67e45ac80b70a7860a0f2128aefa7a573a7d9ca7bdeced0af5bc552d7c54da3a31b5ac8dbe6784deddebee2ffa8db68f20229be03c6848f26a48cc9987455a20218c072a1523f933f052ae9e8084664676e361d4472889bbb7617769522a0a61fb3a51154e7cbf680bebceabe0d815d3e84ef7cf422bf890a83027ce32236b99b1a3dce56f04f1e2fad3ed879168839b4756c43a29d7fc0cce47a91888d05624f0ddec475b07201174b155007b61df3290a8df66692173b67751d5d59c58d141b582760ce275f8d22db413d228535646b1d86e5f3000a816171b73fc01f16c33e4c982e56b45a4c5fcf5ccc60d7f6e96702004bdd0e546817336e39e35d9ea6700ea90609ff29e70e65c2c7b6d700b27dcfaebf32e7e5cb5f0461aec80f332609bdc8d74bb3e288ffccdfcaab64548e481cecb6ff6663cb01470fcbe60168b64b1251b392cc9e6c13a3cad7b84800d027c4695e0d0008d5d7e12a8f0162d02a694e1569c8174a7649004b58b1ab27ed1119f0be02d46313247742b2265880c8eed5f96b3c8ac6074ea662fd1fba8235315fbbc3a142104f0fd51330ad1ad837855ec81fda4a463f0fac7f7721ae02c3f87ac8b7981c2a07e6774a0c2215455f7b807b30f01f207bbf6cd8e0dbe3bf380d2fb0ab5db2b9910c8750c0229cd1da120ab45118f40cd0093e6b26423b262ef663adc7f68f0ebeed8d01b0a7f3f3e1a4d39c3f86b897f9d7f13bacac0ec9303be1c85380cede888cc486c44a00452b198b30f0d02b94c82d51807d7d6197c520add863ac05630d169e069a4413a7a7a5a83b212cdb83901f64a4e9625af43f53c9f08619799e82977735429e4406e92b5b8df4aa77376544c7d764c1d242cc0ced80e18a5268acbf4aac6d0230a539cbdb03b99bd4907100359756fa0ec7853ad427244586b59aac0aac0de6ed00700de9af1fa07582e44c079cbae29ec0e9fc443fd02625eae60d438fadb002d763744b21bcbc98a2257ad0d0284622310445d1d6ecb9a7f401d4196c8c7dc735b927dd4b9baeb08d264538b42a281d638e70464e43f811e228f45ae7e0bb8197c802d91e08bde6e21c7c6f2a976ddba2cabe73877bdc96cb031b3ad858b3e938d74487f20546ed3f5e840541ea07225b66dc5931335c8e012b68317d82002b731b09da348ce774ec02dd00d083a80d228248855045c8eb96314e4eb6aa833aa3e00eaa541f3a9f54df611fb70330f1231a79e8269646f8ba24761e35d5c5379398335e7ba6bb33532aa837ba205077de115c5291565312c2f635546cbec8e1b4e26e17ca84de3c0691a2179bfd09f997a036c2d80526d22aa1a931c1685bb5691df3c117280a47985deb4f70e4f7ffe9f50bf9a3d551597d2c5068433e0297fd3d7d21775984d870c4cb0fd50e7ae0cafe5ae00a370da9262577f85579d27fbc5251ca6aa47d3980ee35abbc768a7055eef3b1a491b22c8ebd9d4d9e30c2c604eee30203831e99458122a5842fd215976d5d8a1534bf45a032cde4575fdaf451ce2be026e7b729cbcf155e315fcbf9bab7e3bf557f2dfb8aa73e687ecc0e87c9591e82e74d3af6438acfdae216e8f7c816988a0d86614f5c1318d83c06a65bd954ed94add8aea9f95a1c58aa09be277ed49236e8b6d6e30f812122ec423dc1d11f250d4609ec882d407a80274318298329e720708288483bb31eaaa459c9b2e9ffe0dda81a89b6bbbc9bf5dd9ac8bbc674874ce24281dd37fc9bd3e0509a151092a0fd3caf8dcc513a927aa3fae0991e9c430a0a139326db3f08c5f1fe8f9bd5b0fda983f4e57c93a3026993cacf1441b21d30f48181f27fafa0f6bf12f3f654a5bf68efdc02deedfefa27810b9d039b845471ce47106e08e710f09e788b1bf3770f56a5e0497f8511114cf26bf59bd27bb64af38ae71e27c2cda126cd7720fc9bd1f33dc9102110bf27e138e1b79f80573e954dfd8884cb62276658d23c34061e3a96edacff4a0e62e6ab2127becc955fd46333c673e6877bb19b31d9eaa7e67701aca300d18e769ce1fcdb514310a2437a7e76ea0363c1bf690f5aa59860b91c2c19de6865284c91afa24a522eaa37767932da78e76c29e99878eb9df511045ee7d3138b346fa65de0ca365d93e150950435961f0bdd257d00c52a0a8efc939f28f1a6534648a1494ec00df839a1d072af8618925c6c279e518dc798d78e36b0f13dbde431dbd8918b3382e4a1f2a30bc3219015f084fb215f2e917fdd9a5ff6f258cb213ef2da7d3b26a2bdf56d83822bc1d2de37c76fd874c4e76e0c865a7603375c1a4decb062f642501213896336500c138571d1fb6aaf96e26bd4254597bf68d258911861987a1ee5b50365abe2f0c44c7f47693b180c2b5e6c5b91c1b062714e37b36b1013a482e6b525fab71cf56048986a0a3fa2b4667836eff0ba729e607c8a170305d39d278c320dd4df132d10918105513af0ee158f84909cc662dfece96cb86f4c1b92383169a8da08c0e05a841ee449f1b921035fd76ab4a6f0dbf4816441f74d9c71b684773d5f894770617349254741d77076b830a8c785c4fcc5fa01def37184cf42c5ea0c897ad44b86967e3f573334c627ff5626a98825d2ca112fca307c479ae41e782278f29220fbce0858edf2b58a82b4e696f41eed4565d7a68b20d715af917816c1ddbc391a652f389dca7033b9bcd39231828ef7b1bb78f674274e364e903548a04625e4cce3c3737f258db0dc5a54c10769c594a0cb0883c79cc85354bf66642b6b0bc7aca15c75153b2bc82137f17e2cd81cfd50f69ebb15eb6f467e8a503c395c966f91d9df1b59f83f3aa3ec2055a8d1d18c1fd956f63267a73ba0455361b2f6dcdeedc8ff08a7c02c6daaa720e3c242ff22d3d1d15cf70e49c9263c58ee2cf7750d249c7e5a27857b881c25da6c41220422778f58d14690ea28581b0086bd9de93ecc147970281d317bdc27cf694c90c96e32381ed09540cb05ef46205b96e011ced18ade406737cadd6006750f521b9f35a0b11454f36b5ee56a0b8ade1213fbde1bfa815ba0508653106ebb3f4e2296d84f5eb94727b8418d1e6ca8139abcab8bd06bdfad1965d0602ba0693dc32c9d25c7bee1d9b29a329d03ebc4013d1d23822567a381800032fcdb02423eceeb45bf458b5451832a813a69350305fb4965274bf66796ea253e44c51c10bc098ed92c626f5029338bf78f6012756670356aef2d39e200c79d8d7e57dd76855030e0c0acfff59106659f598de805e4548c37c4af0a1267ae174893b00a72a7dcf385e6ae3e65412c05411019b539fe4974a219776642af9398977bd2d0fb1ffe8085c720bd65c0b441f552cf4984f320c37df05178ce368e6fded9898ddfab8202e69d83a21ecbe2b2b914e2d17f8b955a128fc20608d780c61abf2b8db667c8707e4112f86688c651deb6032aebb8b719fcf048673a1d03d5eecdecf01825e0525ca24769776d97089b2ca13719741af8c582f61029e7e6215d883a89acda434148394762b9c513e390b75e01996131dbb7d1708aa5bdca6ba36209e88600eabbb0480dbe3172163b02206af8bb28a1bc2c251d7be8f60a02968f7d93a817eda7c0007d9d33d09f33939330b2fb8a6016173b5b415f804eefabbd72f81b969072d4e6b5a90f606b7156f6e586e4b01450ccd62913cb07af475d0a97fa31ae6c7dde689c4ca0a14154456f3f55d4e5dc5e41a0222a511810f88fac22eeeb6648654ac4bea0918ec26ac85b33422f78464713c2f8550db53b4fbc03a284a59ea2cb55e52f7090a98de423ae7980f607bfc2ff33d15b64fb7d15bb6fba59b13d1f0ebb694ee7cf15b39e10fe63e45f1aa6597db314ba3cc108640ac28b3c88b606b671db0d50867b5d54b670ecc876db5fcbbe9f6a964a9497f6e63ad17fe0fdde50eacaa105ad467cd662e5d111468c1616fab31c575d6c93dbf8e5837931ad6dd74faecc71b992d2c40137162934d3d966b51da72352303bee453c5e6aed3eff32045381a2f6056b6438c14f211154a81180bf7744508114a3cd47af16c41ea45cb886ea0d5e4e316c2b27832e4c53db5f08e7f831d53dc95bc019998b69b4578378f638b180f7de887ec9fac2bd5e91a5c7fa3020232705eb1aa9c0abe425c80a9fd496be1431310823036e4d258de46a6399a317bd4aee8bd86ebf0988e1392cbc54497e10bfc98249be08e1b3115a33ad51c3dd7fde6eeaeb9844929a4da99937de064a532fd457ea369451b6aa0c5e5c1c48085619f2cee1c8087ef599fc0386be20ba3807f60f223f1a85719cf3016c813b48360b8dde83a536b1890c8149b6aec7f854707cd7c5e6a42683fd1ca22470a93c8f08a8a525844eda518b3e247f050684be9865bb269d31f3eb3e8c7c18c8f8d98a7eed6a91a10063065767d38716701a2f3fea808ed0ca4f61af7005d3828ba670da9ea2534916a4ce4cf04e8d3a40f82ebb2aff2a22e816eaa94cf2b7f56badfdd6c41fcd589cd8bf1f7fee3ca2fb3b5a963e1176caddcc25bbce20732683f60ccbeac911b1c686f660efd8c4534ac0eae592962ff94c4e44099cb7c49ab463d144e6c109cc1711cf34327574276d5910f60f047a90ef10c93828ed7f610c09c76a81734ede15406cab37a7eac71d43efa6408e70892ff67073a57018e4a65c70594f7665cadf331793c4a48c3c47c6beb7acbd509c2213f847b51f0bd8de98d888fcbd8f7258b3521365fd6bfd05275d4e10c74e58a427eccb4f382328ae1d14f07155c36b462d334aed52299ce013e9469020dfca3e9c1ce6751e6f6bbfb4f7aa45f122260af8030a09679805f031957edf18cbdb9d1317542b9a46ea0f8a660cf5eb32281a437f84024ea7429b0405a4434265293de0a11498cd4837af76056ebc03829a0a763b02cb5867803d7c0bde1584047598d2b33f3207d3089bbaffca3fee8916b8bbf18f33a78b48e347ce49e07d8c05c28ba89dfac9a8d224a23a1c83343c3b8867da97429b0141f8480959adc814e83ff31554872612348b6336964f740c87fe6b4b6ce11cb81577ca421e0877bd01c7242fc108c638a26cdddc7b2a81f91ceab4e62179e92de6bf666ce2f2877847527a6e55e211a38100add8059d8ced9de9790ce01c9c4e512b10160bff5a1868125448ca65a20e6c09b91e31758e3448ee57012edb1b9701e284fdc5d81f79aed4183f904e261c7b7dd8e95794e133575681a382a42a0af3fb9574515de9489584525b20366fd5eb6d8552ec92db91c3a3213794b9d2547cd6b3583f88bd191e52d8bfafed8c7aec97603686bff80260c8a1e18f1c7be9827811424d0baf03bb0a25ca40b87371fd432c4358a0e006f33f23f126e29a7a38403ac2b4a3a4d5baf164ee5e2dfec96ad4a0d61cbdebfceca9c50f621ec69d1348b48d1832af6df8aa5575cf904b0654389b11e302582226e1419b34c786a4737e883f6c4ca71e5c235a1732ae7a783d612d9b03bbe9b5d80c03980edad65d27c29e8c9172c8a10366e996382c660a2aa6236c14c06018759f38a69632b7e4cbf3e057d5c3551db964193a9b15450295aca9ecb2d6914150e6c451c0f83d83a23d2ac56023a2ed5ab2f9e15ff2083c167fa55b0a0dc964d39882cf585cd6739ae0c1251c07088b4152cc54b4e3943dad01677af4284cafefb44f311db2577be705f9d5f1227a87a4080f0e56f8dc536c0cea5376525546b9762699d937a5360c8387fb1b117d8c7475b88ccc5423ff9bf43a4b22167bb700978ef24152029050ab31a52791f947e85d85288cb3c3a6a63132d481da68dd473e297957c0491c73fb1dfb62e2a47299893be832f84ae60a1d3de2d5e76d737b72bcb31eb15059341bf82fc96f1def6a0ebde97fee893798723e9b0f714f237a51630d620b9c7b85d316c481bb7b5a13a58b804c9c883c1b1762417033e727bef7d3658d95b58a65e6e3bf21f7230cc3b17eb09c1909fc94b1b1e6e8dbbea661d25019ac797ce37d94d6711e236a7e86a9137e585a76a432f15d4e6419194d2d9f7232d28034204129172035f02adf8ce5872bc1d51c90b232534240862be1f1ce780f47df46f40755023caa66e5cad204e2580ae4d185174409d8f68dfb1a9654c62ce58c24f0d2e8796da26213df131ee16ed06e9cdd18bf3f57e13d78b502e45f736941e598e224bf50b3aa82662b35fafe9f291e065053db80ac225491db02ef9f0666ec36c444d8239477b8197b22cf461c27cd6a48d83b28f233e8d9f07bbdc2ba91361f574467f2a16eaf261573762cb3be17b475f10e9c25d5bda6ffb4fd29a1c1162f264d328ebfce359402599cbbe420d4182697714736f08f4f0c3b1471e51eaa69e42971261c573e2584c0042ea935f3ecfcb86fc0356743c39c2bf870264ab3df41430c9fe43820a87140aa8a05161372aa992e1904ee4a6225032c09987126f8b12427fdcbd0ca688d8e2cd1f4d9ed9b806d7f2c0c75fdc0bf248a93e76e356d75375652be80151a825d929026215fad7eee6791da7cd8127f31e35644a8274a2f7439968141f2faf2f1bf1b6f6eaaaee9abef7daf41f6625cf12bf8cc49b4955f7c7af72886d99c35c3e6aa3afa5ed17a1ecbd30b28f92c6b6ee402f85f7de280c51c08f9577b35caed0a660133be8f1da0adf05552ed86cae2dd4a21485d15fb36fd7a73c3e0599f49fbab096473b38632e1ef0b441b31813cc2c8990e21bd1454ce9768c205e17bd62cbab52ab11151b35abf9dd3ef5c2d5ec793ebe1a606b2569ee4694140c8ccec20ea6c3b4875f51e763ba3227465ddc91249b87229297d1cea737d8bd56a79cec18aa6e282d8d68fb0cef5b25903513ba820495bd436e402a621e209765e8c31100bad7261d9ab55eb1c92ab31f4fe147d5ab29c888ae9ba426269f2c6c9821f896befa9758228ff78254e8380a5aed4b717cf1b56f6760b87b38c675d37954675c51a3cd150050274956f98828ab68deb14d7acd12cba5b562e6a0eb0f7ed8f04938cd61fcc20fa8ea450e42f914632e188334c49c010668828a12d1146a6c85c76511ebb603cebad8d0051decb41da0110669386ea49c21df3ae07c79bac99e55b39b8a08a52e6062a2dbd61cdaafac3afdc7813ac97e3a4d256661886af110eea57f1c45bb4a3a1be108a095bdac34f958762813e8fcfb98868b915692db86f3af8ece375a0423e18b7212fc6fbac659292dfc2b81780f8da09542184839681ff0b86e90b6923c96a7b6a7baa14355ea9022c056e4c2282287882af9b084ab1dceab3e0bc11bb8178a9280587a316900a5e401fdc38bab5c231f46f896f7c4fc9dcfd19bff2d6f8edae43d8d377877de8ff6b5b1ff0877f7f671358a086bfd85d201f3e435f645cd5169204f3f1de64fbd2844767a12f59d0ffcbc50004bc6cc15985616a86d46ad39f5f6421d86807ec6232f17be8c674fa7a3c456dd4e1ecf8c7eacc6051e5ce3e2ef22e56b2b4aeaac0652e481d2cb14aa97de2aced8e60f6b4a90db9d768aae1c1792e8f70859fed9b1696eb3461fb83d70367b6517a4395acbfbdb31839aa9c7b7a5b65f4e05cb7a54fbe5bbfa03b027b05b0b53f2dfdf801487f3a4bf8850c09a635cfa30621fa9d8e8740bf15332c7a7e3de5ee66e265154cbda1b3309d218533a22238a6c3ebd461f03ac13b30d3bb4b0085766fe97981ce11527f3b023339fb15d5f3abde14eb7689f6eca5cc132571bbceb303eb9ec27007868dea4ff36638a75c8110fc97334e6824f2aa301ebdc16eddcfe304e595a01dfdb47d2949f88c77d98ae9ec97d8c2bf745361c65f4ae52cdfed1218f104c777d9bbcc04207f5b9f28bacba2e8ec4bb1609b1cb01b2edd0d4baab756c40da43472e9e4006f2e2cecce3fdb8b932c368c2e005c6c4b8477002a41f7b389d5ea6998e140afb1660717dcb832bf9c763649f16ab6371ff675624d753f600e90da3f0b8165c7e9814605b4b69711920600066fdf3c816ee79b266f38e21d21cd9dcf9deffc52c30a28b3350545a80ddf0da256b9d1fcb0d3e9eb688d8f096daec04bf5839445d5c661df3a7cb2fe716add9e8d62928c1954c4745d4f61d3144ba55a14f44be18bbe3ea35cf83aa8786b55a0ae2bac7618f08b1faa6aa72be1f409773405eb20fc4ed389d6016cdc5363217fba3be19c8d35939f93bb2c1ba80df0c17de6a6edb532c7d698f76fb5d08eef9758c1bf8972b5c6675511a9d95963d99953bdf1ba9c5bf20e817aa2abe6a43464ad9300cff0b93c361e06bf53628dc9b611886d2412dcd880a1e86780fb18a1222aaa8fb0cb245f36e6e1e17f350a2785f1cefbc13d3aefa4c808485fe5cb31d3f702080dee73fe10866d0bb560bc5b22c140006e3169fade5ab86d57a662c41edb39e521f0ba411955592aa7c6d68216cce9f29339694dbca41d35ffa8df8f56e3d26c9d6be45832c0abc36b7f9f1167284596f6897a94aa7d22bdaef9de685286586754478373f1bc0a0252474cf744622d13c9fd11820f6420f06c6397f0e88659008c5dc49b2e8f8b060c1253989c719e37f1845e96a51cb56005c631ed9d8957c0a19c9b4f19e3ee0eaad76c5cdb3150431ba413780f77e04366c1a4adcbb40d184a14b3e2952bd019ba6f5954903b884eadfaa7970c5aa45106ca04ca0a80d732494d260b637d2c5a833f447ea20f95dc103ffaaaefa30b27849ca5196a1f30aab34fce492deebe756c19942e822a96f39a25f49de32551bdefa823b7865f487a8233040ab8dc0a37c376a1787f07060e162d1ff8c8c921ed964ce0fa7d8c5f9a66203ce325b0c1d27178e635a23cdfa1a87408d8d88759e755381854d425f748c2b41c809b77a209a400004a6311dd70d16f24b5c5e4168a2e37a8c85424c1e80d04e106a5b32022df520a42f33730f5ecc324a484295a9cd2685ed7c1746531855fc748e87ad6400352ff47114f4ee50caac11ecf2e41b4ec3e6772b7565a0d32c5b138dc3ec20ce1a187796284e017317f823fe062838fa099259a97314f30f198e6df8a59dc668faa7ed8d9d0ba92b66024aad98f44dcdff5b00f6450f0a1ad8b49772299c02a9cee7647072609bebb5c11e20b94b40042ea5466464cf92d559c0cf40b79ea60c90a00b1ea68768c8a8de1b0d3280670ce3301be5be784bda35cc9119a037bfca042c3f2990c5227ce204f610501fdfbbe609f7cec74d8f8f541f64ce75ab178c6c2a2213f21b5f468aa2acc36aca433512ffce5d19a128ef23b42610438c0040ecadf2c2e8f07162ae110799e65f44c5f31a8c524784f5881d3a97ce18a6d2017686811f808728142e44e2addd412ce872da5192c48815a3838fad491de3aada01491c21537851d811492c5a130b2d3548764fe4e76d07f5a6c9bba18aa112930224324a2a2c2c5a94e6830d2e2d184a0153df7f50d1c0bf25aa4c37e1287b5a65eb77786169002d7c1e3b4de4ee1cdae40b1a0eb2785b09e3a2770924118d6c5bf78d37af685fe596f496ccc1f8f44371764695f6d02df33f834add7b6ebb14dc118830abcfe11fb7330b44daa4edaa3c220378b97d3a07afe1280f468d4229bfbf631b18bcc700cd88dc662224b601a6f457624c5099db02732e89f9dffcc21f9ca1948df33a1433aeeb453e2f0a01e46e60a0cd1a34c17a64f5c6c58f95e8c97837dbc72df0896e58c596246a9188204180dd6674448c51f67aa6588459bca22fcd52ba4aa38ceefdaff89cbfa51f58fde45ea054829941315002edde1fa9668ce86dd517e0fab6b3740c6acba76d6d220b6f7889ffa4d0288b7487feef5342bb21ba0613cfc4fd1ef85294b22f6ff002a77c31cbc5522dd708e5c2af3b45e893e1c566ef0494d2a47883889cc872f1619ed6dda643698cfe0fc0dd8046cee41fec9db5b2d752a8d45f8d370dbfcbaaa794f8b665297d7cbed8726b3623c108b4cf026f28618ea9bc5bdf8fb50ca912c8b06c58e4d1a12d679be9ef770adbdb492c2a2ec30756e7ad3a6c8e8ae66024f7a7418031405332a9a0b603510fdcd42fc550da3c4fede7c0e47a344a256c026c15ef58114e6ee4ef921f328ee40004e409ec0ba9eb538ac0e3c2f3d5c148de220ae88d5d71cf9821fffbb41fe8faf93f7a23771209b7f97ea5c715cb85c994fc1ccc6c8ae4c3926a16acec4ee0b7b326befcb4a2728e25d1b22e48bd431d335a0de910774854ecbb7c66a5eb34731f43ec84956721c4e385b98644a2097033ee9d8bc509a98ed8e04ad81c172462e5948ab012a098498a349d06629e6f04c25227cb60b9d2487edb54a42d3694c53a3a8334f1871654ac78d13e419ee83cf900b4a62078281209621b67295d9b519a9a1567169f6e3673d5d527daa8decd3a7f08e11298ab6f0335c0c7bf2dd9c5340971218a8fa191c6c470a4335aec1b8654491e71df799009e2581f9716d7d7b8c9e131f4205c9320046d9c7413f2b655fd857a041d4f5e538f948dbccb56ff2f7ea0daa6f4ce702d4849810bef133bda07a2b7d10889f860447d893bfa7cd963e711ab4031d4228060ae4d7bcd3934824a6484c6925d38bb30275d7a08218df52426cf014c4e5bb0e10ec6b5d8bea1de0abef00a2b48399319f54dc8f2010908c49ee50632776bfefef1579b7b13c1aabdcdaddf1304a5ebf5bd6cba74c346f2e16d6bd2df47e6c4726c1b2fb38d5db6ad1d8af3a445950dec8b73a91487b4aaf64b9fa85203bf3fb46377496648ca36e4db4e457995cc06f2d2a0bf3a5e28405c5344ccd375e3898a530fe230485db8bf384b11e37d9267d2205d8630313b156a522618d480587ffd9c639fc18a2bfbbbc310b7d41785bcb49e0b304090d94f0c4199ad19bf371a220125d74c0c5b0e281a798aa57f0cc5e3ef6c8d3b22cbb3959e317e9d471f2e9a15ef206d3387782799fb7a316154dbea52cf6e6fbe7ffb60b91478c06aa4c05a91948516c7a46929c23b6c7e7525b11dcad83ac3097294c9ba1f20d4987d83f8abf49023ee9c7f5e03f9bb455bb7cadc780659b13288a060d8d496f4bb20aad9abae943145cad15473b1ebaf8bea5fcef89e4d4afaa16784f857fecd9a4cc1435743078420f5b5233c7282856786b10616b9244c7b998dcd1c324f79c410c3a2fa913c08f24bb2a0b6125ebb789a141776d2a6f9a4d7d34f8c952a0456552a2b1c3cb6cbdf85ea41304f7107dd2dcdff0ba0cc1636a853a2054d6bd18dc68277ea0dcc6d1191f7691852671d74879e0bfc3dea5eabf9ea682a5a1afbba43dc5595f8cc5acefbd65d1f3b9937b98eca1043fe68f748f90c82640343b9f0e7b20dc12087be4561e2a9b834765ca55d5bb67347588a74a6f168bd543ac0d722dedba9d175fde065005e7776b569a887e8bc53e64d6acac6a621f3c151f72d0b58fba9fb6b5a778bf62b7ded6f198e1e0cc62b9e101ffb5e404fc69a2feb9e059a9cec03a15aa59ff6aeef86d2aa8318e4b4e18ad58e2cec0ab6b08d31f17e0550cd22dbcf09a81cd528efb65db408ee9f8e52b0987b4dcfea0b0e23646198160d53ec948ce89d157f18c9d6a24960db69a733f748ee6b5907a1e9169a53debd9f985b4e2e3911818709846b5d3499f353f9317fb8894caf397959c99c4312ac9f5758a200814128bc117be99d8dde2c06c25866d7177d50eb71274577367f2367fd5b7cb5f7d00abbb28ea6abe9ce7e3dcc0f491c97ffc8d7b06fb7fdc553e9a80f05e09bc0c8ad6e2077e77542b1b0b7c117b368200ec44f256f50fa5a9726394f929f71014c72b55c50e1aecc1caffdd4ef87d84162275938a511fd305e8b570750216ad3580a23b5b1ad1ec3592803418f8870cbb665382a757937d8f8cb71690090fa18855c49df989b49d72bb3c4b4fe4586c2297deaa312215793792351adcae7545cc18ff55bfaab0dbba7d967f981a181fd3a94bce880905cb87960d56e98e59d995e72cf55606bbf850677157882d0986c8f8d73aa9f76c49fec909df3c0978bc96319cad3561e59781da0861568e11e913912024174caf241a150722665bee997e544d0ab4d2275521eafcba21a9f56c340450ec6c3dfeec6f7bd89330a9bb40bb04ea31fdffd2e8cf4cd4e995b855a85a52f1060ef69f5821f7885f9b1e0a1e8f84d27ea2a5e5214d6faf64aec3aa9c06fe0ee0e017cbb2ef6d1d6ec82d66875dd0d73252efaa84859a2c2e6101c1d918e9c7ec4b16d2048e365a6b35387d336f16e7cd952589709c236a670ed77731e08304efabe9eeb3b646f308b9080924c87c18ebf590bd9edd985239b46c52f26e522f4ccec62c9e7a21aa8d54ce7ed0279495a3b144b0e07b2fd31c492cde7a89fea16e609c4dc0e2d7a544c895a55a661c67ab2929ddb3dbf45d2a96f1ae0208252c2eaa4d36aff5887c3a1ae861c36307cda1ebd83b5590cf770e2d36c5ca390f807fa09470305bc6f82e5a6d879142f32f2b5574f3848841c0f70ee1ebd6de3d13450cf259992dc81a4bf8b3b39603b1dc591c158c4171562587cfb0538e60c094dbcee6e1ce9d581322cfd2e5b1de41bad2186bad33c94de25ab84c9f538f6921ea2d72415e163f9f9765b975f7877ec12c1e00a9c310df351d6723106c098fbdf45af423ede257ddd2093e8ee53f3d93d56a5666b420e5e67749f7f42cffca2000b6b3f97f7dbda16f4874175616eca94cd762dc4309268fe2735a2cd7303f3dbc10078acf6452a0e90103956e3bb9c8ac78a01c02ab8ce8988eaec858c80346ae60b298c010973279ec7f6054408424ddc419a87542dd4694380864d16195028b43f6a007632f87b00c29cbd81dc97714d6b14126e18ac9ef2e133fbe2e5ce6aebabed2a2fd4f0f7710e500333741d0b8cff3d6d6b2ded0eaec71c2da47aa643a042d7e5a4aa6a94e11b46019f3a3cda0cf5601b2d4a8d050645035c5346de2b35a72507aef7bf147a0c7674049af434973718754c9056301a0e20d3078721d418f64e9234103a0133d20f4c44e1dfb29f9a1473ce5ea9391a1934640017a55992d93c1ce8b200d58ddee93c4e074efa0630c8cc632195437827215a2fad5f61bc56975672947eb671a38a62630b062325f3dd9e50433342cb776c448c099f31c873fd3f6a75948377212fd084ea11f1a266e6a8fe2147d0527b7e8743d133c74af33361137d6a741be658c278df8a6761d93156b8f2d4c31b14afeb230af24b2b69dff7fd298e631cb49b5eda5cd90992aa2b857f15e202c476b24d74c2738c3fb8edd6604b79b2a0f0e7fe8b98493a3a5a121e2fb17636b55dd23f6fff44889391892fb0f7286044d28615f8bce67426ef1e61efa24a5ee03753bb8d7b46b9d3ae3231322b56f36dc2df1f4aa724b0d547a20255bab31e6ba1dd6dfa458b80384f05a413273571f8893d16644bb9be158cabc1dc8882b5a48e71576ea1527808a23510c7197a807ff4353c19ea63af93d54714478a692c8ea099f6947ae59079a9388d1ba6478cc9840b3afe23d726b02142d3aba62ac10f91cf87c002536e66375525f5d466ce1684efbb59e3399fce3cf872954298d79a8a88095dfcef29623ac4de7ac5c93e7c879108ed2dc57bc82ee97bd9d188018e980010f546dd0acdd7f1663cfd66c33264e70c5a987ef62c2216dcec43ad34455bfb7aa2708bc4d0a5651bac35404d96d5a746ec300af237d891cd3cfcdd05a9816b1e8fc539b09913c16f1a05c56b6aafbda655dfa87851c764bf3a25ab1b555d1c61d72012b16a115b6104db8ce064e4f0eb92d842aa8d4b85983dc07ad7f8f60a0a803cc9b8175612b67816f97ece6af005e90e7105a77438ba8dceaf4faa38dc86c1a0cdd4154af6541b6687268c63d88255572898741eb8d56925200b4286ce5ff980b6184e343e6eee40b31220a3e1849dd3a2766cc668cbd9caf89ab50405bf57dce304c18c35af522a70d7e55028d6523aca825710ab63efc7376938b86b4aa3915e7b99fdb4b84e94f22d65466e38fd23c43489d56e5f31b4577e263f417975ae98a66389b946d4a50746d012be01a391bde59a2434e5a16cd7658ab4f78de9af0c91e9a21068b35c3332de3ce9b171cf605369d90561350efcd70a936e85187313a70e647363e618bb7c8f80f5aece7a775cb5a87648f24a1c51cffa7656c58daf0de1fe2873ea6b8ceef18c8ca4dc3d034a9759368040ed0896c5728609f75451857da6c16d0c4a03e6c1c901d3089d25333ef8415494e743a9df044ca9532d944dfe1f8d36bedec1de4de0214db9ab00296d76c701fae13b1154d01b15c851e4972ea25274f22f48cfb36a0d6e1c932bbef9dbd84ae6e3d1cb2c544f27a129e0407c42c770ee89bfa7a3aad3ffa1b04897a422c2cf4b9f1ad62cd49c25285b38fa7632e7930401a7ba7b377586887ac7d335f4f04669b7df4a30cb05e2ef377966ac34198c14505c7f50d35b2a3ab39cc7c677c1936893f51fb7ee8fba3414769fde1b836a7a01dd633b2a372cc80370031a39eed914ef9632d4d4da79ff06a0069e0516c431655adf7a03f3a386a39f9ceb1462d5cd553445d1ad711e09cd4c9b9c7c73814092dbdc3ed7241226e669e5bae7924ccde004555fc876f5a212d22b7a293f20e76a150e7ae8a3c4395ad0385049565402a273b1afc25c5974afcd5552109378733827cbd7594489aad213acffd5dc3ab85824200a9c4a7a8f9ad425e0e449e734d957255033cf614af55c8c48df2ee550f279835e7c76af8cb3e1614b50eda11edad982dfaddc2a202e889c960eb164ac40ff49ec57fc9d059d6977ea638ef12a07447cf2768001de7a5a70c8bded87e321537fa1a5559caf8dba3dea1aa25437d15e333d041e53d1a48b1fd0f0de4a3b8ab86389bb2b663bed382e13c8edc5b74aae2f1365fe581226cd540b88826eac993a9c1d5b06a1e0da034896f0896f535728c518a94fda1903171d846e9df268500c7fd286407acd9e06a6832fc86c601a85399df6aa812a7fc9687d03b79d0f48c9181850be3bd465642af26fb5a02a8520da775540da5f59a0345ab6ff1aa604aa464e1e64b306f770cc267bbc965a9b5630c2e2fe5074d2334322729d5f27a97b63f35dd34ba2d0615a44705ea240437af3d99a4122848806af3f8c42c4a476df6dde2c4fbf6447ad6fcb212054897e70052e0889ac0ed3067a10c6d05d07d27756341f1800fc59eca85ba6f32b49c399e535fc9da977cda8524bc6fddbf0f42ac23d706fc9112075584044ab1a2883e9a1eee5c1dd21a6e579f5f38a44ca582efd9dd0c90fb919a1e7a045a4dd0ec0dfce16374056a45701c8d1c8ffdce3e773c0f0c5a0b95ab2da264d0a16a9869eac5b6a9272855396367399305dd755453ae2c47c1520053d0fb62934e46fb9516525a3d6875340f0f5cf22b227052a8f2f0b36352ce761a451b702a805950b4600d9301ad8dba9e2c17d3d94675d4bd7d8ab9e43fd612afb8ad31f1e89c3a948bad3a5239728682062e812c29afc222d69425956dbb084c405c675d8c98a13aa8daf88f5a09cc9841c8fcc5ee9ba4371c48038c17046ade1a915a40d819a503ee76771350e060974964334b65b3fa431f61cdb052aca16a48b3e9b359fd59fdeb4551163f930201b3cdbc617b0ecca51b46255cb53e80ceb608cdea2d99212ff98bbd486f1b204dbe6d9290bdf7de724b2953922916099f080e0941a8ad5d3ac897ddea332cea774519021b0164a30ce10a620f08cad684fa5d44362d6af4a6b1b350b30a7b9cad349d538e109fa233ce73ce85ba70789eb523cf1e758a4cbf38242e17809497af00382286d45c3eb96f88f4ebea90b8bcb9785d9a11f658d3d18ab0f753bf39a4e4e7109fbe99a4792c203e9d9649154d9ebf0c0aa4618bf069d7a7e94f109f564bf8147d809aaa9a0e3596c292604a9eb3559467cf84be6c05e5c2b97eae27d98a4915491ec72f932a709ee3bcf3acb71b0ee3178a4f3338570e9f72a4f874a3890f9f6c383b75c2271cce4e7ff814438c2f973cffea0b588c2fd3cc17fcd2c9e13964985a0684dade83bd1a0e00970c88405f2300fec59f3d7dc603c0b960a0b64f0f80c7007407a063b464c8d19bcd8c0c32b80decd590a10b123f87c3ed26470ecfa03693a3db81bd1ad8131b6ec3b3d6b29fdaae83bd1afe613fcf35b057c3e16653c363e048ecd5f03954c3b986cbd470af86c3e038b8ca5e0dbf61c31bda7069c34657c36bd4e85c6ee07cb91eb8c647cb70cea365b88f7adc5761e0869e1d00dc771179f618eea342cf0ec30d11ff05eea34f9ebde3be2034eab0c754f8c448fcbe73375a5059adb438e2f76970375a68a1854a884f6cc4efbbc0dda8845443aaa129387c62d4efb7c0dd4cc199923325070a9fe017bfffc2dda8a00405a9a2f0097af1fb2edc8d2a8a4aa5524d29c2279885df9fc1dd4c29326567ca8eea099f6095df377137aa272a2015d014227c8258f8fd12773385888e4e153ec12bfcbe0cee4655650a6a0a4a45854fd00abf1f83bb515151ad542b2950f804877e1f067723058a94202941387c8255f87d1277a3c2c9c95119e113a4c2efb770372a232a1e158f109fe0147e9f85bb91223434d4844f50e8f757b81b294d7c7ca43ce11394c2ef57ee46ca13294052805444f804a3f0fb23ee464544a5a3d271c2270885df177137529cfcfc44e1135cfdfec6dd4889a25249a1c227d8c5ef6bdc8d142a52565256aa227c824ff8fd8cbb5115d9d9b99152854f908bdfc7b81b29372ad4ee80bfbfdb8d0ab538f679f014478e4815f364ebba288dcd832d69356775e7344ea3d8631a7952ec3da9b3fb600a95ad32a12c5bb197ad20afba7956cd4353dd3a34056b28aa69342afc35ea5993ac51619a6b09cbb05f947ed96af5346569c14d6491fa65abd4f3969229020eb1220f4664bb34ac7fedccdb0e21ccb15f74e68f0ef9a343f83f228c30c7fab538d6658c94616c0e64bc5b0038276dde27193357caa78dde907dd2eeedd8fbc21e8ca72d1dc9289f209cd1e1ded2c8b8b3a34b233bdaed72db5122dda5e9beaba3df1e638cf161203f29a58c9df6a4d7e1eefef87cb65b583da32dd32e23b5a2f7f4af5db2cb8e7b911d658f7deb11b2dd1d302f74cc9cd370a185179719a6928c18bbcca41696953a126d5a865dd49a323643981762442d486c8dcc3d5b76ec6ec84bc42fc61897243549e11036d0b3811e2759ac45888979987fe1bbc97d36b5e6fc69b8f0d9a535d7c2bf7c9669cdb97ca669cdcdf86cd39a3395647c56595a585a483062c8309966b8bcb8e082d61c7deb33d79a9bf2b34e7b2183c9608001408c2808bb5d7d39bd95599fb1b6a4524a9f523afb177d4e1863b0ab2076074b0aeaa9b6043df4ac3716160c5446b2f3702508211005791ec21ea804a6760581601054c1d5c3a187be0259761e96badbee6341fda4c104cbcd8af360ec05b5ba63ae65a06e0e7787c87bb4bbb5380f8658d4eaa3ee966359eedb940ecf8b315c4669143ddf920c2e0667a3913496968fc405995f77d4f176535d081a5f7cf1c55716afce8357b81dd883eb7b801fbc079f6af7c1afaef5a8579ad4a26e69181d647e7516cf960382c32106157ef56f7daaefc01e4fd3ec0201a3fb74446fe9be1917c595110f107c6adf6e7930953a047cf810e89860203e34cdcc0c8b43df01a7a65958bc679979161dcfc2d332d08605f53af8e42c3c4d23c4a723053f4382875ebbcf8319284187f09783cf52e18b381a8b03fa1e809b99813de83a8008428afaad0cad0cd15d19fa56861efac0271e331d13ecd075f0691d7a0dbe4e23a3e1b2ce4d17709d44fdf8d75978f8b4751ffc85bcbe1dcb11f6200bcf0ecbc72caafb1c32827a88fa76a0a19f958f2a15844ae5f800a5dcfb74709f535aeb66bedcf4dda2c60941e306d97d9dcb3a7cbfe1efd94d00fe15643eed5cb663e16139c232709aa03a0bcfc39d25d84cba4819049fe4430f52e5a1439e05421011498458782091202888c00f1e792863b3f0b01ce113a4f2307e84db7df577158428676b5a824445c04983033a3bf8b4b4b4b4b4e81ce9009277f2a45f263750f22d90c529b400aa6bda77ebd10eb9205c10ec512171dfcc935c089f583aa65ffd181e8383c12db7240fc225071d2ccccee23c18a256ba8faeb8d5a146dd57e2d9340baab68e47ebba92e532bca5a5a5c56b107535f8f898440ebde470eb21eae00a3e899bf9164e05a9b0c7aef2a13e5b8aa71687dfc1cca37bd35c1ee333e68cb5742e3ac4166fe97cb0e7d3342cfed1161f1f551d5151a9761f7d611a193eeabe5af227759f3f4bf7c1c0f096eebbf10c3b201d1019ddb60c4328bfc55b38fa30bb82c46fe9fca77eebe3639f655b5a4e2d0ea585a5abbbd2e3b01b20f9975f2637d0c02b606bae8e888bc54533a209ea7e74243ab538974ad4ca765e44f28cd4b9e0b04ff29b7d52e79201254ff2210fb9cfebb3780b89cb586be1ea8af3e021cfc205b1c205915f3b480398631f87cd3ffa55de44ddd6d5a75957ba3c5ed2b707b4631d1197778fabdb1aca45e81ad36c4d8d4e0fc18a600b46083014c0cc09da3382d8d3820fcc2864bb343de79c332dd3314ed91d504a29e5d618638c147e8ccb4df022ac9da46df0816f8fd966d3fd44145bc0f0edd866d37d0166e61a5a90b382194041429225bc05b33067e10acfccccaca448bbc6272549be9d548a02896f7f6926515cf1ed307c327d3bdd6cda9ba10346acb08344145b0839c18b2eac1a70e0d9815003283a9e7dd61e41a45164682002a634d39ee8eeee8662e8996de83144091f24806084091b80000353b021234d136bb2781a1a17562d91d88362890c0041b182246c7022078a1670e00914143418810d4dfc40e10322363c012445cf7fd5e7e5046ace396713487e4e27cd39a777d4e2b6c90b8cfc44fdc80795fe3269420652202139c1031b3cf9af7a1c8168039a17966f5a540819c05d0e13f99e3087518cc3453ef2e88094be1d12f9e83a1fbb2f88f5dd710ee3604028dab3b729640bf341201f8e2069826777a10e24025370890f285b0221a1121f7aeae752843d76813c1009e979b381a9291f954fe30346171d1e769fcb0d171e3f81f83fa00bb15f7810c2395df617c4cf1ff2069c017252c6196394d1677710406810d694fa51273f2b53b4c8e1c09845258f19d4e91b44044affe3eac11e07610fc3a696c2a8d5b9500a83f049087b417a34b52c8b2d2b76fd1e4d69fdb99cdd86ebbafcba86bc151a80f25be5d78a9ca077c9e18b40e97f4418bdce40b9d9690f37abd3d89ba154f9d4404a9e49d96953d4af32d1deddb20c7486981533ef1786755f7ba541857f03c332313af78ff6c8832b897a641acbad8dce8eb2a735912be3e8198965da77bbca5ed3ae8da8f1a3472e7ed38e09cbdbe2013a3b8469ee4196ad7f982fe6a366f2832a6ffa6502049dcfa4a6712e397c9b6b3ee4b36df3e85b1781f6ed663f9b1cf58c37d73ceb95f08b77b1346cbb815f7b083de3a8c31ba4a671dfcdfe0f20ed9a47d7ba0874746cb3d16e3ecc2d1f022e0ecb83c4dfc56179e47e601e5dc94fb183e417eb5c7468b7894e616f8b32cf5a9bf08b228c78cc335f2cebe035e14fc822786d8b8392184e2294b6c29c18744bd9393198c24c84dcbb2f2586a45ab3094fed2748a3b58c13466e40ee0eb9b17977e1a5830db8a027a76aecf1c820f4dddddd5d38816c7777979340b6bb0b77172e2fa481ceafefae14616e7721849099b9bb674e4fa14629a5e4292dcb9273ceb0b77bc3b22c6666ae1fe5755d18063129d2a864a7318b17b5a6bc2e783133b307e18f8d719165d92edc850b77b963c78edddddd0ad130425e926d94871af7ddf875bec1de42c83a1a6867f86af8f5de6f7f60af7d7d86cee58b0ff35147ce9ca72b8164908b115502bdc55c536a1a7e0ce861cba87ec2965c532bd056d2353ea5947618e782010c831083176d8b63ef20b13b0689fbc505f90ae73e89e2d67d16cc0be782812cab97474de3e0dbd0e93c9a07538edd02e23dcbf8b0290fc99000bbbb190a8139a6551a4413e83e8dd3b4fecb4723898d30c8f3a343d42d73cdda7618f25f37ed1a6273218a40fdc83511b6d96074db61c8c669392e4e769ff409371bfa244d8b1c40d883d50791df4e45124c6041124c60018fcfeccec0b26328848887dd37fd890a5f325f586b5c0775d86b4c35f3cd7db0880dd4619827956ef13c0e4db30c4b4b371f459e1d321b91529929b5a8640bf305e0e77977b53b3a4dcb7c9354f899c7d6185a0b9f3dae3c86613cf86b6102733000ad1f0ca746c0faf91f926a51bf6cb53b5e5c7672bbc9f190fad7c2e4f92686871d0d1e5641fd6ca09efda3c1f336502f9fad1607ffac280d9e7ac3ecb400fc4c29500fd9dbed66f0abb1cebaa597665efa0b7bd835c3271f43c09f01021ed3aec4160fcaa76f867d4a28404861f7cdf8581cecf248fda0d77d43775ac744e4140efc87f49e52878e9919cff3a791d2de62ec3e20405efaac703a8c3250f21f95011149d2418019193c19e74aa695ed66d7fb2af0f313f2f3a33d8fd8bcfa475b4aa5ca71f64587129301311919c79ff7924e924b3a3f6a7d259bab88c75c72920032c8bc7419bee222cf64985eaebcbc5ed2a62179a9fb68bce532ba8f06edbe52f518dd5761741f7d526775b476bb32a34ed46d5dd67da5b78428e2296f3696104ef1ce0afa401e298510f2d67f453c14c27a4abbb56e4096d6134530666146b268f9746a59965b945a256fdec97a5c74a68e68c972cb5b9f9dec80ccfc2ee426487d455d6f08bb8570927420a5945df77e419a25dd018c3142ff11a19c3b219c703903bb31eeee6e8c717777799013e40a4debae664b63b7a496ecee97393b1a2508bda2b647ed7757fb9d12a59452d26fc618a3f6d2c994f39b9ecd9edddd562f8d137643c4d8d1be80f409e4a32f27a5b62fe9639c73ce0e08e963f784e99d6596f715b5bdfb66ebc7dd8ddff4287badedbe281bb21532b9c5e810420823a76d3a6cea6147bbb0a6406b408b94b250a16b33a5af511e3e5791a40acc43ed99717e67a0067860e0cae258b83852ffbd30f141cf7fa59712d3dd5162cf79896c77bb5140b220ba03b9f2a9dc284843c63b809179999b00a5d4255012dc9d51c8c501bf2389d4d2bd4a389a4a17855b54e811e6b706be94fe441965942883a880224629a594b28b714a9451ce20ca29314a29a594314e81b0a7896c6120f77a8f965909996029e147ee0eeba24b841e2bc3be80a5e1b14678864d2a74ad69e8b5437a9768b03be28ec5302cebd28bc35c2bbb322420249a518deea0716a9aa6398499a7f58dd84519407b4027fc6c976832e6932d0c843eb18b66daf6c230de343e504aadcbba2e78a3690e21ccb26ccb36c7a4686c8948e380642fa81af5e1072139ef0308f6d87dc02a10e5c30f0e9852bffd9608cf7ec11d4b62affae88c421dfa2df771f9d6d5a53f609c6ffc1c22101faa920e46a5b32c55bf057a1f7cf8814f16631876ed4208af6b23add77242ecb5fb6486df1a7e53bfd8e5cdb85772af54628a49513b6083fa2d10ecc0b3f329f31d2b79a01aa76d3f04f940a4926afd218a0f4ea8dc0f391e900395806a0a2a0c0fc920e541132a2fbf4ca8187a1de8fc326952e4bf4dc9b3b7fc326952e59724d181d07fd98a890ea868280d870a13a8960a13043d0dac592a749070e0c8fb2f1544763ef5453a76c72c76ec8e2ea765591f091669d92393f01c79964dbce4618f57ee5ad0a0a11a72c18529392db410f4f2a252b9b84cd9993143056432e9944a53503264a85631624809820123874452f1b4b40cb1b0f8acac4801aa55a5331afd8844aa6d93b2d2b49dccf779f08d0af598ef2f5379f620f6b83bca934a1e699048eb0cdaa94ad58ffed0209aa24da80fd07cc21e4f209e79047b8201614fb09f0c95e1602b4c08ab82ad30a11fd42f5b6d93ff66b6fa32958a4a06250b8a0269c084b29edd91a2a95456a5755a867594344f122cc51e3bcc4ccbd24bd9614bb026cfd79565d3fbaa12240b3d20e8e2bfa993055151e488ffa691a167a7d6cc8658c9f3aa69da5d181089e8cea5016d81d8693c2db3d34360944fbaa08735944f2fbf34d33f19f4a2b3ab9651c9156aa2fae57bc5a7657a3534b3d510711af95594916a8fde824ed34c8f2de8ac0cb5a02323a5f3ed327687133e4117e91a7bd96ad5054916703fdf2f35a90c49c693ed745139460f17b5aa5e50ddf79253e10baa65da4d317abe63f4c4501263c90bea7bd179417def7c5f355cb7a0f42d83fc3252bf52500d2fcc2e7dbf75124b33b54fd6e2e80c28947c77ac0b8df29a3729d67f62a6652483b08327a492c378333ae4cf57191e6e2f6cdd270327da917f7dc9283a6175738c3cd37cb7edbb4abf6305d240bbdd1cdf0a847bbad6632d621913da29f3f54ffefe4743c6c8ca28851c5d1c2f73a62dcbc984d382dd677a083b8b2b790ff75a66ca4fea7743eb8125471707741333154100b8f05cf3c578f68534b47fb2fb2734596f69e8ee9fe0b339be87ad895d944c7a0b55214459410294b8424215d9930508e0e888234649d06a8514d0c08b238636a0841a6436e8eeeea6ce04084a8ee083e7233851da816384500aa8b8821450300db22a4ec0237cc0250ac108499e00bf4c8cf0f37599b00e708413d02d875ed496599edd47aa8c44f6899e5ef1434c1104600e2ec2cf6f15bf4c8a90f319dc604787503fa844160105b908d9f26902f37fd417b2b834be70d4ed972d4cc718e79cd1212744c7894219a9a81c88d384ba8a2ba7615d50ad1f227eeaf3dfd64abf30ef516a3efa310e01bf7db389bb637fdaf4ed824a7f88f89eb73b72581ceded324b830028d35e2ac2b7b787c3e2e8de95b6dd304a24d91041e85b7e6130878cd19252bac509d13ca7e5cb59bb5f098866b1538b07c3dc9899e19a41dd08841996cc1d23b528b5442ca8ad335a41164afc94cc3342acaeb0c4124574a2a802144fd8a2094b38b0a40b9c1a4ef2ec5e161c78f61b310b0cc8234643c8a290c2872a4421892328232fa06206221c906c20da8002bc78820a23046105475e20031b8c7c388a2a70a0facf610c1e568103a0e71a58c3c3d2b47c022941afe50497f3b545210b24bcd4345e9780be4091ba0f088ce7c597c9959ea7c002174928a1022c70208b1aee7906f2e259722fa522f5f34a5c1968e8674a4a2948defc5f091e916551c4224b6812638cd7bffc11404fa2f091520a4b2f573081820e57d26ae85b7e99ac9af05f75c9021ae2057e708bebf9599ed56797b67cfa0a40137be20c1a0c5d194b35f6bce75efac901ba0846ae06f6a044b544c59ff6dc4ba5cd04f513b9749e2356a9c676da94d21863ecb5bad336cd3311c76f756bfba29168f4d1ef1a45dfa21c460f7930e4ff2cc0ef9243ff57c493fa45dc47bbafc77f40665ec3344d8bf9666e1e967924b5bdb3dc5a3dad955e940703a51e9ae0970911509ff576133fce7c7b84bc318a5c72e86f6f917713ebc993ea22cd454f5ee4a37681e65692b75cf48221087dfd91672b239616d1b6b5888b2f638baa6ddd1b075fb4f1d0dd967f22dfdce234cbb32d0791d52ca38b3abf91affc1879dd1d2c428c44f045dac683c86b7591a83a0f66e1f64523dfbc723f462e726cd34173915f5b0ea32e8ba15de031ff4a7c84c708153efbc8254ae2c89c4cc4c19719d81d42c0d5631b0fed22cf62b8c549195b0eeda2ee853dd16ccd4525cbea6a48c2a74fe44af8f443e496b3c79e6bd301bea0c96b3c686e6d39885cf463390cdac25b1ef396ff34cdd6c41f5e1c968c2deac8a7c7d86ee007a289b8f872d361f423986f2c56bf5bfc32c102e7b3199a0c2eea07df459341a54e7fd16470517f2c7b41e0ff58f6bcd9821491d81335b3e8b3c088638b925a60b82d445164cf72fe8dd3b8af85ff9887a7f4c51eff0199f90a23f20a742734a073819963beb46566128c4921246166d931c6ebbafc66fff2219f90872eaf4939cb7783431e5aed53486f206b22a0137a6a32979e04ac71e2019a4bd73a9718563544c4b0aaa1ed019965cff27e990881c815557e995ca1f3387e990421e8ebcad2cc28d979c28a12407185504d3bdc74b062f4c93186617eb38f51ebe23ee89281ca2408409fb176f190672e4847879cc57d423eee4c2f314511233fa48cf6e4645996912ca7dacfcc91a1426834825e9667d61529a5945a975bd6454b108369f99cdd5a1d8dc889414e0c7e7a2cc481381b848401edc38c6c723d64507dc867933b62b9dcd06f393cc21e3b101cc4616fe63c6f0ae01a0bb5a47e1bf43d18c0a7e8f083934adf78437b4d6fb007b1a8b0fb6edc804dd01eb8c642594bea4791f8583d73073aabf9c551b6ba89f8eebecba9674a98bbdbf22c1d683c16bdd7288d3a5fafd955981f7e521d686450ae00e6c01dcf9b5999e20946547cda299e5064a7e8810f5779f67cf06e6ef46603c4a85ea2b8c327283ac2a7e840348d905ee930de898c843dd61109198239a53a82a29c2892395214b76e72a597dca7f111d6d1a87763865215d3b0aa65d829a55d7256b1c7f222415515611e3ec1a8c3479e91340d10dc84fa6945d8d3616f873dcee6c38e8d2021b14ed3d81059070868a454eb2a4a10acc234cb2b9d8df24dd2bae31d66c6aa134a563333423aac09831a954ccd87a6f90ed13838c41e6b9ce6cd7d7005b79e536bad8343b1f419a7fd075750e8494abb4a31ce0e0e355cfde48e70085679fee06a87e00a0ac9e4c02128f404ae646b2748611e4bd69cb3c26f9a45abf418295ed432c1f6cc39e79cdd55803fb4c70eac380899b3833810d57eb303dc81fec11e98042a814df824f2cdd9a1133e69ce4ec4d611c11e6a13b1b305511dc461cfa2122a69d267dc078bb02f10a5b5964aee9e4767745ccd3d3d5970d1a1fd47370389440a7b8a50b97d422008a67243e3815a390d44101668e661dc2eb09cad46398d45b4b36a505d72b87ce4a38bfa475fce3967e5d13ce44aa47df9ee55fabe6610f994d2ce45879f9d4b0ed32db73a1fecb50f79d955d3d200f1d1327c32df6e035796875c895dca628105976a53d46f817a448dce4088f41d22177bd91b42d8ddd6875cf5a169dc811cc88170d8a187463d528b5ace56c73ca036638c1d638cf1c5dddddd0323ba300d47969c51528168ff66a6e002c9145b34f1196b70bb6106f6d806c37cb9cc23c7391ce1e663d4a954250837ab1be6350ef59136157b66061d1ae9c5e27145a6ce30354ecce911799fb5c43840355d654fda50f68644f6606926c0e25446848c4abf5cd2605d6499863f2bd3401a6cbb3ca31d99b05c729165a477dfc02e6daa26235b912dcc47d79ba156b3bbe73164c8f0a765228434a7521974d9c08307162c257c12d23a0e334484904b4814b414b5a68cd49a32461965a421a81ffcc1611ad864a8dd09111ad804cb28699a4e22a4a5483e2f3f16d0df08fa9fd87df11ba29325c9b2dd8dbb4ba306297777c718a394526e36ddcd39a76559164bd9cdd775611886655acc322d6a9a46276977db36b96d36dd6d1b0ef047a3f6d166d39ded6ead7565656565ce492a31330b0b4b4b4b4b0b4bd9cd5f04b2a0e7ee07c908a604a604a604a604a604a604a604a604a604a604a604a6048604764476f9d41d2d959f618a258656dd532cb1f3dd8d051675bb0fe661e03a94c0741c24d6db5d0e4853cadcddfd52f3d1e9d327e6ec33ddd575f0c0a25fb134e7f48f4eb7669c73cef982e239e98c73ce39e79cd37956f6a64f2cd49d9f9eed340d75daed55c3fd40b68cb0c1f3bd560b40f58b59b2248bfac5ecc4ec70cc8e8c5912658d314649aac948fa17e324e627e6898c810223859705e78cce0f3b29a7f3420b5b38da4b45e533099986aa407a59ec9554472fb054daae9333422815fa5e3ca89f2995c55592dae2fc2dce834d4ef824bb162799f88733d3924a79a029ca34a55665a26299844c43a62a22d48b7066e06c5596db8636a16d654ab9388ccfce1b6d2c33945a3a742c67d147fed5917f259791efc3b0ae7861d7d5b1744d6090565a628c71976bb0341be7406a9a84a8dd91c5ec50611ae6e1814294c7b4a47edb8af9c72f23358a8af06934bb6d25cad93132121d11211135f1a224b4098e22a1477a1bda1dcbc38694d5ed80c38c29d4cf9492aaa85da92aa59453d39030d093275090f8d0e387ce39e7b68a305a4fd3684e37df2d4679a8053db4bcd082d8839aca946a9a6dd5db6a73918b3a22b41e8df3b1310144cb90a046e1a8642963ec8112b323e3e9b43bf88e37538e34b5b49c35f960cc2cddc7f4c404f4bc096d4296b42ccb47d6d57da69f51e44c4018775c4363a765da637c14ea6acb76003509f470d56c5d0f313114a0790f9a6fcbc48e09cd371b970c106142f3d8e948962ba5947086e8a3c0478f373112b889b989f9d8b3e1001fe5794973f64a9921aa779afbc2418d2cc4a62a540ba8e21fd40905638e7c7b7b308786f2501e6a858f7bbe4d29934f6faa9e9e2e82aaf0b338a8a83fbb233349538aa63d632fea10d194e2d3c6ec34cd55d3c36885a5059a5c8208b2ad442822d86b17b2432cdaca981df6a694fac5ec04a1ae16b3136384e9113eb9ec64d1b42abb036e124a6c22f8342fb42cec0ef85a16f81443464946a934b7cee4d332eda22d9f72506bac9a46e475e7a90d9defcf464e8da1b781fa8e5ec33fdd896c7a88a248663366ec7ae6328fa8f065225121f46b07b5def87c16d4820b313b2eb8e0428c118ee3b8cd068bdd5781211a3b4da35d236a05c58bfbfc4b1617c45d0c4fcc8e115f8c0b34b46db509717731a922a71c6384bbd33624558343d5d484655ad4699fe9c9b7ff0c29d45511c96868974fa5b1dd307a25ec752ff9f6a91f44691ca7719b0d77fc64775f78e185176262622e8fd96c2e00ecc200030c306c3623cf6276b8c6ae0d1b366c6c36d8288618628861b3c1663cdc78606a4d798db20cb3a1ed66376edcb8b1d9ac380e0d078e15c7b1d9ac7866fa31f9c816126381859c525ad29212e2401c54109f32d34fc6ed9b52304c2918dcbea9898fe989090806676ac2999cb0d7261fd313f6804ca92a2a768d68c4ae116571ec1a5112f7694a3e202c0a6059b1228a925f2b9850e405f06b4514249e4e81832a6fe3778a1bc870b90c328c5c86cd66e470bbc19cbb19abaa394919a1426fe9ed18e3a600aed13aa066a7d6e594fba1ffc92025dfe5d866432b30f4d2a3a4429d4e2367779852d8d6c3d648a06db55fe71ff6d804c563184126151321a6c1d950254082f663e4db4d3f4dc335edd6e419dfbe55e193cb777488e2d3cf8c256a94945ad76545fd62760420852af4cd4501578dc8b755d350bad251a2313b232d1b69903f764c54b12b6eab6d856d5528cf56856db5496169f867c3c2b777d6d2d2d222426d43241289144da9fa6dab989d24d491b3638145cc8971c6196384bdf61823db4a84d2e1932542b1d7bfade8b6096d43229cb9096d55b621f65a8423cad9563aa84066ce47e78e7f1888bdf69f6c00da5545d5769ac68501d5a5019b6bae7516706142e49b6bbef9d6d44e1ba2712611a7ad1ebaf4981d944bec7280463c77313c0f1f7352bf989d989d181e3e6d35f97cab54a614ef7c9b52190fead7f2f3fd7c5af9f56c86466505a9b007e1aa65da356ee81baab8a850b5ad20aa019a6f1d44c928ff999c7c6f2b930f7b6d8240a83106b5514ca9e7ce94ca41fdf887f270d9495b3ecde5938b02ae1a53ea637e7dfea0fa6434da420c2ccdd66dcb74c793e9d02efee127042000013027c06683c5c844a89ff6a42ea4c29407aada45de9bb7b5ad8c30a5dae2a27ea694a989c9874f995f9e355bf16d4aad898aa5a13ccbd3836fd7361bbe6e50bf981d53aa6920ac91402dc3ae7152d5422a6dc24e7e848c3ccb86d4e47c4384b09ea90f75022bdc16b147a151d8eb6813f69a874ffd4d9bd48ff2b8c59952a614cb1939322152c2c4e61f4dd18e2743e998d0509008c4691a51676ac29b56336e68c7d5a4dec43c77fcc35e3bb4a242ffb6158f6ef1699923f5a33c94871ea14df844797826e5e149f93879da40df506247fa4ca928a626ecb5296572624a49a1c2ffb615cd68d828cfe268bfa8a85fcc4eea6715b3b33bb267a0a6d1509cf350d3d956dbaa69ae9aab665ba6860218e0001aa9868d1b38643c180010438e191962022080190670827981a6d341801d34703514c0000798325e99b68960906298a6d4b7bbcc694af1497ebba949926f37ad4c55986600a97693133e2d932534f1bdada0fc7f269fa06fa881ff4caaef14132588e03f93d0b7e9e7dba78cdb6a13e21347f16d83a7875c128ae00558b094cccccc90afb8023af3ce8033baa02cbbafab8899f1bbaa9f5429699aada9aa2ce839333db3298be2d9339fe7a52295f053f62495b09e6a77ecb30e41e06b3fec7d4e520fa35fbe352e56e7f280e8ed0d907e7516708201d1db67f765430fbdcb564d6381d8b934207a7b67aa96819e4d6a31152088f2cc2e412814ca082a4490c27996aba641a804eef555390bd18c00000010008314000028100a87c442c168381c28c2a40714800c7e9a48805e9c09d4248661ce206300000000000000000008cd80010165b96583d86b58cdfc65ef9543026fa581cb1604eb13cae7eff8c37ec8a13d9dbb2340ee80f351a6d75f32580bf13c809cdb1177b8fa75e66ae12ffbb81d4abc7a3eb17e548e731885bea8d6e42ff3417af385cc83c8850870d01d274356624fec380f1439c2b58517cf1e25a09cd9cecbc81dbb6dae7974cf8cf241da1b2559154b8a961270c26c7aec651d0c556b9adaf8a889b284584e2e44d071ad85eaa983d6f3efa6a0d7cd42fe84a14e55c2d90fcfbe267cf6c886aec3a25e0f81ceb5841503e35107e46834add319541f197c66b1a60f57284c7026eac10753c06b350133ceea95cc856486d5a8da48f9290e343d19d02dda456edb05a11bebbe7580fff1e036efbda08b9822ab099e394ebac0a5083f9887935e54007a35038cc4044b8060340b2411dc96b87d22904b636f39228c50e0236f9c630d5b1d21aa8a129fcd574d1ae4097e41703efefc9670f2d444f2ef7052d52e723f9a638e3bbb95649ef275418a3dbc5a45f00b2b475b1f39e816446e42f62c4d9639abfac54136581fd1f13a7b07e8eb4019f93feff2390e421ee90dffcfe2d2708eafdd4d9226c20538544c728938e3c2e5328b6cd9afaea2f2783364c57a1cc84993da0e721a9e52a93eff1974ae971f38bea953d3f1c9e81e11fe4bb186ca1eefad99d8f85df26b6b13a20ea5922b700dc40928b339c11ce338d7162cdcbfa286cd1392a1218a009f51035beb87a5ab406b685546f75f1d839d0bb3a438385c0c5c5d5bf04458a240107264801ea2be421df06980ee8050a3f818a72dff82009ef72998da8c235afd26bb2450d5279f5a72437e03b43ac0a7aeb41be4c343643bf114762361d30d5b6bc8e59796fff7c8b92e9c8badcb8abd6766a6792670af455891650eeebad94c5e349db4865872d9a50612da6d84df988865b0c27c663223b7c2eedbf22a19053a9a196108dca634249216c5c54e9da4a867272bb3b24ecbc04d9f51d2cfda546db13b560a4cb703f87bc7fba412d6d76b49c2f5b78784f8f179ee140ae6dc1a95e43181b33582211f26ce2a125c1135d335e808242ab506a8767eab14a80481c1fc35753327fae7c90ecff8fa39e9e40afd88229cbce03eed0b40b799c00057b44d2f689a9128a5b400244edc026c4e05440e025094c7cb3598c5bb635a967d455361a576b144314d1081db07e1e3ee26f7d188e85cae85c1c046dd04ec632551a1c0e3cf436cad7cfba0a885b8332fa789d327c8de392cf48af5230abd693f3eb939e0ec6e05eb85fb354740efd0ff01f779ed55e087ca52c9472aefd311693a5579a488e4ec402fcf037a5fd8e4159c78e2d24e4842f94d3dd878c055ae62d2043db5d8a7632e73cdab59cacfa269f0c140eab56335f250b85269e2f4fbda6773d134f8656803be958d4b0b5fdd3e38e8abf7f1e4159460ef78c21f856e879136bdb21e83a1a7ba92370e161662554904463d231073c98bfac2b3e620912c6343c6f28b4eac1c47ee34678f17605760e44024f7f2d905a48660d3f2909525584deb0019d01cfa18f1ce64072a0b53116783795b61b5e359ed4ab17a76d8509903f192984d0a419ec03436d4f86d631cd115616e0299d5f7c4246ec62a7faa3e6e4375a9a751e1900595f17bd7a4d07e46ce634910cd456f0a24f038ffed555435d4219bd4e9320de96741ce17b93e7494b87050d2f66a885075693f98d3f7b93a1d4910199c01d418657cd26b018f33f09f7332c49c2418d00581c9ab551aceba41177bc9034e8693c2319f75312c55c626a1266a9bc6f495c0c12822dc320d13d88f9d0c40e0f614bef376252d932bff258d5898db1030879bcb2dce8e0c5423372ce291b55334fb81d12a8b8d29e6f973d720fa47b1dc6a8f5157c8cbc0071ab7c6b73bbd1012bd0a5473f3a34e128c6a0f0160c1f4c9bb00e3a9fe7483c46373460b421aafc3849b63a05485578a262e4a94c730dd4df202df84b0e78bb3e2cb2b04b97e448768d5bae9673ee6676ecf9a1a6bd6d7d866c2692df3f56c4410188bab74ed9d548cc79a11d61bf9bee9ef225590772641eb2e884a0e0260e6643715ffb742dee6a5ba1196aa46b627efe18bd9a37c638d4c891d1db651ece6d3dc2d9a0adedfe79626ba23c7dc334447916a951e4d59a10d1060aaca3f7c4e8ad5413ef3bdbe75c0b84932a9a643ffeef8b2e28b185debe03199d457cd2ff562d2b459b2a610a1f63349e6b3432bc131493c898113d46535ec0170032350585afdfcce0ce019f7d96bb83ec59542c8a6b28c59b6112a0f6a4fafccd00c131dda2b5acbc8824a17dc20bbbb23bc46e247024e02a256ace3cd883489beebc328844f8fe153fe10070ef009d14c80cc7370729dc4e2d12bf648ba321da65f3d1ccd02edb3d034fb478ca9af3114d11e5eda07b2ce220c4e352921af55c66c73e51ac307b835ead9eb8de2b75643c3009fb9be685271f7398b9aef0e0b3306675861874831b95b78ffbf90ecf0d062dac3c0afae3b245b1f58771b2cb4e5b4a072318af38d144073ec0feb9104ccee2d97af89df3c322401191e0f12cb7f3cf8b4c06ef1583e43d26092a00be27e1af4de2ab7df9f928c8059a9a3ceee862fc9ef235684ed8b368889b8e8ea71a0997c23c135aa23d5ad2a9436da88b798a608baaf5ae952f11e26f9717b78fdb7613f940af82042931b47561efbe0de422eec65c882de17517ceb18255578320f2672338511d808a5291f32a460664079f2d0a309b270cc050728427b50a29bf9a215f23fb6af2640ed70260d7f2df8b8617a056a0485f9372f3504232bd5026eb60fd8fc87d1e35455cdfd0f8cce7b8a6fbff469de21844d799e6490dd91b54a51f3759d2353af09b9ae5c51a371cbc40c55ecf511630619c0d2ba74ba85f810fe7c260c5addb367a94a4cc66ab81bb214d4b7d44ef1899949d8c5821e92e8ba7a4c45944c547c96ffa59e779075a49c6d1a84acbdea791e308cdb5430789ecf786305c4d6160c751a5da34d987711306b6d0873841bbd85f7bafedba248464341d11681f253d19356c4b039a8a543c3083de73ff36cd226488fd0ac31fe093904351760f99db6e64cb1acd34c076e289b89090791701f5e8d3f0478af10863825b9af1e546d92a9e18fbe95fa2585f41c98b75ef4535821c290eb4b1080da90b667922e4e78b6e98f1e81c50f244f20829e6e167f07fa5138c98a78ddd581b1e5443321117d3aae22254c212442ecdce2e24d340406f21683845ca152a2464044c7c2bab61150009ae03c2180cc572f2905bb14b74b7ea6c280b9cb46834c50aceccfc7941c6d5dde83915f14e29770c8bce6a1ddab2df1db73fa11d4ff78b54dfc98c66c3967c2a2aa4515c5416af4809cf9d25e11c705747150ed1ecd2390d8c127b4821a5d8e239085ebd901ff8eb547012aec1257f2cb356c6ca6ac2f1eacdc265efeef68457e89239882d2639494844e150f2fe06f515bce684871ac0550691107736993a79f852b0bb9e7970309985f2611161f10e80dd09c259ff95f38cd9cbf7d38525bdc1d8038b088921cbb72b1acc384eea191a7984be30989c61b416e79fb98224ebc097fc069385a1c906c5cc06488f7cf935dc7ca15731917c4625a47b13e4b59eb7e943c95ddfa99f7ad02e01424ed6e1f8400fd0a3adae8695aa66b8a876036d9d92dfadcf4cbff01ddf989e038bee81feaa09fef6222cef1f1b26ffbe77fc505e7d9ce7be17b685d74edfa52e80268deae75caf221c86b32bacd6ded337fe86c004787b3ef28a6a21a90d9a2bc33b2ad4707823ceedfca68266c8be69d705fb107118c65bea15584e7381a9129637c5703d2fcd608e7cbe965b7425ae02c6bf59f09477eb480b68605545962570acf348113c1896f10525801dca71c5ec93899ef3683008dc570fe230ac706d59fe54d3085c26315b5d783ff09eb65d7032637b045947292b2e96c1fd3932820f79f3f101bf8649f6f62c6837973f987332bbd7430e945c0ad4065458a8415bd50eb81cad0b137e70c6bc61c331e3ebe4986453deb2f1642c510986aad25ddf8118b9ebee77a3cb98860758f7d70ab0f7f5ce76c9199cd98ab5f386ec34ec596b378ca667f9ac0b8e048998594d24733a8402c24020c951cbaac2a6b69e216e57e6cc2de40ca698c14d08c5ea454a82f22863f0baf5c89637325eda290b26244102d43def2514ac005e0583ebb248292f836e7d8d2ca03b9ccc22471455be36967a1cebacd4424e017bdd5f66a5b496668ab2544838868704a8948fe3861bb39028baffe382a0d7644ed285fab29dc353bab2590069972c27c56c56a8a6441e85962409f49abe85d628821867cb9533316961713cce0bc6559e9e975172d3011fe9e07c48baa14d938b9aa82731fd23f86a611e70d7208656e6e262188e8eab65d1f43f561cd9a426cf0b34654edeb575b121ee8e653e05c487edccff22cbca879d7ec7dc808c4a7088a9a2a55d4074cad335655eafa3dc9b91d34497d25a6ba0704dfb11d4d61c83ca510752784d201fc1d4ff3fa9c4e87fd53b51b4b0c8df33136e1ea91069c1fe9cc0d3b2f0a876f29188c23b08cf1ef26282187bf07470a8dd8f4b99487e1441b9c16f4929905451e48b7c23d4718678e895e8a1ccf7403a79a079e3ea2286682880e675a6497bf023aa114be48c37f764408e85c0e823315ac1d213703b0ad518369c855998fd3e82f9574c48b9514f4e6929148f723618193835afa13853d36e672db5bfa30c793d38c4d96ea791f40ad7fe7c732290b5e86d47cf589bf530b1c4142646ae6318ec33d86046179c7e131f7be54b3b82ed8923e59c1672b532069644ff181cf973b0ae41c93b63f13b117412b3805b1396138e0873a9bacbe5cc44ad7a768912df58c7f8f338252525a2c223c8dd7ff1e338a9b1ce2938aef15dec861f0a041443598b36046c2bccc1f98d1a25a7309e8eb94cfd2c93063e03cb846bd3a18866717a8c0e4557366392c5e5e6bcd106897fdc243151a9111d8e4d0e4a53041fa55d2da20d004832082e7c63e75ac9a5e99ca8042e921480b24f64d8dd5f8fdd8aec33b991e1c1736b0a6a842e3cdc6216c2c87354a16e18deb05a0d7b86c69978d04d3dcca492338181104f131afbfcf5bee0b5546671b0c8842c8f83380e3a7aee499c3bb2d60b77fdf7400764146ba7b8052999febce2f50d487ba1f6b9b5d03c0091288acc4fd356e5e0be6c494dad90346a4449655c92766cf0de83279b7b3e4fc9436af7bb58b3a41b558169d7bee77e9da7d1d67441b07e61e7f07b740bfb3a16fd8e8fb2ba60227ed14585ba6c9d0df9d416bcfb511573388e6fb2e6beab08ae6008ae039b73c0c098fdc37dcbea7b579493a8f156f1982e168c4f2af706c09c03fa4347af606879c5649c81c0bd0ebe1b8a808dce34309f95f2c4d4fe9a2799fa9114ecd73a967a68159947eb89321f1ac15657094c4589e03a95a1836c8befa1b85e0910e2f0d2bcbbe7f0085247d15f8b4683753982aeb68045816fdb25bafca3c262b64eebf66fd16ddcb8310f5d7f07a19dd75dc2567d7d791fb8c51a3eefc1f25d2c0bb77a2db63b5944df7261c75c813e6b3ab25c45e213e1e575486f4dfedc24a9b625fd0bb38908fc12721865b00f8001590235e7a72f7b08bb015305df5acb72c576f12d0d794e3da63143a9469304c1457763601b99f9730cdb701faa62ac6984e23d8a1a97d571e4c4a3a0d27fdb710525a88adf4bdf535dab4b15091c58dbab4325a99c42f1ca690f4f286bce1fad008f236720438485730bbb57448caaf4f96a5761882898c385abce381c25ab4a77c593de5de1c866f63bdfa7c9e608ffea9da94eba465cabd425afc3f232b1cb8aacc9b4e67226ba24a9ac897fac13234aae782afb7048e48e07f8004b6e139bdb638c58435789eee140aa733e22053307ead2afba26d43762d3beab142d20d9bc986b36623508ea72ac29313ba9068e586ca730346c23b772a7437bcb9479eda54024420245ec8e3bda9033e6e97a873635683bd98e699f431e2a0d737f67e5de2239b114b3ff8b939a87a7d8e2e68d52668cbe1ca83cca66c6def4dcb4899716467f99336471a96d6583b4c93b11646b143f3917f155849041ce1587ed718598b8b26130f93145e66dfc44b4e047c945b5d322dde19cd5a5c40bd93c0be745853e51ca2165e476bd00fce14af72bf90fe375082e25da3fe3a54b103fcb814cc027dab0d34e723c9893359511536e2fd6256b15e301b0f90474e42f100ac1d75eb42b606c4a009ba61b180960d93104682bb3222e2de2f4d85b3cab837387e4026fa2317bfd080f4f0af267517b11f84b8d5ff6cd75770c3698e628625e2d5a2114669eb95a051be14464182eade061d840a9ec31d4193e6180cad5284b69bf4bba2eed1489617a5aeabaff51162933cee16be7007e08f856ab687b7bb3cb0ceb0c3bd8b196ac4a4b14c4f49a361c1c722e018eca0bfa691f82b70c7ac7695f62e689208dda68949a862044e3d19e822822308c6299b402e061350afc13dd5df78790fa243302535debce105ba5c096081513573ea58179c6f923372fdb4066c6ca0ca03bf8b818bf5bb8453b956aea51aebdd7b0c06de9817e4de052ca6ec1cafda7483354d655ba5e9785f99ecd55117e9a7a409378952dcc807db7d43822b2ea72003f53b6905ef4ec7d4d204484a42c6a1310d0634555c2806c57c0279f79e65cbedac9a3ab3b8ab80ce39e6cc785b2dd5b94e93cd331844b8402cdbd7bfcd47de6aeae19e208955f908948d2b267a051c5d95d7dae88642b3210833b466309d4d0185d751bc5902d11ed5030a90be5c5608d46225aa495169da2baf31de9ace0081eb9f3b65d31e27a00e6a40fa27c6f05cb008917c417f82e5f9957a3c15d3159abecadc097afab42bd29f137b7b34f2ffd81a4e7201d2fc252f3d81d7423df2a289f915e68e28de804b8f320b1b3da2c08908a2bc656e88202081332b547a5d159337d6371b9046d74e036fa48149373390ff8309fdf51cc0844eccde0fd633214d60909c3c5b462cb6483986b3583e1ca6052d763d19ee9796edae8eb4b00583aed0dd4f24c98c87a94dfeb4b6a5ce678437877a911d2fdf889e8bb0c24c7bd13b62d35b601040e0afbb7d7fe0fd52bec7a7b58bee21f187831bbddfcb5e2f734d57d8a40e047088b388f10b2d7c6421df3ade445194a9675d4e812b03d3d5395e5b1b134de0b10cbf3de3d816e16984acb281ae03fddc10a1b845068797cbf789ac2467a9822916bcc6c09c59f5f272dc2afa7efad59740339962cbb6f6bd38988f541df8f8a792201d17cc855033999678946f4703f9c0e08cc6e32a61dd51c5e7578a576078e7a01059e9175e94990528c59e585713ecea653ac3c339659faca788e3e70034c6009a3248c1bc69b1824ff0bec4e3f640181d841d375682149f6f9a6b45b56bdd2acbb491728da5444936bf2cb0ed867a5e7da3a5f6e06945c5412cb1ad10b3a157a6e6a6b8d5382cbe85cbe437b28c154dd7651c82a062876f5034070fd84b93eb019477da5f2ff81be658b6c405b9dfb4bdc18ff28a62fcc2f5a2bbf523840bd68945efc25114970c8195e9bcf91f51e48b30cdc070f4666e89ec22bc131a302e8c1f8555e522394794be264435990fdbe950918371f8e6c213e8461d804bd33c5df5fe2876b941c4930bed62633bed8bb490e4fe35641e35db6187be92cf174bd40aa4ec7e657054773063b86c4f56ed2a7578e439ffb624d36a5d5348c51b572bf175e2c50bac35d3e2a1ebaa4b9a320d52733c62f61d418a72958c4310db8280d8c8c1579a0c234e9cbe7431bc1e7cac23f73a4adad4b149583558e1e65fa295119f59d0e2e2a6bdbbb26203baf8cd10f35d24e02846e354aa4dd8938a0b6e378516e790cf79ce1cffee0aab270bf86cb20d920a84d43a902e0afe208d10cb6ded0eee3624333e63de62728a7cb39c41a0c28f309baaf4d359d4eed3e36e6ae99b6db3920658ff78c74bcee9a57f665d1d3198cd37547102863879f668bb8bcd8c85b6062f948e8afc73be488ec9324c04c35be4371cc7640bfcddf3d8347a8aaa7d1b359e6111b8a71f646bf41e35f0561c05fc3d5e7f4b345c36a62a44cccbaa433f86c6e7dda6c72bde822732c1d8416495aeff2ce1b5ba78f2199639f80656e7821ba61fa167ccc67d180cc02b9db21b22ccf337390e9cf8cf2e9cc4a5a9942cce7e661732adea1b82ce5b3e46a9a282ddb5d7c560b1d841d8c86d91aa2e1855d5142e2162cb4fc89c850b0dc7807b39f19c5c8fc3f36ae609e5bbdb0dff45a453826e0415db9586b54234d203d2a8534555c9d8948f54b0c0db7ba906ebe09681fa5255b33d421ee14b4f05fd280ff3cca901ffa92649edd5d1772813ed65653a7a1f446a288c9adb9841cb22792c5878a6207e7936ac0ecd2b2b730667ad44209c3123427094c99194e2ea700d07cf6d8ed75e913ea7b0d72bfd9d2f97de7259cf6fbb0c1d3077d065c51f60ada8752790d5264747cd631a027a290aae4f6901994adc4b61a46d10ba685ec5964d80ecd4b1637ac6ceff14bbbe64f75c3b956705a9d80bb47764e6a3f23a520473e17237a0ccc2dc0d15b3524ead7c9c033518bec435a11d1c9eb1305454009ec8fa5f483fcbd1573195e944134fd0454969ec2fe43c0c04cbfdac2550d74954711958c91a7c0941ad5911519053d2a2222a548e70c139aa5f30928c7470f71982abce13365171ac4661ed145f36d97810b1a65419a4858c8abab4fd72da46f8f356da183d755cdd5318a50965170b670debc28c3a2bdc196cc2a833777d863735958e54f481f7099982d5afd8707de55a043a8d2b823d24c39884f433976008de2035e2b4572065584ce1fbe37eb46cb13ab13308a603de7c4e38febd918cb61e4f94fd9411ea1335d096d4b1d2781edd72a24103f7848aec39e2ed00f2a592e86fc240265e848740f8d8e56252c3db555929b0ab3b436758d6b36b3cf3499d21a02b74545767cd5c7b584dc92dd388ad424176369388c521a827a2e006fea1c3ca95c28388a155e04e0ae36016985a151eed010bbaa889cc1e3c864eae74619595e788cc816b67db9c07d37ba185d9166b84f66ef5422fb5f416b84aa5ebb784e3a8414e55e25f439e494e8694b622571dc18c2c4d0ed55e42a7836c8412c2e4c0e172dbb268266aabc91f04e62c846ca389aff832531cd6e0fac212e300c635cfe3c9442afc317b8bb0a254ff21bfcaa1b298f1fbf8555fb4b43ebd6954a5bb632d0bcf0b0f152eb0ef4ece975c38f970ccc7b1a7848f7153d024bf005b066c43ab4520b5245f8b9a984f86321f611ab939145c8d70748462e60bc0a06acd9fed1cd8d8615438a2d74ada5588c5b766ebf88707535eb2394d7d9e3f27b4961375c62da2783d2ae0a0c3dd1c707dc4b21551ac9eb52229733dfa49213b583b1aa887e91c72a5eff2b5cbd2721f58e6d662ca60c0bbc6db5c6cacfe703ad936c211c3912971190ee979650e0f5804bdb20343672decca3d4902cc4f6ed4413c0650462c047d0cd25e328c947af34f4e30d443c0c716845c006908119d6e3dc5f2db2207f069239c1a852121d213b4d6d16f6bfe0394e6c9dc907767b277ddcb15d4508a7ab2a27cb476aa69eaba8e5ba98a412d8d863de55618b1a7227bf7835e0239ec86539a32a1435d0c44f6a91ef2e79ec0a1f7f165009bbf68cc09884f329146bad7466a4784526e0f51c29aa59a1af0354c13668b4d828566dab47e8957e01a7099ae3c4343807de8178277f114025cdbce793cfa277ba3d9fa15b5066cb8174a9b5cf0ba3156e0a35bac988c596fcbe186ed3de05d3d768421750d4232a73dc8670705353e7f8ac24507a4f83abc95cdb4883c5dc037cad478c452d131ea536d0a81ea0b5b84a94105ac8413f56f093a53d9309cf96bbe1ddec0b81aa368cc4cde6e0e082a9624b1ff44495a7edd9ee448c6819d38ff7e174b0bb51e482bc03f415818191a44c43c6016914aa1bb852206c480b1e8a79fbf2e596596814f91e41a8c730f1edaa0c3a2bf5badb2a6d0d6881e4b862000916ccef5d943f0d4158407e28ef85d1cc74a606e7e811337249b11a76b4e6b875445be0a99498b814b86b58684f4de42ffbec3299c232b6143c8168ec44b7f74f03ffecc8fc724e8fb3cc5b91c6ef1664078f1fd3e6e9a6dbe06ef78c9532760ac189343e8c14b199df0b6e82d23ad8a9f4c667ad804a1750b598cda912b67e9ab02f7229901d4c35a4c9b52e77bd413f6901fa125c2e00533680dfe66593cd91a9e95fa430a68f44d8846a01a923505fe680e18ddf3b785cc29468de495b8b99710d74f1e53858ef9caefc61c04088c63d989fcee096b4c840bce8e346e78d4441a627c988fdfa7bbdf89e8654ed7471e01d472a45629ab33a7c4013670d049858a6c8b1e7fd5fb3a2d14c755a9552ad83694bb5b0f5b4a2bcb5f7e6a7039c144b2cdecc482fd03b00563ec5825747efc0fc06a1c509d0f90df052a2377635107e6a3df3e3e01bc953fd3aece849c7a4dbd21bc9e87006b1585a22030083dc62f2b24796939f8c0c5cc26d4ff7d71b951a4cad985b784cc16f45f268dd7902796236ed7f7eb6d591685254062404118b0382814b987d08dbd793bad61b9fc6e8c6dc51a129a5b8368469d0da7ce788a0643727717d17fa3be2084a8b56de6894fd4b7b2b927d1ec987a819e0c4496afd24bc9ec49d0fa8f3d3dd833f772fd27527157ab7a3909661f81ea05f55e7db4aa5724803dd397ba2c3ecd2ac40ad8eb35e59c937812e9128a639e2ab4ce889b2e3424041dca8e34f18f434a207201f686b4c660745a3cf794bffcd9b31f274f0f3424c7cb3f4e96f86ed088a592b2dcdc5790723fd8b3198739076389b47c060bea2cdce8066880c95c73addcf5ca3eadbc65c9d678ab48449185fe89833b222f374f3b0e241e11150e4365a5f0791b5df2d7821279d6bad5d5c03ed6cd0c399f0195627e24ea15f597cc17e224e43564c0c19de52a4b0820f173be533b4609f184a88c37883e78dcac1d33bfee57dc7183c470d2a600dca2bc830ac9475e9f401cf480620194de71d0bd0986640521fac4785e76170f7fa3523589d79e1a28e33bb0cf1d3aa5bf0d6cf364f051759d7366f995925d7d63e4e9018998539e2fe2ec33808d6bb97f19d1368011fdd3de7ba0fcd0c8d5e569a9d6d37b291378f453ccd986594d1e5ec915754f95769a3c8c32adca40821538cad0eb9f713d9d55450669a009083f6aa454928bc01ef6269d6f6b2b3e3bc3102ea8fc8bde48be067a0c78307e32da311681da1aa34e9c9773e42fa11c4ee901b944876016823552c8d89aeb4d7d339f116a8c22f9a7419dbea813de20f07d517c75e68fb625fec77d1cabb1c9b42809335736e3c229d8e52eb03b64ed3ed0eabab8887b4c40783b8eead306296510c30c7a93543337857ff85c5fa920caf88faa30b621b4af919771952d834b1068290d2bdf58983d8edd1da79962bf3ffdd4a68f1a49cecc928e84d6050b083cbeda878ce48a6d1b775f427bc3ec0b14aa73782def8124f2f39bb1b468c304d212a5a28603548d1d922026981d5c14ce24e276a77112dad6384005d8ded87cfd88a245aedb5e89c1ad45c4088ce71b1360c60ab78f133541fc86d18eca8773350cdeebf62eaf39c5e83624e8786ffe667972230a048fb36c59218199b0260ca4eb934246e0b0f514d41b6c063ec4a780b8438c4e4a3df55403a6ac0c57dcf63fdd471c4b4d247cf451a874daec7ad30585200346031e9ef7cb3c914e631b4ab82818e254ac16723308e32298b33c2098fe994b9bcf95dfc34f76e52c9c09c8b7e38130477e8d275528e6c4d08763e312059f24762c47468ea46301b1280c21d6297444ed1173135fe47363ec90fae82c771f66ba11769cb3c88a49d5b7e5340d986c7a93ce0e8ae0f18a107a39d1fc45b109564ef403b3740e1bc4b511b2507be06961fc0d7cfe0c217c732d7e70929741209f0ccd517187ab400f7d7b2d4b890e06b3cd355e4cfc2a0911e31053ccc94269fe67df40bf7899fed238748d23a239b75ac2a8ffbeb6f6f6b4099b4ef2472840768ff4d9ecf2d917b86d9f98f8f8c720e0d3f0f11bbc3a4f237453a30bef5223cadbc42a9a5ffc01a580194f19c1bbd5fd32e2959d86681ca1e191770c639a07e1689fb2a56e661c033eb818d6bb822cea50c1bfaaa9adca1745a6e86fa2432601508beacd6367e555acd7c4cfd4fd419f1e8ec1d226b167a5a0d1a6626f14c1fdcf23a8f696ac77135e29ec1da5b8615a82ffefc973169bc6574b23b08c820b024fa168aead2ad6f1076a09fd26965fd36e463cc0fec15f8ddcaf57209d71038b6aac5c150bbab2263f25e07d44760d16182b15ce35183b8ea5e91eeba8b475c1e1687bc5a613695472b124510c0c732f4f9bf3045e888bf80f31348905d28f330b4ef89e5e8067c639588e1de83d39c908492a81d927d6dc42776f718b843b487e9b47450a84e2450dfeeaa90c2d4fe496e18bc784a72bc0b78cf39f0d2e37e0f3d2931716d07d7cbd7c71ca5f645a5c3f6099d385705cca448551136eddcedf0ded1c8629d24ad02b367fc030961192dc7d5c6c021f437e0e16f06bd8edc95178e3586dc90af874feaa42278397989f7e083892376e56340ca256539350c60330ef057aedc179004c015fb53e29230d86bf9ab29f3895f41791fd328524d0db0539821ee2b6ad13efd6138928ad4682ce37cda31fcdd81b9959436a61ac456c315df70d151d9810c32a1497677e0da6a1e02c7eab1c12921a205fc7b06ce0771f47338ab3c6d705079812a9e56e76692d2c57fc1d0e84137300d00643a67cdea5c460c830c94a4c26ca94db0e50afcb8c64b227401cfecadb877646575e878037dacac0273024a077a11706c816916282bb87d498a4c1296ae29d5c60ea2107f7e4eba26f92472ff60621b341eb351c78330e91b08f6c258acdc5957f92037238e4581cd4f310d2c2bf0bb4acf425ef13c0b2a8779903fca696399fc50b621135833642d3d5557a5ef1d8380bb165bee7917b7afbaf0158b46cde3c08cc18c2ab699d608856bafbb1c1ac8595158b4607e611d80786ff196092a13cecffae7d3883c6ae88a9af6100ed0d30a24c2693e6373c7c2ac36fb675addae62283afa360f6dc07a52656243b6c350d5477f6cbe3505d681ee58b9de43f9f23340a99b015a060315dfa47c4550d975e20813f3c29173e8f6afdb9406e14a8b157fabb6f2fff2592cfa0774e582ddc60f897c9a90bd716c60c34c08bb1e5ce0485a0b7a8f1828574c5a9b56d7dc13fb86a4b67dac378022df0fbfb67379a59714eb7b2bf0e3b4021e72dc641241fc4d09b3540cdac762c507faaa2548e4aebd173c1ab2f3ec64541d20970781f20ba0d81ec115e785882b14ed03b49512e9fd80433971cebc78e4d3cc495ca8b96017880964625260686cbdbdaef43f2541121138ade4ae2d528415f2e234e8d9a5dae52356e6d4ca826c5dff2f094f4e55740c5cae3a4c0f7b77e68608c97f8a8a52933aa43140ba5ba0c8350e3f6e9d73a7ebc559437b6e7c2ae66f574cb79bcd1ce515d19c36bd3894c44582807eb0d991cab6f65e9c8a9aa3420b3602a10c965c09ea086b42d23990e5d47c014aa0ad1d2dc70727f70e4e043511505c77182b6c4ada3750b719939622af75faa21ae0d582987c9dd86a4861e807fa77dc49a9e9100b414319d3e02452e0ed22bcd6ba087cd1d52fbba097a3617371ecd7d1baf839ee7f29cf5ef54a179087423523b9bc7e6a909f230b9dad3f9a3c2e4a55e7dfa17c0f39e94c0e966b6b2a40132c43018871431467ebc50195e0f3552569f1254882e127fb348b03a780783924258d7184133050a123cc03208cfc0c78f20b9f024679e7179ebcd3b87eabdbdc84e07fbf6cdf3d0e44ee75b567a793226bd7f1a93b804784186b0ff705ec79cecfee9a590da51fc003f48378f76d8acf0c2e59958a619b8dc1b08b0a419fb97f9c1fe8fa0a35f9fb9e68a19ecfbf5517fefce3727e5a532f307a1e94d5f6aaa02605789f2aa49f519fd6a0e3061449a5900e532dc563c98aa1821d0d3a1d9e99fb1adb21903ac05962a980e48444e02804a2920fbda0bcc72ac59b32f77eb08f43599fcea80fc2696fc3b0167812645396c75d69faa8a67e8d27a61518019025c882413d399945d954c580d9f4c172538251275542d569cc4c94ce995549680a3753329f6b96920ede4518ca46d1c5c0ccf6e5841d99a7ed2ec19d3cf6a5d31e6c31073c946c3d454896daf86edbc46fb15288de63a451721bd1823eaf47e1fab7970364afd76e984a92a4e432d3d0e355e4f63d9d50dad94245a3feb68c3daddb4a7216d932c9a45510bc150d10f604c4250933edb3d8979ffcf5bbe2090aa4ca740aaa70da97000d7e0abd22e4767d6c4d790d3a3fb1c49565e57dd9b19752a3f68add008532b216c2458712b1daef7174e257b44f83e84e0943644e870af8cbbe8c554722aeb95bc18ac9580ab491a02697c09e1e200091329986bb214768a9e088a7ebbac9e68434c6c4a9104cf4fdfe0d9c6fb009471494b1064c5482930b91f2e65b58cd94f5db34690c022933d970f5f269ffab895d1c5aeecc6c004983b4966e2788125b0e9f128b8c1356b6013515f3c8242f298a6fc3a929a32102056b1855952b33f1832b564423f2eeb4d28533f5b19a51c9679349f069e8e1c40dfd5109aa5f60dd6030d3d6847f301197b32de977943e0d2f49e4ddd3357c85070a5ba293989817b1c04ea90ced76801a33004dd048c4383dd29a21d0465640d8c1303cfd645cfd91393d418b6cff9e59926a01da81cf50cd63d6037e9f319fde505bb3e185de6163fb636ee3c04975aa058d89c41b6218102ff759865ab90c34025ee14b029fca5f5081c7bd957b1f439f1099dc9daf4a5b83859b109ed0c142af16099246ca1e99850b53c92154935ccdce7c48934d427aaa31c561475a603764b257a38d27761a57ecf42be82038202f71102f3220db9f7be7b79a9e19e9390ae0328ccacd7fb07a33687cd137b552c9f6c211d31f681fe5032916e525d1fa82d75c5b8589b9638935b53cc1ec6923e2291f94b096b6f82b36039018b7f4f1201a9c5a94462d334a7c6aadc4ef5887f5d42d6ac240382381406ee239f7332c121e5226e8af3a441f8784cca317acfe2f9f5e11bb859618d2036bae37db854a7cedd70c8db08bb55a8cef683a47a1d541f6862cda0331e5f6d100fea7a4d34bebc2a8f0a1b5d322ed5fe4e962043cc547402127ac4b18289d4277fa3e78c8868c3b576ac4df65366594a106ed28fa3e044037d6962823e291944118f9f88ce816c5298ca4d072d9bfe05ab48478f174d86ab4266a80d6e3946aa7dc99433e909e66f39284bfccb4a3eaabefac39656d944578515388155c96db40fb2cc0e8a326a362f1808a4386224b26cf6ff4ff339fd126c10624226183af50ea892cd5150e822733d355f5286b9a3c610354466a2bc5201ba40f557a4751b54c4b1949fcebd5ca559383cbd9b65d5f1b1a3971361a509a01eac072f43b78cfb056e24caeafef5ff3a996c22cab4a6697148fa61965ced77c21ac502a966f8b4a2148375b217a620be5c9af3d0cf3f3a9bdd3086a4bb6ee15e9f4bdd955ca52cc7851b55cf05c0ce984bc826f7d8e8ace2d0b8aa52a60b1be625d14a83861b038903912ceea420b439b2a2deb8fc47f3cb17466290a60307f33e76600eb9424b8f450fd993c3844678394c558ca3969385f7a6a79369799a130841a1fbb5d37a7da791c3be3cd009fd1bc0e017f6c1d2da922d6d98602398c00217ce2e898d3ed17887755670d754ffd045cc0364c0119f9b4d4755cbf5cce65055ad4c2e9b98ba82f2b465c0bdb814ef082fd072e927b33520b50253cdac9401299755c21612a7b06fcab86c72eb6cb1364bc055a98f8995208f400bb754ec8acef2dce45153367ca779f754929eba8805a95b3a50184ad888f5fb84eae981e5e159512fc35e626249f305d46c59e2062fb40ae16aa7793c025f24461bae346565fc937c2e8aa8877746ced3f141f3cdac6aaf416181eba55b1b1692d8bf5446814c82ad9d6bc896453eae800e38312355012ef4f2c9d559631bfc32cfd688c33f6ec59b004100bbe08ce9af6419ce26633fc45d2049fdbd9705788c5e2bfb8d1b33a7911d11cadd530d397eeab5a9d4fd8848c01e2d3b5a27b5c48da17084cab24f5ee418cf08cbbb2bddd1eb5bb718916aa08c1a5aed290e8dd016ffdbaeb3b528001e919a5392b6e27eb8afcbe07434d55f2d549b5e0fac7c765bd474ea3662855ea95a0265009e204d68289f2b8cec26b83a3d302a4433b39248df4a13343682fcab510f9192dcc379b2b834f751734e09c0490a99262f95313d40e02f525660ea07f4886fc598541f20ab45be349aaf00b8810b85c3dbee0cdcfc9b5de962ff026a4da3b73ef1bb66eeedb5b84ab1c0558b5c7e5825d3a36ca7841795c380230bfb5de40e07413656cac0b65783aa59d27b303691124f8a13e09212ce4ed44ecd4680def15f82fd6d64b246016d119e04aad505177731777ecea486b8eaf0c0c9a846629c3447766a2ba14b0b48764a43756d85e9b3b7498c46909e1b11caba2e23f2d9f7a6a32901758bffe3d842298e354e5820f78c199e6eaa4d8e170287f8424332b40c17124dde6d658ab82c918151bead66fb2066fbc3ceae4153eb33ab87db14fdc8cdf3dacdcf766ca25b5ad72bd1b890e490bc2a9cf645f107bf631185d4d5ae5c4c2693aa94877917ad610e648a926a5eb547e2fe211ad238bd0224365f45061b25a063be6e716ab644b659415060d093809f6d0204edf01ad78a653910a45a643561fb9f8c066c937004dea733bce8e95f6b5daafcb75e561c5b9ecc575ace498267b63d6b1b6d081bf0c3973bc410f38849b3a3ea4cb427b10ed6df6f43ff1bdcaf9202d1f006dcb5f023dc49c74f22a4702a95709a1246f88240aa5771c94261fa1f6cd121cfae5001cb45ff80bcc0744fd269c04772e89c1b6b0912978df86c94d99ae0c1163318ebf3a6f834c0dc53ad59e6a7ec0f8cef62393dfeea0294326671b60004597460cbe2c096590c0e7122896722a06fbc9c2c4839a7ea4e0e5d2995b58f821556392a2b08f3c739a5c2e905808b4c0a6222f92c7a220a5eb26cbb74a625f8fca6d72c7d68efa5a9801b5b6b29d1648bdeaf66a8b6586bb045fd8f867ef29ee810c31a6c233c0dc2c875ebdfbfc073ef62b02bfff2aea29285b8cfee3ad45afac12915687e8fb9b03bdfd1898bf8e255010680e7eef5224f408247863a65bc03e1e450d1a19d2c50c1659f5690fa4c2aa4d1cc08c1415783bfb54ecf47963ce7a5796f63b4e13134298fab6aa676e48aedcb0911920c911622b05a868f134dd7b4a64b7cbac0b7a4d5619c10d7df7a3aa0e02ae49cd0af52a0faa0b7c39936a39a80b4ce1987be755eb0125900d11d96002ccb15aec4defd2052aca8d4bb796766fc122325c687566606068117cf4bb989a527050af711beb0125376dc28f7fc0133bf47d59261ea11d6dc37682b1724703c7d54613d47c3603ccb0115685b31eb849dd625178f5c58dc0a9e0dc490cd38ced7c7841ceb6db2ffec514d3a4a0b223da294f5bf91531f0551149daae7550b36bc0238796ddc3b07559412db95418f9fc2bcab66d35803cc2a6d5eefbf10dc11589cc9bf0eb87e6b211f8fb38e4a45c36be6f40b207af4aebf503cb41119bbc12fdf6c9ebab791cd9510a46d6afc43636c88d0d421a640a56c38ce610eaacbec5088640429ab2c3c1fd62f80f8fab4aa8394e434300aad3222b449d446ee46c78fc2dc95a7305283de48227154003716cef9aedae223bd3b0cd9075598c2223167ec020d1c1af6367cae9610a1b8b10e43fdf0e69cab19864e6bc6ce4953f8b258c50203e1c32e842da239e5651b815bf2444e05e2bc1416c340bcb48c4cc09e5a86d57b276329d6dd505840839c4a640b326a39f1b796e4bbbfeadc25568f93021100229a0c2e4aea7e58ed025a08891bd3347b7d874cc9d448be33c7c2c257e215dfd6438e62509f1001639b8588b5f22ac6816942428a0722493ad4695e4e0e7a8755ff3a27d1cb85cb43c153a314c4161269922734875a88ebef516edde0d0f70e868c5a5598f23bf29fa80183c204bd184276a1e39ea92178049b94f188a18d2b29686eacdfb08f83bfa7a9c48d1e0402305dea31ccd34bc8f680694db68709ec85e032472ff3603ef076c54b5b029e9f62eac502d4aadb1548ba9a25026a414915c87b9231f671f1562ec67e0d76234348cf11d3f756e01abeafd303edf79fa5eb0d513ddc69b5685d61e4427e86f08633294ce250fbb88a843dd0ff3b2e4692ec6bcd971cbf55eaf1eea40614e1469be5c5b7f6ef3a0718952543b1a8c8f6cda78b11d719a95977078d8eba4e808090d97d8441d392c30b0c217a483e22751698109325abf8ca90597a61941b6de620f0e97a58dd14625590f767a61084a787fdce763d2abe6fedf8c179a66e808ab6def6ce20161b6b1443c0bac3904661945906103ad682befd0b59c9b692c0328c0aa3a7c68e1cb2783f7dd8cda89f9c91059661a25d611e0ca70ed9deb0a3e6e8b1d7a3e650736e7701db2a982f30b6c67a0e5f38863a176f6b310a6468a06f9d0bd776da134cdd68a6e59863a9cc0216fb38f05e823c3012b8314f88326fca9020df35903b7029433e44c16d4c6a7a3db6b294b77525d13e3e5d18abed547315774691708598265828eb93f93c4fa276a54609a566a2b7acc6081cea4052c53e92784a01c34717a09877f858e5c056311241bc5dd2a554286608c743a45f7967c04795bd49fd7df4cb9fe9ee5c6959c77c0e77148e8c5bfa127ce882c5824730a4937864ac2e762550572312819caad631e2d5ebc640fffd3f04961f12e2eec49bf7b425eabebb102d28f43b0f8693ba63ff07e00dc0938262afbeb439c70de6f540fe13308770993cd46c8d14ff64e60c2d05f42d7101c8890e791d4cf4922b25bb6823a5f2d0490066664dac9f2cc11b7e2585b331a343d923e9052870eb1b1ccbc358d3adbe06b27edad3f30c530973d8c6cfe39009dda84ebdcc978c8718e93d6950e9baa24ba012759c79ca8388495654e0972374053557af47e01875fd03899c0683c791a44b712ed1d31543dc4e488df0dac5c1855d51cc9006284436ef75f0452e810ad2130ddf455425f9abcfe526f673ddc51ffa1e6be786505e6d8df32cf9d03b56cf701b7542ed74838643569e85290c0072905c6a6f679ea2dde8e4cee37b6532263b78e81c750f0d7cb8c440bd7da55286a50134f82c04029905cde45e63b28cd721436276ea682bf00da6b277f9d2e570784424323ba8d47357979c18787168af5382d57aa7199f2609531398618818bbd6c23ebe235622a4392f3bbe0166b613550fdd360ed896453c158a866cc2c43150669abd97f29c4f701c0b8dde4ed38958be45cc48dd1f2a8cb65caf0b847334c30c8f6578f7cd7c763cb523ad5973397af93c5f1236b8a691135b4e887fd3e63f644926ca4e28d3287b60980644269f461f0ca34d15734f94cf101a7da1424e16d0fd58e5131f0bf0c856cb1768d6f97b15d337b2c59b72e2e9ea6e40f826d393f219bd594110d579a57c863aced7854d41e4d16c917d06c7a3a935ce90eab0aafd227d29728a2bd12e259998190ee2a1c205eb08d851ffb3b828ea1f676c684264283e2e1e1a1298a57130ac23f1cbfd7b285faa1d7d0dc72c1386e28a57782fe01f4501a34b8683921da5518b0eefa2e6857ac49dd9027cebd1a4ead7b71a75697e938d5d26b0eac52d71e652dcdf2e26640274f0cd2d5379b85527fb34748ed42402038ba4d42d94eed8fcacccd0cff7cd8cbc245f7801be090c382a47fe9742a9402f068bc947b195a29c13f78181c1f469ffe6811ae70c017476326572868ae9383799e060c9f7faffe2d97e6222da70dd5ffa5c9204f7655da0628b90e759878b95e8c3b79684045b9a9d03c45338a4536531c6fa2a42dddb1070825364c4985d3f76f294a13f178078511a7851911611bf66ea1e25b1f358432e9556125b73ec7eb0019b2b058f1f091c296a3480f0c0c262d1adbde0d5764f0cf9d3919fb501bfb5dbed10ef086e458134afd0ab081fb4cb08dc07a0b48cf1d5e2bd2587eb22cabeb43d6e0f462b5e20bdc897bfd3fe64b4b0f5000d6fe8cac6ea77f7c5004aa67ff0faeec93773bf6910bd57da954ff7fd9eb1796910f7225404d8902525d293291336e84f8089e61c41bf9778710fd5cca4574c9824f0cdd1fff2a0fb30097cd7cb9294ef51e020e27231cbb4ed624606605d53e34bba52943f3ad4b8ee7529c922bae9260c1d1924440177a82e16bd1102dd61bb322438277ff332044698124746c02305db38d197b3e60f6ae51f83cf4e4c84ed26323bae5edfa13fe18e6e4947aa4f0cf206034e369122cd776081fa99b8e225176268fe14af9fa0090627c227cb9f042f2006d7aef67b0cdab02028417cf3dff5e511cd416352d331b878e7d6f1188446b37dad4591401adca8d8c6e36122e8af3a4e37e204c4d33665fb50e4ea470177e9929c175d1368bf7ba14a18bcff2065a1aaed7fad81671498b161bf801113d6d7f9bd2d5cfbef5327f49d70b59011be8b289522f46c6401a98f3a3a2516e90a29629b8e3967257c21131f4b10026461a631f52a0955cf07e3672a33c44659b0d00d9046ebb00d0d49b1f76f97a904f8a9105cf0a23a9115dbfd44a31440aad003814d1cb2f06a3a1ccefa4608c1e5bf4fb30d6ac40e58c96bbb9acd1e3dc877b0e771f09016e11ad563ecc8392e14aaf44869592f45215edd69d418bcb966daa8dbbfb8e92d466ffa19b42bbcae9edbbfcb07ee1e021d4dbe0a67fb3aaae8f9b491008c41feca68ce6216e9cfed952511d4506f3d285a20458119f872009dd2ae9ec7e0714cbc80598f36c2fe6d38323331aea7b4f4a553e55870d4bd385ff5f316745d302d8445ac4f006e52b9b4ddbc7dcba035e6b75f34c40d62daa645f2b57a59acbfbe5f57c7ada4655a57b416dd502d52ff5d600214d90123116f9711a01ea246e2414739325db16720eca076fba314d0e40418065e63ba717bb43de59a9e31024b9ff8b716f0960805066e1e08e335aa4e4e930f1a914085e9da410083c2f7ea64cd3e40265e8bc84a0f7a7f1c6ab1b30d57fd14b7409c0a64d5eaf3e0d8209aa53d73e81e8359f9cd2296b3825e052f00de001f16285f789c93cf42bf0f217090fec1b19454f3f9ac0f7eb4e3bf0ee3cb4545a0304c01786c4df2f4ddfa48679b70816c5759d75648eb1859ecc8980709d73cf3df3dbfe0de5c7f7f955806e414a81b77dc1afd29781c7b1c36be65919437ea04081cf002ece314aafee2a43b1f744d5bcfe22e6b1af6327d55d662482035b7f407546251fc256f4861cfd042c3222c47f18ed89a2a702b1d78126a62733c9cdc3bf6a59980af55acf513fa2432c0a641110de75a6bbc02f04b8081514cb2b3ac29261fa52839c5ee420ff06f5680a9fef87521250fd3c7cdfcfdbb3d1d2a0d9473f7b5fddf4ff223f121ec0bbafbc61f20f3798f56c8e620d40c1dc6f65afeb05200f8a4ff4c06df443a335e7ac76d4f9342cead3fbc2703c56a219e995fe294bce768f29439ee77822122efb954cd18f132100b08dd26d8379cc850266c50196f60c3c4a5ffcdc0f0baadf2778d1a805025764b818210d81373aac89cf2fc298b51e293bcfb6e618a89b09e417eebf5b1d9fce253854da672f1d2caa4fb986b96303aefa431f59e2fbd2a3219204703078a3bbe06758121c3e4bb80a788648614b106688578cdd5d49485408c1d40f5101b386ff7e9aa179e0cf2f48e2936060792b637cda393d10705af888831cf870cdb39369120b16063ee583cbda9fa93545d53b8d7d3279f89753035d58b1a292e30dd57100822a07b2286e3a9b78c03e23c1c7fc3cfe092d0bd68d500cda973be8d8f2540f6143263389e012e9357f46e7d03708586072e53807f6c65ddc55c890f148aa2d68dfe19693be26f7d66d38a62406e3a1b9a7d02298e0d2dd25d6a1972bab9bbedce51e420c83ada9fd1ebbe612a9b6baa6990ffd5e5f5ca151046e1616e8e225633b849aae423de92a7ce2d27859ad0dc3abec5f54df247b84e47248462bdb34d86dd3c7709ac99f3655d4a41ec5ed523b0fb05e3fcc2ae67d92290a8d4930c566c2c7e3d7430566e6b1c20f185edef9f74b0318624b66cbb578644bc613099b94c17f03feca77a897e6e5a0497ff9d2fac71c94a77e6164a9caabb224fc364278e25e88b0ee0f0973d0e38623b82476b6edc7429f86284f7342973ea19b05a6a6682a374fb60b9e0fb2e2ca52b877517a7864243cbefbdb5f355703e6ab6dd95ca860f1bef2420186c0171d32967643cc3ab53a0e8eb8cca6e3dd7d822c3002b8a8f1e9bbb8d342e8de55fc90295242e8bc86cf0f4511fc94d531f4d6670389ff5a1597fe854c8d4b1fa6a92ca0a0a1c6a9aa8df3f110f52c90a0a70e902ba1554aa33002784346204d39b0c97377e16c6ca12c7ad4cd5ac98f5f8eb98c5456d9cb4ebe9d7ba988bff723ca44575a72dfe41ddee75487c47a2b86720aa1f04d9a2d428d86c65dfb78771f4ffbfcb0bdbd6d25e30b2ff9613228944f42bd25e1895fcfc00bad22aef9485357c4e0dac0771a4eddaebe49b064bb4c85520c42387d0e12e14a055a041845f8f07f6ce55f51cf1bb6633a99b1710a1a8338ef5bbac50ac4712cbe315b1e5537b4a13137e815fde034e8e606c81168d07929838e53604d11581401afd428c1533d7461e6b125db600bbdecc14aa48a28bf503907b92b0015a742ac19f9f18a346f2e3d223e2b743118ea8693ce599a9e610317fb22caa3e68919f738f5f7b9bdf396bcb73dfa8cf06a466d8637eb8e1fdb0455e977a14a70a1ec097b265fb0e72af921c80c1ce09ee22afef4fa19350a3c0c80c18120b6c1ab1e0a07b0f11498317d033a36d45dd5628e3b096e0ed2be14cd3e1ed231cc6eb02720e826de3848047819eb3b1ac53380d4af8a545752b259cf9602c83535111d72fee41909f529c9a64c50d2b33bcd7485989133842e085ee5998a45987d39ea32d46c94b0d704fd6a9c0f6bc54a272095cabace9fde1a4591d2bc24dafcd82cb42ac76e1328b069202cc50c5c3e41ff69cf80c527f138d5a82a0439d9a4df09a649e5f70cd98fcedfb4042db9acbf7c0841620679f1cc18672db1d0c60af0ed4d7f5486dfba047d03aa9087c6c6521437eed83e9547e8df197a908fcff54048f04cb0705c7357c4351ddfc065dca10cf5592286f0b3b9cc7ae4e2c72c96e3fd3f2c5e015ff27985664094f307ce1aba234250253cf4e492f885de314fce400f3500a703dc6ef39a8bf727fa660f09b67b7021cda699dbc742382107093dfb7a802e08ccf7709d0a4b3224d13f3ac7ca1b5cfe7ae60ddda49342c6e0f76887c23ac900d22d4f98b526e2169eac158f3fc6def2913ac783a96003cfe214ae91bb26f2dafa49806820395fd079c639eb963e19648ae21301ef53b4370b8a1d659609147063672e5de90c74b96cf4e7bc69b28cf87a70d45b90d588db3918f607a56c4467646a961d413189556a5ca6fd5a3f98ca3a2eac7cc5f461962faa59b9ec284820cabbfa1246708e7ce5f6a3fdd6e84bb4c3ddbe30e84255c07cbc96d7dede8593c6e218a898a3d8def450c832e128214d5c3f77c81f49385311fd93f9f0a9f48140f7477fa7a5b392a960622b60dd0d43ba44da0aa46ee65b7dc38d1fc57a0f2c40356201663e5506cd22b95c0d0f3b4838486fb56ffd5e8b98ed943ffb888970f82a8a11c986382ec6d9ef786f0e9405250790a80d7c6fb3c1c0080cc160b063aa3b0d6175078ffe9f1f6db110c398553742f021758c808b695b7252161df0f3423c42845c6082f38ca516d3cf9328c0f7ac87ad7e6e635fc14d1cc148709e3b9aeb4ef058bb6001839552e1d808ff4cfc6a4c9d277a2fe210766e0daae104cce176afe7b9356d9d1b25b8ee5835d4843b8e818b1766e5f4378264d30b68f9eb0edbf3c970f1a48cf3c49c1fbfd05ce3d21c1e78ae5b26fc554ac35648a536971925df9c904e884640bfa8a4ba22328e27c3ad88a0c7b84dba751921ea24802c82b0d680d0a7375ef879d38ef8143561f74a2d99a4a5a7a10ed1bd3263fa36113b53edbb656b44424ec612b882a96fe299ed3417a05a378268b45f61fb23c3438ec9e53c7a6a445946a0a9ee59f7b890d5705f2fbba96549683cd4b2d131d07ba68302613d78c4e25f46d5f3cab2173e793e3c84b2cbbb7cf8894a877247b53fdc0636dfaeb65ff45ccda409698ba053fa4168ec4aaa42a500e7da71fdce6fb72601ad1134408faaba2c2b350f898ce15477969f50375b626a1215d01d2a1b5c6aafb66eb3917d7a6e59fe659af54006b524662605568785c41f93ff8fa5cbc03180e8c565f301fd9f341710eb21d7a05c3707dd0c6f233c96d017a282942b2807c4d5cdd6de412697595210a975925c5fb0eeee25a4fead6e699b758418abf03a68926467c8849c608d4ea42834400fe49fdacdc00e8528aae35830f473dd407fb8ee027573323dcefe6b159150fbd4a40e8f389505df6e99e02758a2be77b5365313a16178150a9f827b94032f49f1609bd9d4e6c353f8dd404b4afd110fb342355054cbebfe4f2922b23bc33f09b951fd1fc44aecd71062ab84bcc17a2354c40cca7f352ed03d46a9657f6212ab10973e20eb84a29872ff7e1f85f2006df49fb067a62a24b552d2c100eb8d714d64363c4fe5f6239d4f89c6c5b2a501c4b1bbfc966f4e403000f4d8f1ff9b7985978e70d31705bd40486f894ab60feee7bd376a114f6aff62d890f69aacb8c2302065bea3fa83154f885ad0d3bc2ba19587d8b70a40673190213b8eb19e73847ab5b2bf14add0b690e7fe06819ee4e94a42cd39dfa2940eca9248bae59e80b4223daaf39fc50a264cc5321bff255a828f88adbc5114bb40801b4b8dc39a9845950f28b997bf425b945db97da046d592c1e798c3c96fa3c4c501d37dfb9ab6e83f591434170c7bdce3e82efce40eca4940558f2c321f91b32a5d1fb26ad05a78a53587d9f81ebcef73ede2e1e2c85add72648e202897b70df94bd756a2a694c684d16ac9804948bfba14e4ba069bc3984c25f62a6fe8b43fe0108a3ab2c602f03ee189fde625c9fc2775087f78ba12f941d2ede78ccab32c107b087fcda89f6e6b2ee439ca7f25a5870f2f6ac776964997287e36fd127209419a9eea964f3c904cb80ee1b4bd58cbcb9b460a23117762a7f063101349f7d53ee73f256d2334a67b172138b25b52484056c7eb8c304aa1890ebf4cec77c72e87fb41f95d387a8b4102053fd0263222fb11e3ffecaddc464069f06995e686f2875044002ce544b046c039d4225561f0deec9598761f878d214dcf6d683be664304b0a741a4bb1e73bddd6111b01f8011f81440185ba27c8586607c92095063e48d9a73e82d585c75c739826530c49e93725fac41142a2efe387f0e2faaa97ba21920357270475f438b4e4352949d6906490459e59d016e845c0a2f84f3318d706a85912785e10138e6447fa9ba466541e7bd00cdf484b31fd3cabe8b107624ac2bb5f768e86af36451e660de731a681d151c2ac44af69e244d30b58f5ff4b34620b852760b4a37a34a39bf06e87f2d2423ee945999618f33e2bcc9286705a855ade411ad8d32224508bba60279987f5fddc11d5a91dbea1dc686e7119f91d06ef8fc27b14b86a447f65f2d3fa447c3280b7595d5eae4c0a9ccf6f1c59a03ff3d31b88b3e09135ebfc91b834efea43c4b43b1fba52e96217d333bd750d09df6ff0cb7efc0538cbc6f3fc4301370030170291497790e984900db4f2c95a58f7e5ae861f3845cc93e2138ba8147537351696a904c6d961d6c7c9e94399d2e0e2e799ea92ae15d67c3dc87c0d60615dc3e3ef6178659247678e71908ac051690ec001e6ff6ebb0d426450137ec72ac6324f7f8c4efffd6084bfa1b64e131b0aab80ce9f5e3e48e80278be486065d507d3781098adf0892a3f5015818a41e8c71c6043000e033a598347ac6baf887dc9a5394539c18917d7fe0139a8571f8c9bb4a72221194cc5b1a1386ddc127ed8fbd4738534bcac0de30f873f69c980b329735080f884ef5b70a73a80c915659b9d3b4e933b5a36745cf0958f6791cc13aee4548b9c66234c823925f770621a70e098e19f41c969f05c541676e31df234b463829a204c3c41f52603d30bbcd3bcf4616306360bcd14e76083756453480201a6fe1fbacacff830a0ed3c0a9d3e1642d12702303667ef97511aace1044180e80f884c338fcf48175e20d7c914e8c5324264c1a339b52fac5afb42447ce3bf7d0bb3c159dd6cd2402e3bd869afab9800d5a3caa00301166cd4af78c01b0a3ca4d26df238b9837fff7f2de90008a626ba675c49beaed095760cb84ea3fa0450c857dc4a0e263e390c4b41fff2a06033a43b8fda224f0426e1d637b3eb7772daaf3ca1f90d4eb50da96a9c313ed05a8900b8f9d2a94d500e2aa4eb73747adbe8c652e5e056d7832ac973ab999b2e63fe81bd1721f4367d82a341dec5b6d0d472a731d93c6a15d91d0afd1ebe6a86cdad7678bf54460cc64cf25acaa16048805dacd192e36140ab9443c864757ef999fc7917325cbc91144cd966deea68d2e5792a3e5c0f10117626c3b7a4af6504e938a73a3c55d542e45c43e8481eadce295fcad58c9c4c1588e081e8951bb6f9e9d62136eb6365021e9708ad01d10ed28c129308802798a636ff093f6317d8c05193e0cb3c7cfcbf1f9590e1a2081b87629512701a5620d2d73e4a246aa132e74818898f088464987b116476b466f08dae5efe487e9844a930f0cf7329d97b0d364af1edbd8e4d3bf18e61250cca4d55c61a1148e62e5a4634b97201f5157b2352a2996bc0fcba3618c45deae6ae9bd2c08229e8eb25346af29b0009f5f1c79a561c7918fd2dc22f3551a6b09d656574647fc87677b2269acde8400b84e245e6d6d2877f73b318ce7041c66ddc78c177d6b9b69fac509b23ab24e28697fb2181aac345a77e490ead1d204e4bbbffa01e2f978916540ccda05cbe0bdddf32cdc4d29a07c48691f0f8b6237f4ac28f8d817605a3afedb8f9dd01b093e2b7ec50ffd2b3afa033c78ca40f0a29f8c5af960562629cd0f6ba885a2e7001429362d7d9ba73bb783f5055558c118205beb244f1594deec162391888b8a9a34b79686e5b2cb3bf5856265e00fadb866480dee7fb68b3ba3e5491055a9844e52f95fcd07e999ccce07a2e8716943eca3f9b08495c4f42053dfd4fef9717a3ca67b90418863b3f53f4604f61bd48b1425c158ea414628db2054b426a2f6624cdfb54fb346a28dbfd0e7e0b30a9572162471e77fe087836018ec7a696e1f5f94f67ac6434d83fdde7ed7102c046619535435b49c7832d7d905554685e5132746a894654a5c60191585a7e60b3e1af3662e5a9108e3f7023eb17f751e088edb2f1b114725f80ea50c7d7a16580d890c2c5a60d87b7609f5d7ad4a2cbe770a57f8c36cdaaaeb154a8ed4fabed5499647b5d5db5ada1dd7867cbfe920b9cf5f5e23399a8640e60cf88954d5ba3d0c693b5056ec563470ab41bace442e47d9d090af0f985751a9abd5d48e0bfb103a75adfcfe49f56451f2dc33d156d4fd6be560c67ce3e73d8d9f8bd35795417c84a887b14199a73b30ee4ac88aacff9621766493767a3010901227429e4876c904b657202afd05e7efa8d27c143b181844b59150a2775fb050571c674c19b2de57d1db63a39778be4749a6874ee765eb39c20a57aef78432da0c9240d35b5afd6764bd37a52e9acfe97308802705a9e076cb0345a19bd2014e022e3e92b50f682be8b77da1c8fe6fe2e0933c18fd83be74c3a0017908cb2b8be99b418dc6819fdaf86bd057918c648c33ff028d0f9f76e30bd3a2825a841a7fe6351a0f3f6de3ae405e0519e91907fdae8ad5afae22988918477ec58dbf8247c5cc3455016cc28c1f4067a85004fee0338749a93149a6bc0a3d25d466029202a6bcf8893301df79ceeb2684982330bf1d084b6b05877a8d6edd423020caf704f52b85d9c1ac1fab66306af04269efe8acf883f94b56501330a55392bb4895f7019bfcf2fd4d9bdb1701d09448328a946596ad5a927355c62e10a378b1d2b02f388c2d5a7d6a795b38077618067dfe5ab1e7e55b01b7ec720c0265c4b4c5fcac6800014ecb498d9bb0d5f72ff4a235edbc62503fd47ff83cf9697c5f2baea1f2326c74a93cca0e01a009b3c7c4378daec64bbf11e0b49c3e470927abfa282e88562950f871754ee55e4515ba3ab0e91c1faf461da49e621c292ae5c2e7d022daa75b03ebc9c6bfcea793dd719603697a9981c86eb954e79cf8c446efbda489f0d12724ea021803f9a9645a8bbbef2799cb0bb5bcab84789498a2b80480f340e9afd5012fc788b7064ea348cc686ceac4bddbb1bd1324caed8f6c394cc76331eba06049ec7e70ddc728259ace9f03971900ba83996a90766766f39974a0d05d39278457e67a0cd1f7b3cea1975c0ccabc0c92c974567225e8161626e4b9bbe3ae79169e857af200b6fbaa6b35db72904fda19615adf0722342e966b5640c1f177814e394aeb15ec9c72cf9d8ad7fd720c0293398a2abd837f08ef0ea0bb386821573c4be9c44f0de10049b8bf34f25c1f9f741aa8df89be3815ba7c85242502b00fae7805a9fae794785bce604580360c9ddd39219dd7888987c56f7022135c052e309241202bbcf2208a0c845daa13c8689e1c40757769012688fef5d43a85f52bdd5b4f3dc661fd0aece4304ed0b0f212403d837df32b89705aa6b13f7025d384362aa9f318b18319b56525d69354b426ce15cbac84af82ceff683ef28061dd42c01930103a726781edc2634d7f92c040bb91cf47086e8b8844f26af2e59cf4685c730fa10eb4abf553cc1013dcca1050ec93d0b254398c468ef4d9f6eea3b07502e499276e2d49f457e5e9bcde7f083496aef2872f97ebe4b9f529305d2402f23f64a53962e2923ee5d9b77c35b1d5dd8a1829b701636b626f28f9f37678bf460b14128b05f82bc45e4861120ef857dea628f6b6fd345c9b3a09b35b4b05949a07e3ead6e4e668beb065bfc3464f92134ec4c9ed0e7c274c6254a6969f77d0d776b2764b25dfc00dd642ef28b48189390107c97a1ca441be9a747c390cadc04ea9e2726d617698989b707f2cd3a3fe2dfd30640a93c9111bae6b88acffad6a9fd0657821b150c8fa18030b440a1fcf686020a6798b801bd08055d80e8aa3d56731b0792cb485521702bd2c0b9fd1d4f03bc6716c757bdc73f7ea029a3da08765921dfc14067ff2ae51b7f8537a807ba1cf06d3f6fd157f898ffe967ee84e89437f4a7d7407255e15a04387e4047d8c3668ea4b444deedc2e3e2f3599069f0ff44a1e74bff4a3fb253ebab7f4a1774a0efd53fad13ba51ffd2ff9e8ffd217dd9312f940348f859db05c559b9240b0af7e3839120cd75f117c372f16a04b666bc5f9a623f847094e912173dfbe73b47d887fa43897f083cffb6c52b37ade5c4eeabb2c408d7c54dd5a416a1ee7cdfe76dc43679693dce4eadc34a87bd979f64934a55ba4689125d6db722cb32c2c1763a6bf00517be70ff5dc7e92720d47fbfb5ffd40ff4c5a9af564a2d772fa0621ca70a50049d2e1fbf98db9b9e3a928e2486efb5761f0a810eb4b6391866a2aa4de3e533c9553fc39914783674cbc309ea805b0762d9b91539b7d004e291861047971b7629624506e40eca472ac3e290558b506b5da664fb69873bd4c44e25ba85bcc084d6ede6641842ad00178b6c1464dccb298f53ce910cbe7ad43978e39064093a0974a43fa717574eebf011a91324a6173ff214d12b336472a37515040f32ee2662e005ce59d28aed2204244fa8e02eca73f21a2a83a8bc7780e860ccd2d4ecb95a5688954f5a7415756a7e715b743e71d76b6a491f627758a1e9f59e7a3ab3850e40455aacb88c4fcf7950dfc71964f8144c22df0fd73e2ca9750124aa7e748d6c23b40f492fc3bb42c0f31d787e84e7bc1abe061a9bda9b47887b6745f7cc167a72af4b0962848231d97db5bc9902fd124e41b459589bc2018978c491ea84ac70938d4ba4382e5cfec133412ea5e78df5a985d83df252dc14bba2129db73e77e5e4671d31c6bf6ae66277e9ae162d5c1e4f33d4b6e5aeaa77a09c8abdb969a93e97d218e8ad41e578e8202a4b9ad69c32961ef7bf6bb31bf674bd0e297bbe8b5fd9e4d62d773f8fde334ce30262397c1efd93a38a5b7a15a5324537152c3e6c7cf66fcdb9e6645562f5cdcaf2969c1ede7a97f55e883a36b4612b67a4a0b4a9988482b1fa4d04ae583ec146bf575e03d2e7fb2521b3a0837ffa7e219ab3262b58e4a8f08ad18780f0eac3f1bab50f786a5a2f080645882d0153a90a1df9699d8d0f6c354ada0434298c96effb055364ff53526d573874973cc4a337e173ebac27d8f4205fe13a8e86b47a25cd805f053f8a3c394609a0bf8a78d26ceb67752abdbe388568f8f4152eb052618fcb7e8895afbcd59865e541146be6b2ac818ec58369c9ce23d3b776fc2305be5b2632e177ce08e005a6b70573fa0e0e182a0838e1e26fa05e1a75f7a000935fb08676ed5f323f3ce2023f022064f048137b77c756f0a73a5f7727064676564e3c9fa0ceee1ccca249fc16d8ffc152ae8fb945e14f07c855613fc05b948eb06222a869b6d8f86015d91a21b1bf1fa34b9ddedd31aa7461150147d452aded9237a0eb514c36144bc1b850e2024e30caea3c1f4e2772fa6aabe5f0b690050d715bdbfd915824dd2c9856b27732e665da53b40bd5703a4286db430913e3969e358a01d2a9db7c98d23f48e2371b37006d6bbf24c3ccbc0147d99e489109773e26ae85bdb40611959454d24c59e12b315e2b10734c2e0db5093d496d589b4aef92998d6f88e6e9c5d5050b139ae771452c475dfb396990adf6e508c9a1f5d9d90fd339879b88acc1a6b4158614713d80c9d3c1f80b3e5f79c2cdd20964c63c6ecf9648389dfd1fa9e23e0d96f9afe45404ab1daef8166e45b80091c01bfd79993fb0e0949d3e23e92eb9f61a4ded11085ea01b9fbe9a1614f8236ff5c495e941e1fb0e72370c4068ea6ca656b2cb22c76a6871f0ceb63faaa2ef1fbe282a5af19135ed5829eb0b2cc61ecc95ed4c153b9596167db5ff8c206acac59711ef4d6c1a4f33c9d8941105e57a748a21a060c5c14ec60bd1655eddcc60a1188b6b781c7b8619840676a3671e31a5292a9734ea6c91ae3d66183b56aba62ec4adbab4325676fb097cb2b5a79c46910c804c6d2c43befd798ba3daa6d1484118109f849a563e056cab539c6143530ef4eced43bb1ab024bcadb1f6b96879b6f2490149b112cbe28727b0870dc1ed76a18715880018d2c24f4ab6d28144a4bd18c842dfe0f9cf425123974b6982ae9667aa3f47efc73864ba4988eeee9afe1ea576cbb0fc13678b79d131fb740f7d813d1680f2b8446c4ab2c06308f7aa7c75102856e96e6ff79c4436bc2e803c110092cd6a608d8eb891e91eb7a95c5be0123f149a34d5b7718b64614458a16ab91a5393488ccf2bfe84c7f3edd6632d9c2c37538a6078017962f494417e3810b6d91c6c48ed86df4b13395b250e4f6b91423def7229c3fd02a4606655149bbeb49af87e42f2221c60d0dcd5e5cbb946d59f9650fcec0f5880b4936b2e37e8062d2078af8ed7ee26f5a8b73e2174fd4f50e07edebf799c3b516c51637015907c8b0c965e22d8ae6ddb35b53fd5462af7f0204c4e021998cec8063ee5e618b355b1a14cc87bdb8a5f00728ff00b78e3b80ed4483eb6cacf03c6859a61513512029fff25ede90b53e5a8a42148970da2bf422166cf565d97136541ee4254347d826bafdc31c932315dbfeb7bdd332e8656a95f1025f2772428e0c90f8b799b755e5a9fd5ed7cafe657595f6e6c7c0aeff87d01e03cbb433f7aa11a2d0cea902f947e7a657f7c6ebe3e5ef4e8e606064c0a48d9283985a774b191c2c07ab4b669401f16ea1e87fc27679ccd8622423c8e6f888bc6f1882a2c8fc5ec207c1a098e8e33283cebc12cf079dbd7747df5fd277f981096e1a284db19b19fc76aa936ad6436417d6adc3dc4f2b4f21e3d2cac492474e080c2f1111f21d2fb894c32b2832fe3e772c754fc70fed424cb1a70cdb3c8fefe74dd6f97e76daa82d2ebbfc0b56762c2733cff4decde11062b9baa84e64085040bd76928dc35654abe30a2b56f375dc5432e120908cd08ee2f4bb6e91a832f74735bb8e12111b0634d4dd51ca2f08b18681b6041adc059ce55e2530899ffa2078324b5709ce9dd5e17ef561a25cf9f75ef19a4e1530ace8003eeebe5d43809b4859c94d0fa8962d5e5cae33c899d86dc5bcd2479f106cae265fbbc05550b7dc2c2de0197a20e98f7d52a181b0ac0abb93528bdea830c28127ee037b45b15fdc9674154495b21216e7a24d0411a21ee34630bd7ec44443702eeb0bde218e1dbfe18c7fea471e94146b0edd53c621b43229cb749effa06ca2896452192c6b0f3811398001444345e83a93c9b061bacafaa75c9195316d26c2073bff87cf320897b6fbdad914dc5d56b784272adffc9b33402a1e33378b075f45e021f7f937517372e68c8ff96be095a0c85e0a3c87cad35d069b01d26bb9b48ede5fb418fdc6581930e6e66812dfb18680be0ba712489917f1cf975a857168480a1d5e5937c41399fb44152f2b7f196df516303b0bffdb16c821c4fe9ee9c45bcc4d8280cf0f4fb02d2a1a0f258d1c48b11b29963c422235016e4010fff4d926681f444308180bc924c712a4f21308e80d7227e84d898da850b90788cd776558d033444064150b0fd6eb4052e03cfe84ead646f2bc3a7866430ace2bca713c1bbd414c2120c7e8b724c026b0d4ba7c594181f67edcb300ac51ca5ce266faf3785bcd0211cc20a28025583d95b9f498e0d4c4d649dbefc557823381e3d9adba830ddd39096434264443666c0b78a9db55dffd2423fa1acaf49230d2a6fc4eec403ba010380e4fde5ffb258332982d86f53ecda4dd8d39b82e662c9c839b1b07712e3d1e267a10a8e633cd71ce988a82e4f2e191fec4f9a89880a406782d65d30c1420a132ee1281cc2e58a259c123374aba2452666cee7391a876047cd006fcab3faf7ab42e3bff497abc12c0960f849d65dced2598c9eb6f9b43aece606881b6bf1f462023f129ea5a320e81324ec19a964ebf337d406e3b134849872c33f49ec6e123fae9d89a0f12c06faed587c268242f5636bbdfb51f7448a84ac84902a9557217de4567308f80963e057e66556298fa2918307492744a2deffe522d071fb572dcba1bfc98c19e47088797930c8e5da661d12f7f707799300ff9d91013c969c14a474d5d067f68cc99e2dfb8acbacecdd7e30e29f823929b8f5fee1349b0a04229fb8dd4f184b4cf4b0d198d70f85e644201e9b73fc5dccf6d1f828f2c7565db6b3ceff98c797fcb1d3306e5a92914b8cc3a89ad81b359a7da9317193b30231e7b9b5eb1193481f3647e02d378a02486ad3f07873e687e8541a9c72dad93922e23d5e0bc3af11803267d7e14a53e7d4813d995efd124688e40bfe3f8b28479b6eea68d9391408404db3842e634d2d870fedfa91d5d8b0fcda8c06480043854193bb87ee6b03a8fc429cb42ebdf59200de3b9930b89d7168cd2de497d91bbe6dcdf7d46dab30790fef7da5ac4664966d88a25327a16f8a21dccfe936b653d3e0ecbf1d720f3bcccdf68275b7cb110b97f21077141af88f5d20b14b4d203faa0a2825730da55a50371802a85bc59d4092c9021ba78ebb2ea33118d989f2037b72d8a6a57fe2e887b4d8bad2352d3fd883318800077770884b4d8357b8d010c6466d6759fa6d26f7daf99a21e31caa28ad9bd37d9724b29534a295c080108e407fc7df3bcf233c46bef6868746cd16407b3bd33fdd052ea1498b35c666666668ef07164c88f999955f5e9eeea32abaa46156bca7bafdfee5e2b331ebfd7efbd27e6faeae0a97a4634b7bbc16630d7fbbdd7f10544fcc418e3f67003df8f889f273c26f66552f5da5d7531a9cd0ef9d06b16dfb93caf1393131daa62aeab0d5d36f29448a930f0a164de101f14c4584dd3ab900ab67034579e903826306047fd740c25e68c5721151c2109a9c087c80ec98111a754c023a515a1c31841e43ba78956c839a7343d93c969f6e62d8ae6043124339bc030d253c4b7f740e1db4bed6eca98999999a1a80d22b4f8f637839c71f4840c561021c4b70340cbd1f3a2e185166aa3c64261882c0ef02d34c4ceb79fb41c3d37318041e30a21866ef070f1ed33a6cb175ff0d0e07b8a6f3769395a3207b10a333333b33432fd91015eb41c0d8b480113629e9999b52fb4704109a5a0896f2f6939da34062f5a12f0ed328a0d424108e1db59b060820f941f9464e189b9535650848915a9c1368a82a226e46068c48e101ab023862b50b0323dfbf46b074510031249604216945084153054100309cf2eb35932d57018948cd1b3abe000c4041a2c61892835088308329aa8768514589e7d964c359c6000212050f0ec19e4999d040301423f20c922054e38011606c8c134550051810b21a2c085177889c948a552a5a0bbbbbb9b3486203c3b9972c4690a92273c5908a9aa600ae3c9b35f373f27cf602f8458a95284d1826fb74a586cbc9112856f8752a3ec2411a2081e700174847908cccc7e830373c6dc7c357c4b41c17712bea5acbedb958a6fd34d1834b8aae0c51114be60820ccf3ebfd8c2156044c1d3649adf3588e10a9d09c754230c613c6b218627cf46cf47533c6be1d91dc60b203c4fb9324b4c29496030b29c38bac020e58878832259440fbc0190858412af64c24150fc4112adf2c924ab865a8eab6da65c05f1de2b99901ce118fd0b899c61b062c81027823a466518b2c0821c49546c4a19e1d331fa52964c35848a504265884b2ac182e80a581c39918510318ac84206047865220529d6ebf72ccd62885111265f283283d512d8df5daee961b95acc5002ddfab8f1db62ed968e9df217a763fdf271e31fd7b507599d6fcb72981991d02178c50f8a5eaf780dfa41cf6f3628810741d3df94a9380f90f2ea3cf4c036a45504ba78d521250af24210df6991176ef09d76d13b3b268cd92dcfab922974e394d9c9f5f102a6705f14a82de43e800b4890cc052e9e9f3c73918467293c1b3971e351df3ecdaa11be6e6d1f60bbbbeb25f87d114228354657a818b436e8dd95bd17c498b25595673f86cb5f681940dd18dedd5dbf4e203161aa771b8334c5aaaa8d7e3d98e327028b71717466a3de88dc7bdd9d76747e279997ff9ce65131c60813638cb147cb78c24495e68baeaffb4fc613261f613e659bb588789b83f64ce1831807047ece632ab01e7cb041093c08721a869134dd5c37b5d0052c54a9dd5f92041155aaa5a9c97b2fc97c6f7fb8a0e495091790acea013b40e88d5412d621e57b9b3e7e5cc703bcb298909b99bbbbbd2de7e139e2b4dc7218c55ccefbc809e12108f3c483ddb153bf0d8cef2840de773442b2751a29a7c9e48e42d10841e227478d76873c0f8c9a8118903c63f1bcabec89b7c59c6d5a9a1d6c8b8a7e7efdcde5f9fb024c61bafe79b843c7ecc89e3984a8c8abc340d8ea2608d5af243449924868f3e9b25c2f16cc6e9f1461515e78e6f3c554ba385a0ce3ba6582f7915c6edacb10f371dd42d9274e968851d0a52062f51dab14440c3d747e02a33c6c6ea12c3767a329c8501efa0ed0574574031dfacd0d0d0d0ae5aaf681ce5eacbab7f0e46d786512e5c8330fa32054e72293f7a8bbbbbb1be58477d09976e2c4c96a353424bdbbe968a477286d968e4979e83bc0708dbe44498cebbc9d74cc1424164cdf4f8aa4c393942513dcf1e19a181d9ea2eb5f513564f54fcb1698c274eca38a85b883dad8f6cb7de0f00f9392eb7cd05898dc4d72da5eae5d8eb9073a064224e6f36e8b3a9999b8c672e8a8a28d723924b95c23d9fa85c3a8dd4bfa402e3cc625a421f14ef876898528df9e65018a6f2830d14212a83f80518a7d7967e53ce27a5ee8e0bdf77626cab3b3945d0ddd8d7f7ef3de7bef354cd1d06ce29232a802ca4731e284e8df2b132d30a1a578af08a699a543b2c5cc1677cb962dbbb9b7e8620b28dfd128932d9efc7a0d38db64a5808206aad4c362ae3fa279b96a17b8fc4561eeb876e6253965b9ae2639e9e397676be2390cc7acf360fa773463e0c1771a64edf48b52c034a5af0b25af4cbef0e4bbf98521fc12060313bf4045de66a7c062767cb44b8e8e34124655c1355db7cd8e65d445131ccfa91db9fa42141b961f266020621642139723619bcb492ebb9cbe366897b4f7a9bde3a3ee79e628ed63c4a8c9a8538cf19549174d707cb43a478c2ccb79fd50dc84469262943246cbba41c3584b290b6401b111d02ee11acb83760a2ee228dc85ce746176738316888f5ca17cb3145e62c60c5bcdd96aa9a5167783f1a47cf2bdee961810d09c966559b0832c6e6101a61a15a60a1d117404901653858e501d6952982ae4d3e4d9d7a1eb2bd7a8908f3294280ae549108c56e4ba52d474648c821e5d1995314a4732ea418f9de487f341d729cd3d29e587bb9d5f43ef079faa764fdb82a24bddefe7492b616ac73e6d846bbaee76f53d6cb3434376b57a2693fbaedeae9a449d5c13df8a31fa8b5376cb17dfcfd5232403265e85887084082e782857e8798ce21a28b5c728d819d7b0112638a669d46b88718aaea90be6bde527cb3545f2ce88e67d7a1cffa894ef4c6018c9560b92e582b7cdd217e60c2f62dae3c63f2c7811c9f4efd6481834f8c758ccd8f1bd27ad6d2a31336e669cf15b3e65ca9431fce39e949ff92a3464092b4479151ae2e44daf4243821ee3077b78eb49ae7b2f2597c332b306ee668b9a336dfbb38451fe1b047777b708aa709d9c1345c3401a04f617c9febcfc0a74d576bcecb5da2a7ba4a998cf8f702d85f9f22a44c4e7df8dafe1dfead538f21c9359035e17f03a233597d59e4a90c675374bb345288ceb9cc43557b482d969100422c0380f9052ea34e824c42cb1cde5d0a703a477325118057d8f483759a6c9151dd9224641ef2be6cfd3d93601b1104298492955f6aeb2d304610a037b3cae59f68fd1b26c009233045dcf5920eb277d41948f6e8af105aa8f0e4313bd06d7ac7c74264f18f231fac7ef4ad09f5f464c7d92478f9991f9e843f4eee6c7be8a8a0ab632b80c323c3b8ec7b8c3f913c9f57b5c2e1ddb4e248e87035e3a101dd55fdc6987035e3ae0d9759e784c4f278bd3f7fb415c908a50d70855b53b7a3de6f19d55f9e8af6a876b34ef1da2aa4a34798751568cbbd7e5962f84ccccccccd7bb300c7bd8c31ef61e3fbf72b08a519d4eb4c264225cd3a911fb9084784e0ea93b35daedf93652231e7284b091ab35242ee65555558ee4bce5c8970fc3e232f3427f6fdfbe7dcf79499b59da23754ad429d1723b6c1e605477e05232bb2552378a2e6841176f8057a11644e12f873fc7b91edce320a739fbecd0df426ecbf4f9cc938eeec7e5e860ffd1bccede5cc7cedcb217cb01cc065aae7dc3e31ecebc4a50d88ed370d3f71e5618d7d05ccfba914042010988eedddedddddddddddedeeeeeeeeed7fe36aeb60a84b0a89b084208638c511e7dfbb432cbaae16eda5d2965f654302894d7f01a30a6abd7b4d9ae02313bf9d96e9380e8deeddddddddddded6d1210ddbbbdbbbbbbbbbbbdddbbbdbbbbbbbbbbbdddbddbbbbbbbbbbbdbdbbddbbbbbbbbbbbdbdbbddbbbbbbbbbbbdbdb1ba7eec35ab10074700d314ad9df06c4f3a8ffde7bbe1b7ceff9b212a229a4206af22a059112a223af52101179f82a05d111afbd4a41b4f38f1bb2c339a14379c6ae92f6d95d6ec246b415d7d0ca0e102b28d3722c40d80c1d534b26211730f15d36c3c42887e918a0fae898aa1241f9f88451ab2a568c8a969326aa43de648a8f6e85154b943cd0c76d08a3e2e633e313658ca2b3cb184599a38f333b333c7e8451d191b4acda32808a0a3c6b6d3eab28340a2dab6eee86886b66886df36638fa1c96636ff3020d9e97319292a58c943232333348d826c647464a37d32373343333c3c4ccf646846b36235cd3d1e0d986700d1be16393efb6221b137ee4812cf9e83f1fdd022413388f6b0a9339923102637632324632473bd09f8c91cc51db40eee8bb16a3875d41b36b319231d28c7c177f70d8c5410746c500bcfa8c9f2d8c742d462d47af2ba96e8e8286188a8f5b88127f7865622c436d73ca01f6009feba4dec6e2a4635a76b485a765876be959cb598856b6169f96218c8ade42846bb48508a3e266f9cad62d3d5ca32d3b2d3c8c8a3d3ff3b316a38e89deb2e2ada568a885a859866e28f5441aa72b1475cb8a853eaeb8eaf5c5646b3122aca5a865d53637442d2b9652cb0d4ba9c432b4033012546a6131e29aee86e8a60baed1287cfc6276322d44d01fd742d431519339ea98a97552957745a523c1b718718dcec71ba2b6b959754cf49b159100fe6625345732462d2be93246ebc307786bc59b7dc55b565c2300b8626da9a70a473e7acb11d7643e25224cc0acaac0f3d14baab679a9e8dbd6e6139f997c749923ae89e1d165a4a8b806db99e1e11a158f3ed3137b656e39007931b68e65e8a3ab6c1d8b1320fb5a8b118b928f2c442d48d606276da33eddb2c33632159d652833c242a46d34d542e4634b0fc6752cab8f2c404746dfb518fd7c44b5a8b84683e4d0ea9db044f9c872a44443961666a743ec12071dba9b0991d10b11932b0cd1e8b6f887086815a424c6251fe5bbd8e4e346c72107aeb93cba0e0e81b6811e1dd5dada71d761de1695ec789625ddda7c6044244e87e724ce3bfe6c5109a3a2f316811815638b518b51748cb7cd8751d1658b51f4962ea2978c442f2189de2225ca947a3eb6187d2cf97c2c11f95862e26349f5d125cb18714db7f9c87411adcd876bbaf8636d3ff187013249cc7085c168b86a3e946432bf9cb7abc6c59d5d8e61986317e6190da31e8a5137ff9c6b4ed3db65f82315616244a00ecfdf90d1c7bc0aada0c963264c48abbf4035e9d052ce795dfe7239bce40b94ea2e1ec345d98a31468d00a3decbd649e9b2dd306d9d8f1bbfae33b616c23c4946bdd931cf63b88ac720248d875cdc4c6e6d11d06e30790c5f39c2cc5c8894dd5c199a9251d1575cc6767a7119eee2733bbdf8f47dec7271d2d6e950b6bd24660dcfa516d7e6dd61d47321afd4c22282897a1ed021033b74806b48965f9b591649734ce34e3b34c75c87c661aef3bc759b6d1a89b4256069785de92d08985e362039a6f390e12e2a3e37fdd2d6b2b16c56917f2ef563b0ab780432c031cfdf0dd674883998feaebec2ec7688468543ad60767a5464ade7be0225af55bc0a1d41f4d7268451efb15f9ab62373cc75648e695cc7982bb6659b32cab18bd9afebba9cb9d3e981514f95630493e442da66536f07e30ddb1688513a7f394effb0acb844b41c0e04ba90e5e19a8bcbd1c37b2b179601030cbad5ddddddfae4d4ed87aef4a72b04fa6d46014bc6f7dcff71caa9a5b5609ea437155534a1892a05dff572de33b422d78e6a9b1d1ac3ce196a8651432a4cfef54ab79c82294cf714d53148d446cca5360540996c7c7b02be3d07aa574650607e92ea59e68ab9a2b8e6045dffb9b28c4585d9b9b7cda65ecb9028697b33bcb8240ba60ba63f77c675341eb40f0c3fcf37980f3ec6196a3b9a9fb939dc204c6728b3c74366eb572d8744c9fd1883e9015912dd16e3563e30e125026cd394b5e5f02e6e7501d0da4e14c81c731d32ce75b8fc7482f9438fa46bd1eba47452ec18d7edcee573dbc15c3b867442c1fca1479fd31f5d53aa08b841e75b53d7a629d2f6ecc3b7f4a8f150c0152184edfd3183da73ebda9204e1d8b0bb9fa622b920dc6d3d68044c880991cfe99fa108a66d9324fa7e0e6f0171cb615529c9adc65c2290724bf2e07b49a006df7b71d38127c98f9bef91d36f39ea7552d231d5765cdeed8ef42092fcb8f99c4640eb939ba660c73e3cf36879e89895ad3681fe6f8f724b1284adee9650c5e282f0c318b9d84f064db0dd0821fb5adc2a0a42b65ef4a6817d596b7fd90bc2d1f2b8fc8464d599206ced782a954a85eab409f4f0a00960124bbe5de75bd3a1f3cfca3888cc5adbf19ab755a4b726011d6b9b88289214798d02c9cfbf468164898fe10ae38351ac6a5432f97b72fad02e94b7d71b76ef90430456b52d0f2f809de5c9ec7689942a52b5d2a452c91b4e8956a51a5b471843d779a24fed1dce43d779d8dd32b80c32e0782a2a2a333c862984b0b79df2cc2aa8e5606d9507b7fd1e7a6a8e8703989bf2d8cbe41518b4e445ca482c98eb9677127b2a4f6b2566788bfb3103b6da0dd3fab1446f2d7c980dfc831533cb728cbfc7f0cf5972cd0dfd5dfbe597c6037aab26010be3ba76ac7940b7709a084fda7a48d7bfb61e923bf1780ebfb7ae71bcc5fdc0f196f73eeea70202fe20f1ad3801e8cd03728fbbb87859980d501b94de30406b200d0f1d530368a780873b3c8e9b26d7addf6c38eb377072200e0e0ea3a06f356eda665345d3b9e6b4231661c4847577c628e8d625373da9dedc18d21a2a264f801ec29f274f20195551c4771ae5e1d6c0228c3c74996e4d02b108231320391675eba263be4e729c3e2d74795aff99fedd0dbcf9ab0553859078f21187e3b347958cf0adebc63ac3abf2087b857c559b7d8229d7e82fefeec218ccf5ddee6ee70a5ceb8487edaeee2ef7bb0be51630551bdcac92555a0efd79011e6e16f866d2c720cc3ddc34f0d0559bc0f59d0f0f39fea89e3d6f3d687a891897d81526dfdb66bce95f54804819eff2728b1a03551e02f9b6b652cb6356f63696c764f6b695c7aeec6df3311296a93c96656fd31ed3b2b7658fa9646fc31e8b81ad6472d3d4dbaec756b2b7c9c758b2b759f1b152e682c9c05e3253f64c2f3268d050813a039d04dfab0b804f9ede877def0dd154bcaefe3ae27d9e7d235c311c4a62aece44efce043072a7fa080cc8f31c0924473eeb4cfbebefd6da55bd99532398c27470a536a45462f74ac50839526a8bf01e14f0716f6580c912c7f072531a41003f805718de223e60f0bdfd5d2030551bfc3a70f7067031eee9adf990a592ac384337d5704d86e6b506168c05c3b07ec9bd8c073fe6986a15d081c1cf0f5f704114499ea0450adb5a38d2d66d5dce4b89d32b5b13d3e5e623fb26783c35b912c7f4f031627093513c845414e68a7539daf65848b6a9a230922b76bd692262aa74e401b6fe38c4189555e3964939e7fac88ecce883e65fc78edcf11fa31ea3a0e6d0995678046387afdacc6df9e39a2bc394afed726b93cefcb4c3e1f76d73cbddde8340879dd9dfabc63acd598be5ecf7e8211da7ffad240d99ecfda4a5fae7180d0899889cd35720176311a8695ed2bcb4e4db5d1c73d976d80e21740dbae6180032172fb9b8b80c297d9198e2958a5721224e1e1b806b37649e43804c5130dcb6b5b8a954728d089b41e3b19a2683b6a3e41a94f90264ab3622efcb8c9719dbfe0c07c894638c9bbb0c0000000d17ef605cfc72717171717617937643e6311c2b40c644dac6649a536374d2f64cda8b464441ddfbf102fa91c92876c135472f1a8f76d81264bf85ebf63b93b768b1886b7a6891886b5c341e7b03e8ab49c072e8a54dbfa4edd04a0e5d82da668b3a2967742dfa8a76037fe9e5c565f80c8f32dc456dd0f0e86f076aa3e4d1b152c967b88c8dc6b6bfa249a0e42e2e5e728c1587696cfaca9d7eec671ee4fd8bff88f12f5efa92bfbc383b76008c866c5fb43063fcf57e5eac30bb67e44b8ed1bc4031820412d676b4bf7041f686193154b41d325c4a25676d47c95d7cc6a6dfda8f7d195eda82eccbc8b8cee499ef971c6a3a7ac8f09c7d1737712f50a2bf235c238333c2352f5c278309ae71e17ac870178faee2e29ab643860b02b2551b37f4c83c67f505ca4b1666c73c0f886bde0fd7447f4ab826baa6f1b05c7aa6ed901cf72a06559451b2c49d54db01dd722b13c043003c74d5cee5a1f6307b082184d055482d9b7ee62bdbdc72b68b4a7c74150b8771383afb700d0ff7c420cb61e47889b615f9e731a86da2121af3dc07a624ff5c72ddacc1bf87719dfc8e973c25dafb61d47b3fff9c89bce71b856ba4ef839cec1b357ee7c56138d03c8c328351d752ede68524a6f8e8564d7f8739740c6e9d0f3ff4181e3a4baed178441e3d06d5773efcf070ca639cea4ce7c3675cb74ec29cbbf5cb5be3a13365cae33417f1a44de5d6c3e24e3c22f79a63e8eb909655a0da588976d9ab110460f289e218f68602734019b6c91c436913ee618ce3226a7148a07c87e2b70a7a2b0e6af2829a25b7ea1f91c590094c61300871d4f02e5dd1e9d87bfe84306a075a1098ac13aabac393fcb4d2ec717ac411b51cd39dd09a7668a3e2aa0e90bd6d141616967ec2ec1e62b8663a3aa707e83cfd30ae33b5b06c4056650302bfdb234076cb81dfb2d1d4c028292402fa19a3e00fb3db22295909f339f6322e1e16e96a613a8cf162c89fdeed171eae638ec3375c034d2a2c24aecb81374c04104218218410c618a3090296b5aab13bde1dad68452b46668859379665dd80ccefd15852aa919496bf075f37777777938630b19e82bbfbc9a92e97b31aae32cbb22ccb18c60caaa65d2998cc75998a4a8c1899a692614f3c4163730424b189d099e75db7104ac3ebc284be4898ed108c09b94e897cfecde6d199e7cf572d987c5d6f0a463dcbb2a2252d6949cb82dc1b9ac1ecde50f3fc7ba9e76f75d27fdd73f2ef0dfd7b71f5c5aaaaaaaa323364c890599939d5dda937736f2c2a4cbfa199c194614cb804ab58a5367e0912f5eb8ac9ccbc8e13338f996b3a44ee042377ca41f620b913e470907e651c8eceacab8280fa0811938f8ac74775e4276868a52a622315cfb3ca4d268740c7accbcdda3488e1db32b9cc7169063d1df8a21648748610118078604ea0a854480a24cf39a6679d5d20966481ce701fa134b063fcb4d0deaa63d852593f583e4f0b4b88e744c32112750c933e6b62766f4514571d1357912876a8ef2291bba97d22d133c7b54feca1f6199aed5d5c75a607637215fb339f93f7c33ccd57212770e1d997c6290573817c74865d303b8bc88adfaa05d3c8e358034c6b669428ecc33e6a83023ab3fe24c6760d466d6cee6eee488aa4488ab1e31639534683e9dff354538b71a71cb0cc31d74969ae93621d341cba8facb30f1361230cc4413cc42b76c244ecc347adfa751f22ec73719d0e459aec8aa933989de977c3a2cc0c302a07263e99dc999bead4c9af3ef9c5a2b30efd931073e52f57264d3c79d574acfca99d7d6918b5bc43a67f879222e5b31a4cccc7b37790f9d086e819d75c9747ceb4c97862d6e01c8824f98b0e372039310234740bbb4bdf2b126b52600a632ad5d829316466c8dc2d5bb6ece6e676e7476fcf11234d4069e209067b746c31240a273c79a59945861c597db7aa2343ba85196502c999df3dd099f5de81d2b04b56b52ab5d13bd0a670645dbf064db683302bb341d10bd980e8d75b4907751355defde773eae89310f33d1d3afadfd3a132b2309b831c4ef3ee70f36492cb6e30e58a8ea2eb8af95cd3c17cfebe989793f9fcc2623ec75e12a62a73324f16d04c70b9e61777cae1729293b81e4e7c71a71cb01e4e8c7127e670c03c734de9a474521aa71dc340cbcbe373e407286868b5aa22a3239e756fae3371b63550c7ac635bff6cfdd3315107d39f5bcf9c98f21a8230477e10d903ba6579ff6861aecfe59dba3857a397feb6d95c8e101e740a46a91345fa677d94a89013437e7787d44495ead92ddfb6070fe4609d8739f235c7836ee9a76df6078d5917e291f8691b4d0105ad090cb3d359cdc2547f5047f28c30acff76964355da50304ff6b741a8810a0a234a64a8100dba787cad62fce1a334c442d962763a14831515183fe6c7feb8f7e4e97b33c014667b0ec0a00f264c610af35cdf737dcf155e61ce47b3c214c6f24c531b15d0d48324b5219f442291bc06464112d7eddf74373434fbd92c3509424822912034a1b08c44c27cd6c048369a5a276598c786b213f2a49310135e4dbaa273749c9652a3a633104a418326424650f0e80c9cec4eb561628216df3d28b13bfad635d731d783e3ebeec1dedfa37f7737086b9592404c99201c44a32e78488d9ed24e16a5c86d9f7e2a00da2125482c2674f5a0e5c5ec9667884504c687e779931cc33678492bc24b5a11c60823246276cbd359416f148f540dd3569035f4e20a968cd07c3423b41ecd08cde7b520cd47f301025aa0df2220219e54109cda49a9524108f1bc0a01a55497155715979327f134c986c87a3e5b92f50cc1b3432c94e7210b65a1e01843e39082e3e835e807c7118e2a09ecc346708fb211ecf31cf43e4db04cb9fb6aeb6ac932ca8efdc1d2c00d9fc7fd618cb3f89e43c8fcfac1a7edbcbd47e4dfd0fe08213201eba10e1146bd7e726314cccb9df1f5d8cb5aea8cf6c3f8989545f898ccacf7d895495276611929cb3012a6d2faac40e98cda405883519a4197d0650ff97c6453da48ab6378c0e4bba74cfe31f9e7dbe33dde0dfa181402681870d303068129d7e8d0bf5632f37b167347d507e16b4d51caedcacc966c65d54b5a96163e108275730a8ddf70981ad4e2c2e242c71c0c06827c9024e149164d9c90e226b0bb5c36324485912394e684e167361224630e848cfcbc0ac5a0c927e155680931fc8ada1044feb311c4f4ef397b89c9ea9f90909110983ec7f43bc37799f6de5bce5cd7344e41cce67abc85bdcd1a02733ddafb7b70d7479e4c21b91fec6154c3c7dd566487de1b7bc3f66d1dfa4f877e734f9be81c30f45dcedb3a1fd0890a87141c4738c680a3c25101e172725571597139b99c00653d194f3644d693f500e1885f3bec7c642f7bef590cf45d0ec3854a9686d79bbe84f039f6c37a0821cbccb986f3f7836ede6666e6e614a80ee60d65323bd3eb493b1afec1b05556a883b1d69c433d468bb7684702960675c85b3cb1f790ef264fb334acc7ad5b5576dd12b0346cd4e9218cfa613d3fb6160ee92144d13502f101c309e82155989d3a81c1ea053540a1867fcc0d27f7bb5128a86a8d2704a4babb351c04132553da1fac0dfab627979854d01cd540c94461aad01244be332dc1f39d34d540c99c5895ba7cb60d747689c51dd11bca6d8747a6ae8b93fe36b8f1f0d1b2a202b678e59a3845ef41835223783e02f86a5eb3478f7e520e07e891ab80a6184ea1c4ce179162aa41345548892221258808294104039eef4c39b02a05b7f6b8ed80dedc536fc2e29e760c8c3d82f4abb7bc6a3c08f0ca35318257f39a7bca141ba0ab4e99927a396c011ca9c769c7b47b81d9bbee6655556606c29fc3af363cef9a79e89429fb39fc8ae3b587742301539824965f96a5298c03a3dda8340e56553d0a118d00000000a314000020100a0785428140281ad676457e14800c828c447c58198ad324c8711c858c41841042082106810198119281260062f724b45d1ebdf7eba0749be43c98665c9a8cd908f36729ad3985913d2d0f9147db4ddf44cbc8b28923a15b44d9c8d3bf4f561784c8f68489b4b38a4f87cf06dde3535f94f98ed5d20b31d5fc8dfcdab89a928fbcc8b684379e1d251110eb832a4f72cb14b07f9cfc3c1fd40f2a123d03f534d0b0b59427e98d611aefabd5e59397b84981859f5942ae3e2ce9ac160df315e1dc606820b93c7e98dc8931bf82e06deae20942b9b40f147186568950146a8f7e5021914433756219d67d022935dc534f05372a2ce5b054d035bb1a7a4d8ef575f5f5284ff257c5899eda7741d0e994a73df9c9a0dd2879e22606245f0b7fedeeb029fe0d807426bf36c49f0cb35cfafc9cdad1fdfd66ea3083ccd2f3a312eececa925f5e1ea97ea4fb3e9e230a6d9d1f9762ba0706eb4b76353297dafde4b389624406e3703e4f80204c71db0b27004db232a253b92a2cf83841c103ecea249d8b94d38eb53a89de0d93bdac4eb292341e10c085c2cb7fe076b1854ec6b180e000f2d579879e890cdf50d69b30722b5c1b42a30a1a0b590994a64707a0a00faa1b90453edef5f515bc18005230f88b32f502e420b795165427e9ce803d7abea74ea2f98d09fd8a8b53b6b638d07813b79f805471636b6ed1319498a9a0a7ef70f631cb463a6f4aa80cf15bcc20ebf62854e8544529c5dcd16e5e2c537f7eaa7e40a1db72299ee0062a81bc75aee38d408de6ae13e8ea408fab30d746e58bc1805c419acffd7a3527a05c0b79ab82e303b5e252db8cd2abd9f554749a028f88b0af42a87327bc193bde081b9b3e71a76be1dc314bb4eedc719b9c7166df759704144a05b3d6fbb407e4a5f136f641f1a95269db58923ea5326985a8a16b36094563d78f27ba30674ab2510f83e03864b38fa9a81fe1dd98c316c15001040c0e650a54a60d3922a1652dc6ee9dc44f5e7453d229e905f17f86089b599834a131914ec4b45588c4bf64448f548bfdd912016ce3bbd55f4b23a93531725bca8df9628937b1df2693a5b41c5bbb89c820ab1884ebbc55ce029037afd4f03098e793dc88a1abf8da9aae7ad702c1e5f302df96a29ddab521d15b729d77dd2ebcd48c12106c70e46e5cd110ae9454ba637d5bba249b3ba82c8f0b7286577d4b6d7104eddf647730f47cbb4e886e4796446dc091b383ee7e4bf156db768808dc94f854df72c9f4207b4314d7407a4306236792c00669d7c2a1518de007f7fa2e8e196f305cb55b09bc788cd77018608c616f70acb094e10488459a849eac699db66f49c71210c952e74f5bd62a13ba6e0eb4434a3b621bde1240db2eb39767774bc3e94bd17d42984ea1fbd2d80153e4e575c8267bfe46110c3d435b4b50db8b0635a7c5c5bc84274cc12b1ac756b8cd1fc50d7c0bc07f7596afff9e8d1a2bc664b5064c5943624240113c52a6b19a273b918ec2833fb045e58933d6b37b14df11f2e046043fa716f3b498f8406339d8b764ae1c54f3e01f528a53548a5679d927f365e69606253d91dbc271bc5544754ab0d6d85b2ea571fe40f30656f9047f74e596dcb8950a9b7134669f7699811213c2580c3ef21fd1b837ded817e38bfaed8fc8fb0b6cdc23fe18153c72ed0b18242ffba5b72ea9776ded687b50dbdeba6388f7ac1e27e0a3671872ed31f7297133a8631c1ed30962b0427171033019ebd19bc5487964d9975a9c559bf166be62ebdd3c80f8ac340f03b334e3513d2927dd58ee9563462041c564b80ca41e347f6d4582d9c46a045731a665a63457219e1b3f71a3ed9efb896e2c062cb5ec65a21446ba3d914311a3d1db3afdd26322b67066947fad2837b3c16d22143fdf4cdc2268d42f8338d15e28dcf35c722a42504528e272e42b84b830b165930a9b50d6a14e5be10eb313327fe87f189fcc0028dc47fb76f6f090b43e7d2c4701dab45fceacb582ed3b947c704745160113ec219fa24ebad16a51f351bf4884f4a0a3f87449f59a60bb938f6de675e8a41da58f50a2e07b3cb6ca8e88dd9bb0f490f77a2f7e245ae97d556ba4e0017ce14627107c45e7b6dbc17b30a3617d45571978b5bd6b62cd919d68d7e65918d0a627b0531b76fa7ff592a4b7a5d46307a48fb417f5c494d1782cc6a26fdd24b27dee3e8ef67c932fe570aa1350d9d60517334d817f7a9f613570a1528c7b392a56acbfe5bbddfdfc44fdd58696e9c19e67b4edd8322e3c7f1042bf4c16e3265696b30fa5d67bbc56106fad20dacbdc2782962c3d3f7a12914f3ba93fdcc333afdac4e098fc4f6bc63a7be2b2d61741e6acf2a56e7ac66c09a96934ee52502840eab814f1ceb485150699c463bd300cc34ae1d811d09f2ffdbd62ef394ff2ab97ac564f3774796554adbac7a5db3c3ecd20477e30db7a0652176ec0e697741931e5359368be9ca4e0a1b6cd0cdb8a2d1923f5c6cabd9ce48691dad91a6a0bc4629cd3edf7de6dfc20897c6806824254ec94d97eba0cec95d6f975438819e528820fcafc306e878d3ed7cbef823f185246fcef5ff49e85e343654c425a1f87bd2baaaab008641dee552b7138b416683d685b5bd26a037cd9c7deddd54bf8cd47354a3e23ed96b4627c1dd5297cb7d0b5bfe1d9c99999b686e371c3e05f577f0fedd780f5dd165aa9af28d64e03baf508d7f389b088b993fa2bdbeb78eaa7d44b9497dcfa270a61ae7f4ffe81117a5ff8c515f047d6c370ceb112804e04b7abf163aab108e05f6a139d26ba0ee7063ceedca10d465b8e04da32bdacc3390ec048112fbe06a6f4eef338832443a3fbfc1606a25f6f3cfecb2f197a310606ec455c8014f077cd946420de0d2d9409c421f53786bbc56553a95c221f884927038de676fe3e990519e82a71f633b7532b05f91e10b115364223e79cde9b638efbc1daf2bd5019621392fef0b6e8c2829c0540438bc1a0560b88cd0d3916dd0d78464338c64fa1f1b4517e645cd092255f4778bc3b4b14f36981368bcac2b33cd18e00fb7157bad91983d95d7fb06f352e333581d6726b110e3422113dd84b545bdcfc4d6baa2c6a6849532bb6ef0b64b19217106f36cc84d8bcc669dc5cc27155022c19dd5950ce6db15aff1b9f2fea116328c78bc709c767dc02cd30a4bdad7f5780c6309421e7260fcdc152c689b1cc1c108ee9ef4b2b2227f003652ca013f5978cfae64a0e9cc466e01caae8668ad42a2a9b63455ba7b8308c2f53df10b01ad2bea1bc3328caa584e2fa756cebb6e692ced329cf2f460907a69c26399db6af1727be856dd61df8c2ed83307a53135a222c540aa57a675834e6c669da502ceafe0bdf7a50ba1505ce92ad070aea699210ce0a298de7cf22ca1b48dc574361c8c5740ece12e8606f2d186fff5607cfb3f017eaf54a67e24c67699b0dcdf8d16400f8411c8a50b4e883c27f2a6ded361e0a8a537e1f04e46fa1551632dca855c26c34872dc52aa7d2d1af35c96c16739c93091204dce68cd7e7fadabd9bffcd49051d69880689599593f02d4ac67b128cbcf3b4102e2c00a5bd5db060cefe41d148f22844dfd078389b917d6eb636cf213077ef63e78d46cf6db03bbd8a5cb60cb67869a342e1788a4441287a2f70ed3bfe38b84886a5cc1ea877986ee749533e3c17f24cc4dca004d0088a78526b333dbba81b596fa69b941ecb63299092a7ab4da1e4433b298789550a811fc348003cc75f684126e26bb72b0c1acfd38cb3e506fe7a138e7befd0490123af28df5163b1e7f3056a9e295abc13a503c8eb0ebd50dae9659123747733ceb01be44c2e172e53c1173111526091d3186b8cb33c327944e3ef5fc9b542a3cba53c8b894b575cfc9ba77210b3c464bd0f6b4949ad1ab3fae6ba9190802baf8b61bc2cfd1b4b26d34ecbfa1f1ecd140727c5d88148aa78c8343347ed03d43e4d662152f2839da17036727e662611dd7379f6bdc474461801d938f8e719c835b5579c4ea48e08b0d3ea9f17eab38bd0ff8139bcb3820544a38160235521b9771272f84b1f716d40d68f377d63f81e49240320c64e664849948910164b672487376d6ac0cda70c4c01b1a67a5276b77403209296d4815a1b721b2d042af07b63166d789dfbd5090eefdd271eb809afbf3e124de0839783c898868e65f297a5eeec5b0e0720aa1337171f8467ee2fdbc20c8b8b9c7c84d794523fe55551b742f3637f01b65cf0601cf0c6b2ce9a2015497bb9dedcdd3e819a38a2e9e414496442ecc72d7a5f7d56ae5fe9fb848d39dd9d55db461d905b7f5fc02ebc9f5b71d2ecc6d556cde56e6f736d3e3b14878a773f96394f0c20602650f5c7a427f9d0d7d62c0e2eb08f2bf389a3e519a59795b584e06efd0427297d4922bb579eafa29e9a439e3763089d302d7071bc760e3894877754b5c1090f8f0c6d5c6392613ce006b525a9e064a0d4848769a82d1bca1afdc3d0df20e814166af321f38bc346a2807d26cb59d9b3f23460a8b9e9e57203e43a7d72310078b015a043db5d8fb592998794b6554cb089698ba3a89881cfe7d1383a8558b858491d8f8ab9d4e3ca7a155d9768e2dc8e68cd658d4ccdc798517b970b21819e91ba6e8a9f2d4435c5ff3f5e1e631a9f41447ea4eeb196fb2d0d79d3fe8e44f4f6d533de9958bf9c5da71e1fed44184a350157d68efbcb06ff343095714a79b7400321a3c870ff04241b7ab0b202a6c0fe92ad0441f30346470ecb996704b496dfa1ff78f21a64a07c8aafee21c0687d28a4e48722dde3bdbe10920c1c3b0aa4a1ff7bf2c590c734c786b0d9011fcc7ffd9a1c480d64012914f7e1f2b337f00bc9531fa8b28e5ff3d2ae38f1ec04a46020c3d6ea25777a8cffbe9aaaf646599ace16f23f445d376cf407d01291b7602d1fb79c45c90bc96a021856e2f34e941d97422017e638bb995724b563b7c45335bd81e002121437a30b9f2d3f470aa0abbb068cdce3403c4daafa0f9e9170e9d5740daf1f048ed3f759ad330d273bb48cf0d4ed4f6f78ca4d37967ad2376ddbe9e663702f04fba0eece665ef056ebb3d908d64d73808fe5d8d646e17b648313a31a10635e47f64063883a9d9b69b91546a6699c3a5cb52e0b969d119bc841ab17ee2e5542e94ad6d90d6252c466abaab40092ca7dd267b170133ac0383fc5ae7308bd31f99f9721a07db4de591eb8df954895e21e570df5be757ac9c06f657c132e3c65448a068710bad1d24406c589da9fc87a1b7fb84974a1eb4a8a522f6c79dee71004c672ea703339591d563b682dee4540a908dd3b0bf8538209557f12e5fdd0eb21f32cb6e17f7ef4c45737fdae22fd80869543dca7132f3c5509140e62a1fd2295c0c3cdd3b8af65b1531da6740a9d8767b3de16fabfc109acdd20aacabc220cf53d65d9c15d30f182932550341886b8b1361259dc6f1a7db89d686096955fc6bff9b8157bb1ab40ca1de4d1d2c8a8586fba2d1756c20730df47b1ab17a2b75ffff33204b48f7a7f98ef8adb62697b77a6062bb9ad742a79581cddd14d796413c667fd5be9b04db80b0df4c3b4a26eb59bfb22bd6c847ba2b2cf6a448a5ea27ba89ef57602ba16b1dbae2a090226f5f1225695626b0184db0a06bc5a1945d4bd434ad920c1675c5ab98fdffa75125945d7693a2e2dadece80ed8ed32468df2b3215acb45ab833953ebb6038f129a6d5b64ba45b606f470dad2d12f98345f628f70301e28c6f41d74135d43e32f61382cc557bed5a0390972db4c58981ed42e8b3cfc7af181ad541a16509ccb79fd2b7b47c6e39108bee818bf1bacaf2bd32754c1d484e591d0259aa1a74dce2d842bf554d85d7ef281a7c8a666cfa80fcb735aa8948a0320c9a7bb59791bbef42f244d28a6bacce07f9d30d107db97e61f06e8bb772aa17d4e87296763174f0aff06b2fc11049369bd8fe94f61ebafaaf6373c4dfff80de4a51691909f58465636d6c29d9a7d82df1e53bb9b0a4d2f2ed99b7a7cc00d026f17fc65b987fd1f172e99788d85cea1c09da1a43c3fb407ba7db1479c5e34adca952828bc13bfcca3502533d9bcbbc22352999f7097899f31a149e8f60e6c754426c8864e8524c79aa8916987eb816ace1c846d9495131abb1c3d33ec3442980d5e431b30e2a5438953415276bd65c03211f10acba1193440235a9b5c27f0cde0d35491ae8e1888d10991eaba49f9745cb9d93012070ad0bfd1c9751877d2daf5deb127b922667d80dcadd1b98093c2de3dab9dccd1de97fc9fd2f3a319af0cf07f2bc6b30f50967f2733c9ede7324364497560d579b22d449b60a2b288c78dc303aa3ac7189bfac920ff748a5d954fc9dee62e9b779521bb332ee88c52264fc7e950e9b927f72802770ffe2d45983784600581e4578d1bbc1a40af4b75db93394881b843c27ebe20ecd086467351cee321114598a7c47af292fc06c908658acb811b008bbd779939e718904f9c7953dee9be6352acfb8b8730968df314c5414c2b0bd1afa090c7136d7ec6e53c88ffc99230fa5935f967fe78b67ec40203ca117663879e0853fd56ef73b731fb03b55e1655f5211805a7a684f935ec0326fc04507e9e1114f6a003f629301b622016caaa33264891c062513937404510c0986ae38c8e8c67094220508224a17fedb0fd15de02a816ed199e00cadfa535290610fbc177f3e1d808e57ae7e39f265288fb0d69b7de91ded7dd267a1056c9ec5b343cd0cc0b3208848edf6b3e3b8ba5e870f10b6d95faa24107aadc01cf05fc7c030e8422ddb99e7c83acf4770a5370f28d567db1a456074ef124e2632da7fb625b67e6cf10bcd8825cbef5e78e67fb10b17198defba1084af939a2863a0e433af60ad63dedfeebde28b6e493b4d9731f951e63c9ba4d61f34f8f85741c02f6c67299a9c0a3a65103f79c6d0a153792992b37e657f74a6f59d6a8f46fe7e069db14854827cc50ef9b7878be7ec24af1b90c5dc0e15626b8db18cc4f0b41a58fca7b53efd440c73193ef3a65a170dd3dc1e1e89e80e6f9085960e0cdab30eaa7225f6f5648493bc93537c405fd2a73b948495bd20ee232c9d1da5e1e9074d630801c066c038b74d13a8890a72c69c09981d244050215f3c567fca6fa03b192a49d5689fe960e33393d4c5e953ad720e30fcbaf5faa502cacc578bcab654f53e023c040d90c98a06e11de440f979333a4f93a8d29d9a6d6c031339eb406078551a8c408cfc233af1927c83e4c0c8ba6add176fcf01c6773d1750546b9fc517f812aa8b9e854528ae6c818e539381ccf3a2a6d7aafb918118412ae7fa1c7e8adbda6dc0aeac528fda2801dc8bd1df2911a99e9be6a085194f70929eb2ab665ed3450f2838066195688e0f0c381f955876b74aff6884cfd15fb97e9b062e81bbcc727f24b4f06edc37802b68be84e4f0f10419496a914bdccc13a43df90c71393207a9b92b9d258898f0059cf62e151061959a5f52d152f13382bc60e0f222c3383b3830ea481bd83e7a862550b76cc5f40446a35c1a7d2dac9a63f01c8ef30de51f4d604b29722065326df2dff0d024271911c6ea09e4d024748ce4029b573aa5047c57aed5d18868e1d9ab1c2be643bb92f7a62a2a1a9e1e3e3b3a85a2e628e3abbb75a48f55cf52f188ca80c9881eaade02fcc184c986f1afb9acbf11a4c3e22a5fccfb5069d9e820d8155cd998554ecc5e1d0115de51cd4779eed882a9c9214cc0850957d24892aebebe4a7e8beabb1e0ae745cd0edeaaf5fd47ef34a0bdf1a90bf3611e533430b40e90b1ac7f37bea0c47bbd6bf89089dd557cfeea717d5506e8bce3047febe8f587af00fb8654d74ca8c1fb97cb492146771048d0e7e65ab28f05f444568485c6c4e75f29c70428d6b2310eb68400064453750ea2cfdc3bd2761a4eb434530900f220b00f30afafa8fa50ff1794584e5280cbc1111abbc9fd86e7b89e9b0395eb63710e33d09b288d633ba416bd4026a7384cc21ab61ac80daa7a3742f5db8ac356392d9fe4535eb78b9e46c309310e948319d290523ca256cedfac5e3dd6e8166c0382735c84d7c8be8b60bf156b1d52b4e6260a86e3a65bbf1d37b5d055ec2bb7b28642efc5f6fc1fd96d3458dc2c82180cdd18f2b10446fa97ce3cd75c5d3b26da1bdb5fc6caa3afa3f91946ff28c486a03d76c8415ee5b4e78e4e3ad874e44dacab0b72137360148d695a6ae6f44c1a942015820fc3770af341e03432801eb8d2a4f4fa7966b91f06d6f1ff4b22a60990f8625b704ed05a418e7767aa22dbd21622108f8dfe3b1d66d154a2b26137830788ce3b0c172ea0b4c25de369117cd86cf01cbf5469a45699e0f871c11cf4fe122613269e44f843ad3fcf17356016b71c939e9cb22be34777c636af47f20fd3a36ae887cf05ec8ae605ef8f936a28838c5bcc5541042990c2cecd2afbc158974b9cb6b05883e5064b13805f8c268b87aeb82c37438f713035c0c4a79cdbd6e1c242486b886c9ae77abd5120ac5cc4c4d20f43cb136e45d3e45130122f6625081214b8fc6049f39c79ce194f00e38058d979a6acabb4d958251dabaa401c979356041d2102db979a7e0d6a2a5a0fd80a56c20e59be081298c8b0251a7005506edc09f560c3201c225121eec2c9655cd1a1a242336b46949f8ebc309eea75e625115f3eed499ac9452c8213043c08288665ddd8ec4b21f3cf527b07a63a0d73cb8589448f4e3a3595c55db8a805c4a56d62009a0a41025534b4d1bb000dcae93f515e419f00cd20117282633624b8bf31bfa995ade42ed20ec1fb795e58af64f8615443b8b76e0d0d9fde6a2378ccdb3188d9041bdaf987dc212e9b65495004dab12b210190c90cb1192b8aeb95b560a2b857a902dc74f917798b2e3ae64ea40ccddbc628c0606b971918563d1ddc93a2b9d457ab6ca6c7e8f5a10653d83473d433e89087d5b2b9775064c91ea305411990f2fb3310b36c6481f7a9a8b9a56b5b2ae0ee24a229b468ec4ac058d1b608555c22344b8a667eeb41100219444caf20e666bfc4622e72feb1e22847cf72c2f3d17ac29162df687ff202993ae9e004fd37ea949b0c6f922f7ee9ff212e4c3581120d5499a6064c65cc41f679c86905f574cfde1bfaef4bf00362bba6906d09ba81460a7cb473c6485c32e0c46a1148950277d7b0a72528bdad2031e72c6a78263c90cba40b813b3808211004b7f7b71953236cbedfdc5fc0bb48efb0351e3bcd6fe6b1aed2d486fd97c4b02dc5afa3d7bfb0e706ec580d7d7fae210c811645c700b994ffc4c1d95f77c59646e501dc2aff7a70b0c6d1161c2da8499d957aa380327a68325b7aa0a20c2dc24c17897c0ced5a57192f472424a80b29e2ba4cf44c562184998f614a537c543a27edaf454aa7c8bd4e240172eaa8e29760960d7eaeb3f4f647e986f45fbfd5fff62444047b5879f71c926077187157679c5095686517c4a946c574a1d1860194de8003626e72eb5375c990deda09bbe188ffd5cc468cb6ed074b2d6d5fe4802bda2854125d2c19c478c9f718a80456db775b54a12c2af420cdbe88ff302f029b34943567d1c2efa73d4fc34d45c13cdea7ee354ea6a7bebc475365b0dd225716ca62f02f8804ee66845b5e58ae6898127405d18f4594eb5c2850bd33d7829c51b2a1762b07846422e174a8c00c5b2a2300949a9e662201af0cf2ce6b1a2fc4976788c9e445c19b39691aecf8d92a74cc2d5c4644f7f52f35a27fd9462c9c514543ca85b262a759434d742266647f1a153de1aad497da67c96a978dc735fbe7e64d2502a5b10d46733f96d469eee883ba3b2ba92a6db6d861c59cad59dd9212508006ea7073b596de714074b024d93d2193b968c8c7cb785f91c2eadb76f9759f91791998aac20131a96a9e0194d0bef4e68c87349cc5bd3e408e818d3d970acd97a9f88613ef6732b8d02ec78481874144b6a51ca327cfdf5dde48f83f5fcbfef2f05fe67cd9da0252e9bc1047acba68b6283184a78b38ebea8ba4ad741895a9545d1592a0e37225e5536f0e43a1c94799122a6757b31becdb58c3b2e5f01b109e7e68e9b3c7b190b694cfb96eb75c725a4049b302593fbb728d24748252e6baddcb3843defb844669dfd2de02d233828a302fd1db8e60c4a7a3ca2e11cff24cabdac00131e288252e66c0926fd466377017a9c8ad6dc8c6d36e837e440a27170b23feab03407163ecfd6203f34391d4d7772ef6bc99e425cd21b8486f0518f98a66b6bf265b5667627aa9a8eca781b16d6f7fa351075840ba41125dfe2c38acc082e46c1347e36fa4830fcdaa1e4277f3da12702fce972439cdbedfa728112c1112e85ed79af3209fc65450f47741a23cccbf666d2f8c1a9cd646177b073981a3e9dc2f2dd546e15cd70a3bb98bcfea65f08bd8d3d1b1c85092346da638382f66caa6a78bea6db3442d88d7cce2a9b0f8ef6bca689ee99c5acd5bc70c16bbcf5a3b38cc63a63bbbf3402ec3c3d2451ecf1637f34ae3c66828d515e1f77bc8b7f456bd34db5b57ca124cc395234d8402a465b7c1a97e0aacd08c0b2fe79d242036cfa973b59826e0d41042d614766ef672cd209e70c09934db9bcf2523aa5596b427ee733f935bb26dfe59054904e7569e60e2a3142bbac9a0375b3a0f40a6632a93ec320a97cb314631fec14709e9aeba359815e6a7b3f82c6b673988d50b7164eb1580385fb084c0516849ced59900e57f873ef03d32a5f6c217b6300bc228e73ff0db56bdb8649240bb076efae226720b28800d123cb6fe20e8a4022f7d38b70dcabe041bbab1490497de8e7b57d823b273516ac1eea64e8dd3d82e7b783f8012409d4dc93cb5bc6f54dc5ff743ab9206e2bbbc433bb3f4068adae8540550f4ad9f6bd3250c568741f30260b3e18e1db76aa9a3afb26c2402979cd2257e0bcec322d2c2f6170a0d2f67efe2dd42f58b7e41a4efa158788bf0b99c4838515edea020ce9b439a0899bc7aa06c3154a3b50a43068880a9ecc5080ed3417c89ceed4e8cbeb1cc77c263d0edc2c0893508e7319d72bebdd37d11f6f7117d17147c78d24185f5da02c29265ca1d2d1b6f9be275186257a6f5b4a2b22c91284238825cb661468c1ea8647266e7723f84c9fd537df45188ac8b28bd62d04ef94a554e64160e144a48f7151858bffdc96143752fd5b36b49b1a4dd4bf4f8240a3ce91f17811b5f05dcb79205bd53fa9b78de382de411785bb9599473dbdd60a891f3b1946c2fa420bfa7343f87ef5bebd8a7c85a0cd48e76383b5bcc147f04838331c2c32d17e76a26dff7bf55c64c0933eda523affe5198d85d90b55654cf544c287fcf6c8ebcd56f108b3f7f5a7c348f0c34c51c4f6f9cac2f9e4f8a5b5823b0dec04a8673bb202ed9a0320cc85c339140fda70996355824082495f0f745ed2764e570e395cb144f52a00d88f08855f3ea622033c02d8ae9491fecc2c0628ce64ea65a51a43794c4e19d4273a03ebe5efc7473da38537b82183e38e95b8b75b1e837db538f6552d190380c168dfc292db0e65f66b440a5ce5df6be883db7c87021e2b2a2d9feb09d26f4fb0c71247b409f626121f341229059448a5e08e612b4dbe1cb575acea3b92ff75de27f84b08a6af5234f5d5075135474c5b9ea5c8c086186bf51fe431942226192036497412845dcc738ae914b84d52e3858f31085cd872e3a52511ab72f8afd8a4830278316717cbc03a6ae2d0c375a12eb89ec9816321e36d4059c80fd92f1271161159b10a2f7aa12002db0f524bb846c4093232d77b82fb50143b6be04aa08d3987e02b6eb2acbb17e4ecc9c89baebcc191c068b7ba87a8938947df11861f3918bc75da83419489a4c2f58b29da3e73a950ac2dc7cbe0d47d226a75dc755155c27c266feeadc5b43348034a227529217e7b0bec9b9d8b6fd78fc21602bc86727c91f9b6b6496aaaf67a3235a15959daffdf6b82df1fadab43b0e3f0bd8a23ef729a5cbc86ada6047bbdc2e285d5e8af80902c9caa9aa59251d2016a702c3a3010777d8afc276652399b72902442333ebfa8c37ab9ab3b89c54f87f26c7bd51b12d8ba912f843c149eee4799f3ae9aae884da7ebad2e8b62a761cfdbb2b96f8ff1afb4b37c46333a9956b19dec6183f51790bdd28b1d89e0b1f2a2d6c73b04f3624499cc92b75c1d039f7af981438dd076f9f36dbd800942c9eac0a437b6706d0d92ae206e1c9eaa09c8d2646c7b47b2d70442d676051ece1a29f6d62848b523d1a4b77369e38f74710dd19ed89dddf84886a98e1740e99fa408e5870377722bc047e3a3bc86045bf0f91f3a4f7141e2ce83c1382391cec4b5f7b88f6e69fa3d800581896acaa1c66b352086db7f56cb4c8c448dba02c5523bddd40757254f52bb52bfc702059ffa50226dfa5e4969c06a9ab0037454c78d727ac2996c6a624443cbee585489e678ec42abdf91dbfbd2695b79a5241064ea06d95296a8e9a8b1a5a81a1155fa0a2ffc0b909740235e3338f0187fc9ce24a4f2202adddc23290e87e95bca7c9e8940a7de1d0bc9b1bc77737a5e0109fbba7c172367d855669b8b4ec4d0cc7843fe3f124a1ea73620de99f7b96d5e212cd57bbb5457fe258dd1a790740b35da444e05fcc6f2180d1b91284d5d6b756f8663b14a422bd19c037ae23a8dbb49b1e3bee8fc0bce85686e577d0604183408ce3f1491b5bf3fd07091b8097c564a5bd1e99759b05a6ac3dce6640423184a04e9f7678c26c10ed1e9eafa219310a64945ee441f63520caa05f2bc37071c2a224c63e44cc2f9be2ba532886b1c779c096e0596f154d4a8a6fd4a6d01945cad4b9eb08a13af6c455f43de3992ee87b6a7aea9a23207acd42f6245d08d1a0460d17542ecdd6cfd2a0ac6c618339dbeab03fc246286722c3105a88dd71cde64b995ca85be20470dc063a8a447af16ea7fe8895edffdf88b71914013761e9cddcc0aa611dfdd2f41474a78cfb5e7fbc9c6ab8783b97c34419106fca13f96b7602cbf33057875e70f08ee9395f2608d783b78492cb43fa2e31bf58983c5336473dd9dce782b38360f36f758b816cfe126e78cbb3a3843ad40b22013e83c69fefda1bd42a95e05790a46e4948fd52cc0675f223b0d03994466e02d487a79c54047d237e92a15df38ae330eb087894c6771a47ec9e3c173b4a61603b3d5d013d4242db9a8a624ed7814c5f9aad001977a0a98427128339c9896b09d4d8944196a5c6019dd838182c4da85223e67c36a9c9832d828b1da9e503f00345c143719d85d5b8c43c374d5e410b68fc3dcafb8b9de7a92a40ca557af86eaeb1c9d09730365cd0ea7507795d964d704b4d3a6eaf495e80f4a5d452996e86245dc838428d5abfd0aeb001483851c85d928bc3f42051298a1e23b3e0dcca439799b9c084d88ec5be5b63e05d3668c02937e2090b56c49662ba515ad161d46e83e268c94a8a30a0d682a8d20587c4f3b4fc48769eccffc942a0d48ab8ff5c114c9af3f3872ab61c22ee0b648d72b9aa100ad18bbd604daffaef6f77148264b45ffddef0a0e67f4094f929afaa3e6b37de0905bfd27ce8559f29aba7503d88ed4c6e00e55e4c53ab62b4078985dc710efce1a0805871094621c2c2f60d96a2f1ea201ee50b9ccade0d01174cc15686b8ae4b97a0aba8711072742820a306b0e37af509f0005d4f7c5e040f49ecc6f2dc0aa76dc81125fdde2cec1675539081739dc539162ef3b897cfbb074704fd5b13fbc97be07077af47f783b257066be8355d2d007b8b7e38e90e900b5eab297ef209150260184a5e7efdbd5b231d1e3eecc8e9f0234743f96126beaf0b6a261dda493322cef9d6cc1ec4e28e7aeb1fd8ac488b0ee428878e4b8f4eaf3ea3422fd04300b0026674b6045ea8cd64580f597cc2b91b50ea0c2bb1e6f791fcc3b9abd2b46b0d265c0f10aa0ec20a6cc014da36ca38ea1f351e52c93db0e90fd7ba8dbcef8b4881cd46224d0f29f69c4fd311efc8d315b66293aec8bfd11f6cba02a4a8e73c6b3f5d3e60216b5e0b8d21ef10264c969cc582d669e5d8d1a6a363a8b2342b78ad193d9117d8094dc05cfb96ecf792696dc2b928149bdc778330411ce434468b7f10941516df7616e6b45291f4a7c17a7c2f68e62bf9a8a8d33e60e189c00fb2a90267dda88f935c2d7508e0e5146ebc9aa5091ac23374114e8fbd0ed0f31a2a4e8fbd0cf2e3fae6e2d876f11562b88180d8d331f51b5c9ddf8b06f1f0db0ac7f5ad149390b618e13d7ac6424aab66ab82fc42c431d1db93ad6ca5fe686679989c9db164b909e6ccdcbe4ca52aa98e778398723828f935bc9fabf89a8a13cba84dad3eaa50e7e64492958ef0b864b66e3a7d4a2eb5b07524c1457b2f5296d079fec9128d2111d1345d072edc76f3213e6495c8fb5652032af1b8bcec28f83dda044e2507d60774f13c47afd69557c77c6efffe9d9030267e9829a7b493ce47db6fcfedf350e5713652693c113521cd28b3a5d60d2f3398a95891dca915fe2eeea4d32e60cce335e9756b71d1ae5e6fe8400fc6c0637f5e6b8b403f4ed49598bf8118c55e73042d4e47ddbd21d5271921993af12584caab82ec917314c7b1f7006b95d657f004861fb4430cf437f23981c3dbc59190b64222a9e0f8acec8d41b46cad0dfa29eb1b6a047ac088931a537fc6e27e901530634a71aa351801fc19ae07796a10cdc6400c2304cd05b876e17bd381b9647d711d507de7f087a9696c1f0627edf4fe9cdcb17f1ac23038d67cb365026b47b88ba3b5e4182cce56951dd8036804c2b44b68509f609af6f5cf43a5cd5014fc5868848a32c65bf11b8477c8df9643f6c6c04ec5736c11060df884710a6bb518fa01296d91768d2f331b381d988a8f955624d9a880b2e1e46e46ba5a3459cb5cd61eca562ee69ae5d0bd1c78026138b680c81923bbde069b8822f0378882e8154e48521c82be68a212509a73c65b3359507195df9feea29d1860869445812426629d094f433d874740121110f2b4372ba0c77da6c621bfba4fbc402d58eebfec3e6305d362391b217f5ac16a176298f6ef79e5c5a23d98e70d43550999b6543c22fc07e5133c639994443130fe0945d091b3abf8d12b62168ccacf2e01a4ca350a6e2fad2bf7af8b8e990bc67fba038990907077ef0ab9170ee960b41fecafd09370f80281af6c0b56a456d2aeccbf32ce8ac1944adbc54c2ce27b2421b66bfabfb6bce2508fe0970e45e2014daae7198790f3be5aedaab867e5d4c83065a0a0dc7e8c6990424095b7ab5d6498ba9b253fbdd87fcaa7d2747dc489a098a71596090fa4d5f82730f00206b8283b6cae659b7433c5ddacf3a02230c26a204aa70f528a8be3e4321793d2b475daf06007391aff7cc8c62b2e8267425cdf45966456100db536ae4a1f043faf83df266d58253a828941631a48d6a7002c4c1d8e0d831b27bedb46fde29148da691238169c1ba0d53286e6cff1a97397c4f40e138c418c21113042a545da15e2cf5bbb75a76c24e6e11f5b0ca59e2a91ff8fc07976841ab4f55f7b805082c221add42a1b77f01f10d5c75cb7a2cda659479d4520c163f31865b30046ab2414bbbfe4988200d5020b07e498a94a5c66074ccb2028cb00b89012129756b63c9720f977bd9ed7191959e8feda7f6de37cd8077989a1c6dcf5badf976c20e39e9a1c3cb3a95730f2d96e1300031823164d2d33803c62ff97de1738cf0b4998576dd96ac378f65e5a74ece2fdbbf4b0d98cb9d1d2a100f6951a14c1d1c08e5a85401ed60f812e8d7e9f38c2dbf9b69d7b818b834d7c0adcd5277f253feeba6c7bb3b85599a3c55b3da100d96a141e7092f873d2800c220bb0be0fbf6ffbed9a002a10123b43dfe27348abb7ab653a9bc590fbe299c404a0c3a2112a65c17a594689d2b4881215445107c59abc4598a2cd9aeeee11501f40184453fca6eb936fedeec031bd9a00c3a15c3ed58f9ccd54bfc41dd4e6775f3f2eb1c416dd6f0cf1d5dd92caec0cdf6d40b50502f0549e2bd53c472930da208226cc3c4bdc7a571bca6e7f1891b24a677f03b4bebcac6eb27e2e254b2605ddf407a782d60d3f2ed16939d974fab5e76d45713d7423c2ddf4b244020190e64c5724506c8febc79b5be0a1bbddb2dd232be05543811a0b7f9ca2bc020b5d0bfbcb51e160f6a83b258b84dea23793227c1b930fd8d816b92d110aef91b2e4e55c71158f1a018f70d7881c7ef623c491ee0b910fba5227095432508f96ad4bf636e11cde5c31366bbba3432cff414e32f715806069781b295a9d7191fae547c41283b5bcd96234141ff7f542d81f8a25d80e839df60dcbb1cb9546c535062d941ae0899e3d708747180ef4a96fa6b5c8a0dffc73ec91077d115008443430354243c41592b8cc58d7815ae0f7830303a099132c6cea9cd0683e7943af3b14267b7d37443fa623eb79f8442201c02493070c0b16d21e6bfdb4efbf9a0ac9679df10f374ac8e1d49ca2422283b13e903880b91cf231bc4604e08120cd2b3424858f6a24fc4a2940ef0a78b91fe2f74921b2f92c3f1102a9edafebdfa1780bc3eae1d62869816e947c106a9264808be0d42e89708590aeb0d8f2c264d507ab5e238dad876992afc73ec58b196451b754717267fb1c9b841f1e3daa3728f3e70998eeab553701d336d4806c8ac12fa2d3a38f6bdd0aa0f341a5ddf4f5deeea60df95a91f708705229ad296aec0bfa8689d9ec50db0f8306d9244b559b7d935e757751ea7b9b449e19307329942ac322cec47f45ec101edb2017f1c6b1cdeec5b223a3cda1c0c7a45b73247f64a2576273c5238958322f3d21c2822c2098d8e60bcf1f818aca902647f70be3bbc3c9b9ea07083408688d46e0f16043691fcd562c44a79c20adbcd95ad9cf1d774f0bb42382b2d554949aa85adda5fc9ca4c4d8762e15cc5e6c7c4b5d0786f094bbcdf3863aa4b1270c549e5b4e6a73b8f96488c8440cea52d490a5ca6141a0b03d776eb14f0572f5af537b4775f16dc8b2c8585e601bce28a9bbeffaa0977d65a9506b264260352efff9dd6684fbca1b434ab2b15ad2a704f9c8258deb13e146aa44360453f7d256c20a7ab95c4305c7b39ab9a4d7c8684a97c00de7893a0e5aaafe328845adf5ab7397f2cf52ccf3983904f617dfb7abbefcd1f4555138ba50106f6870a678ceac5752c288615a60517328ad4ea4d41c44c3863013814377322c7a4ead7a287dcfe8bffa9c1b393614afc6bd776ba5714dc0360cc40f5a576d1d406ae8f15b93a1284be9d56f2c7ec381e97be718adda762010b46da5b743979dfd3b7c48d152c0371254bea04b04643033f9a2c5c8d213a7ffb31cb60869120197905b3003df0ea06b4f39378e7242b5045d0c12442b569dbb41a5fbd27871f234fbe0daa2b8bbe9a7532e95cbd4dbb61a448d99bb1d7def0d6e737ba8f8918cda398c6b66c41cafe1a42573caa8ef39ed0b32b11ff4899031a657c8daf2f560947e93b01a631837d77e15b099513b581adadd6d23447e8bda27947f347ff6993e8a2a3b35347344d994528f8611be14365bf6d4790c759cc241e37e43f90d37f7e9076a903b5f73aacd51b5e709e2e63acf27e4b8be55933fc38400563eb30970b03f589d2183522b32c2ef22eb4f7c48e8678537bb9d5c00a955e5fa58c4b6b828190bc38525879ddd4083b79fe2d6edaa9b0478f4b1bd8e50aadf12a3dd2f423de98345a49c52862e5ed89d827f9f0ea9ad1986d4bec01a72ef9973f6a2bb171b00f99980588fe2d70a360bf1617c30ccdcf909ad3aef8c95a9445d2261bfc9d9054c6432c16901d1bde28707a41eda70f7d2576ea9aace6971f523e2c4a31b0e885a5a9e17da0516d80c5aed3a95898dc70f5732e0bdd9ca6e0578e897de20368b29ec2fbcb5840673299340d9beadb3521dd537a034f1884312a091cd3ea219cc4b5e4da3e7c665be54cd2b195e3c9be2ffb1bd36c9c702512eda02ce25cce1bc939ff2843e8f5946d17d892b927220a8471fe595250916006637c3d44962485558553fd15381d02906e8167a490c8705d5445abacc2531663de3990182a1de9b1afde044e94306ec427bbee6019441c87d4a2ea014ef1456cab915c87c8f1d3107cc5cd971aed0ea34865c81421eda14f94cdca1ad28c6b8b6e516c8a7b10a2645616b2aed3a32bc591ccceacfd20ffe111c46f9ea2856c879e95b66140ae3636154a101b87023222f8726ca07e64fa6246bc345bf2fbb530eb8830cb7c23a8c0014e52fadefe644e4f47ad9e439dcf591f033e5526c28631abd6aba5828ad26e0e7e7dba5e47d1197163168970cccf9797e43cdc345e4d1bf45359ca76fd2692e699fc3da72bdd9e416f8293a11157b4e439bb36dd8b72f3748c7604cafb5c5eed045a18e576b8f0cce353a047b571089b63865d1fc341bbe6eecf07a8531a935ca194eee0a0bd14cc88f6dba1372133c8ad9946113ff9eadc20c3f5cafa323934303cf1d6c121a7fb0cedfd91b35514ed39f7897f25bb8d4d101ba9e9009068a2459bfa0687d2ae2a549933d7d56cebc6c1f7b8e2edd8b05275660bf2399f8385e6a9edf312694f4d95f4f4b47c86dba619b3bc83f871c9eb24aa33621bfb1fbdf5008be194a8b8e02cc119ea43ba65c8423c324d92fbca73e64d0657448652117ea7525fd30222180cc46be7b9842342ace38733832f0aca0faa07d07699ac0ad5463d858710801a54152aa356ca24d8b9c78a8a8999454943a5e7c2773c7507b7849d868fa8e82effb955e22698eb2f5efd83492da1ce5f3caaaa3a95554c68acbb22168f5d01a58670f7971e54ad96ce284db7ea594cb29241b9c07160840aae799ad76537a3dcfeead92dd8d408ed7cc183ca8ad20cc5c426f520184377ea16fb4bcff27d4c52a82f92322200e1a5886ca6de49e7c9ddb81b1ecb761d456c973c015a476f5b49781836352692334198a3931da4b36a47f0ace1daed2375ba13f0a6e9b4ba49899eea6a9fd4dfb6f488b3134c34eb3f76173291c1fa84201f054b987b84ad331710f41da88e1068ddb0170c99b2bd3a92c5ca92f5b1eba3ac2ba59e2e9d254a2585d62b888ed8b4135d4c2bb4f50e891538991873abf481758d7dc47327e52a32c96135f91c301ff64e51ad8ef107a390966e1d81e6c7b774686d4fd48d8457482e4e23ba78924882de048d4b05ac3cf230cb48e5d74e202f906936c174854108fda954f8af361d0a4ea8469912b833e21fbc2d0196a2d84d59a7388fc7dba07f895ef6bebd6710325d82091745a7a8d1122cbace1a173e0ad937be339eef231c82695ebc75c08161e5753795a225f34b1110633051ce801c3628b4085c17af6c2c123f8bd1124858ff5806a315dbfbdcacac8790fc8a916cfa5eebe2902df7ca981ce97d4ba345e86a37b42469ab1f0c8198da13a39bd5270d2541f307f1da3afbb7916076082a1ea99faca5f149534c2474b561f5e44d4d88192d0506d3db03feab9ea7f20bee845db13cbd6df587db956d89aeb02260704b51d054e8d0b4e96bb0567e0a79c7b09268e73facd9ad703aff7e7e5d55a78e8a9c6e220be14119b7ac575a85a297cfbef3044e9735e7ae19c3300e2b57f67c160d921e510cde4f1092c98c72c013c6a1ae1a46d65b82ab5807f4337311c61b05c7150f10915272931e51adb084224a47a2b94c60051c9bcacdd76033fbb981671a1c51748d372e1fa28fd84bb7f5b2a031392d4aabdf25fdb5c1f90f3a0fd4ffb09a33fbd47bc600d9b3beaf428c55050e1b2b4fccb609e977a36997e807afb2e195cfde5366f16b7637fd40cd6104739dca059462722bcdef52a3b41ac60eae6fdb00e681860b20473e72b44a4198977aa2dd4bab341c5f1ca27046198eb6e16d0d8d935789703cb179457d2e87083529e970caa6dc33f90c7509e106ee076c2345fb1e547646d0412d1b313bbec3b5eb811cc5cccb30d9a2b778351d8816e14b8f2dccf9481e25758c3c7aac44350d8e1cd2f6a0d256db0c8de39fe5cba6b39a495b8685be4bd16e4fe3f4d8f3692d74f1f65966287141caefc80e4d444e71730e63a7a584d7128425a3c293ee9e440e1234bf8b42a6b4048e82406e399ed206993d08108ac46aec68391d89daddc533e176df54298abde425270a7890602c9fa0dc2712036d5a78742f5da6a4cfdaf4c91bf5aba484ffd6e33d373b634fe5370ee3c31886008b8d77d607ab428899ac79904f60f9df099c4f3284490409d20101c46d838b0acdb3fc8fa4a187608318644d870b5978689291c3f2515a6bfd8ce809c9aeabdb0e48f497dc268a8130297823776451eba6d8eb2a77597130147d04c93d01cafeb3be2156483e9a3e155cfd105d661c17fdff186bcd43dc0bb4e4991c029a66401f667caf50e53ed1f9fab1d4059cf13c685b91727e9e85e98e401c0fbce969cf28d8d0b75a3d5ce28680ef187093bf2204b4f03dce8b400599a896122b914664eb7fcc3b9f16c02543826ed1079d0e05a67ad5c82a01d565f8bc72f2017bd1385c780991baabcb7627bb4485461910423e6b0ad93016c1c1e3689c951942c934c1021aba6639cd6f9844532455bea1a5df69b7be5728cd0e4240c41f27fb03c21fc9cf99b859c729ba81309f39344d587a84fb7da5e99069874568ac4b8d196496415620be1d9e5b7aeb743dff7bfc63c915808d573c2faae228a50d7d98c68acca8d560c25ce65b57ae326e95f9a6a6accc6e297271e1047ba621045d050005dc0e96b1f24d0e6bda41d66568ae4a05ec909d703524b546a7b6a7f19cec34046ea9c1186d9883e1e368bae330a95907333bc49cf7cd82ba7bae17a1b94a84c71e9ea6ab70dfab57ee3bcaf641c933ac2d42537d26d03802512c2d5cf6a76baae267341e3db9ec765b74655624054f103c1468dd73d88258b18b7b3160539e071e404859d9ba23cd9f9e9409f7917c2118e6e65f7c82aab21f7a16f87759934858af17529a9a24d4736477811ecf388f3146e9358fe63f3fa8d743f5c8b53609ada12442e4f4221e2701e1a0c502df4584eeb94222f894f8ee306039935bc42eec0bbe94f01300afc6681439be0b081f6c75a02d3e05ef7a93228defca1a5f1706017930447d932e12dfadc17d82f1d74728d33712c84d9329accc2c9c39faa37556b9c4cbb95ffe45ef0c70df16417d670187f6b950e9097752ec8b1e43df26d8a980cacc531db0af6545e4c1d211349646a25755b3094cab73319c9d4e5e07779ebf9ab79c5feae3119c7883e69e6b02a5e1babee5c903f02e8f89a4b84bfaa0a2d9acc92cd122451a3fa321a44ba32364556ef672e6c3ca85bffe5dda6cfd7a73b6b5bca6c1a913fcd887c70bdd4b066bc527c1ecd148af3acf6e98dcba51023300bfe18a71bead7e996f568bc96a498c7bda9d967c6e1193ec498497a9c2542b7c4b0a30c9b411f32f4b9b43414cb8e7756e93043cef6f04a9d00b34222f31d34ddc0cbdbc05c76350f89ca247bc95f257bf907da81910cae6f5fa786c2a7329edef72a0cc3a7898979c63618bd94335770bc68bf88db9b40b7fd18f19422994e3f4b918142967a0265f59cb6f849ec2313c7c79eefe45909e932f9a61a41cebdcd4f233de106e56e2096b0030648b1510a78b63118b84a5774d25e1c5d824930b325a608c995569e27a5302a9283621129c2e64f13428b354d0c3e43f4e99182c8e9353872d6a30c7e046c23696b60bd33504cb56403870811d1bf631d9ab3be0e202d3c60d18d7d86e463b2e2bdf8c44869fd3101da6e14c4dbf646b7c4673f0bdb42aa5fcb82c06b1d31d6f7a0752dfb5b3d88b0e021f50c0b65a5bea217b426ff15911f0fe458d2ac25f3ff1b628e1888f502c5467708fdb86a5ca79037e0b116a17ffe1d625ee5fbd5e0bf84c51bcb04b56f38a0bb55b16e1950bee752b8507374f51b809bd18aaf8c3bf7c4b07f0b23c4011078a7838fceca7d0d06ea8d47321f0489051d3e4e7a1955f0d2e0999015674446318ebcad9f3dddb8ab06d95b0633a80359dd67826526bd66384f4fadb808f47095372539cedb01e5fc28e66f393dc038318b90a18bfaa99e08924a1da7c00c0a46bb2fff5964a80befe2542fab305fdce0275e5264d1f99ec48fa6625622318c59b850adb0d928145c36c80cd3ef5c9c7ab5307a2d7fb8bffa40316632d73da416f53987ecda087828435280405fdd22533012c45c70dd31a857f59163a01b6b6a34418fddd391e005b4826d90d04984bc5dde851c44a399446022291cb309eee8fd933d8db71c0fad333bd155a3d241606f338d29eb35b41ecebd6aa3555ba9d1d0b8baa745703cc0c2785532d21a998e41ee0f293c0e6b0779f0b1fe90fb4690d59fb3f5cc975ee513892055b66fd925fdff606baa74f1202421da509b85c8d87b1ea5c01e1d83d01404dee1f3e9d2c04bd6913ff3f79d73e190853189b25cc229473bcdb78cf984404e410b9c4b35e96891459f6211a1e7cf3ef0702123e427d92b97680ae9d2b32998014ee946b05315a9541cf21ee715ac48840a5941a96ad0989fac4d80e7cb72899afcca92800f461353ceda3818b6da107e7d618873fd44e05e252437c1cd81264739f24384ad6f23546af88a0c5adb7c951caeab99d4cbc5f15339c1a522eb9e33c3de84cdb23e729c8e1c0898e8e1350741cc3f87f23a39f462c5db4f4531d87d244948376108cffb31929633e747052640559cf54f01b9a8b4f66be7b96a9aec3034088896e01ab057be8c8502ac4d228a00df620785a3fc7bf0a423fd25815245516b467dc703f2bfed0d171586a7c95eb0b586c1119da8e2530f34f8ff2e02a19a4c95587a9cf2fa07ebe94899a32fd7e227cd9de74c9444452f64f8cc1900b8f95b3da306875b049320480d95ee47fc4aacaa0c41340c1a6bc53e775001d3749347bc2cc2f8b8853f02133606919ba517d860dcf0e435d6ea89b24b5b1e8f28b622071406d40d6a34e5369548ead4800a24005380cd66a54e4409c49c322899ef718bed8accb2ab23856a2ed61e2cfecc995f8dfe9dc56b560afef5a5d72e35e88c73ce033179dcac8e7c412263560e1115e769325b681ad7f323a0f9ba3d50270840cdbc01bf220a92e8ee0a004d98241698bb45c293811f6120a36d3600b16b4bf288a373fc7c04ac159f2771e728c22a733e9057d4ca28a7cc4582b802c714e9e42d57e3581e14f6b6c72026d663e5e305fb4eae3888320517099701bf5a3b02b45779ec6be1d2c01861ae2b9d91b3fdba1c395efa640916d9b5c53881eec63bd60fd86a4aeede7c88a9a2cd130311256de5309c918cc557570e64f18ac3c63147cadec300d8d1fb264ef683c5608b7975403073c35ba6ae67da9860631443818da22149af8e72b7f765fc6f900207a7c1123e28a7cf94319947e9469422902d4181ef9cbeb206d99f44c49284ba6bbbfd30b683fa45836ea99bb0296fc2b11c35c708dfacafeae675cfa7fc3db3caabe703376d8b2e7d3540c1e8e65bdb7672bef08dd240e78b6f08bc404886d0c16c1ad251bd387c73e84478a7785d09e68656c1ae556ec9a859a8d38b4bfac254624a42a691bbe5b54c825bf99ae531ac5966cf86d1155ba1ef404bf0f1c3735340318f46049a2115a245bc19475660e12997300061f51482951cbdab809c887ad39e4336a9b4ec60d704a9d8e1ec969c6d1c5eb6441bf50a3d15bd1e06be10af47b82fd2d5cff52853072c228bc867cecaa62178d1b6e61b4ea9b9804e9441722e20adbb162be68ef3ed35f403e55de35fd3f73f0b402428b3376950e155fa4265cfc61c2f79fde785e8a9522e3879fe944c4651efe68021afe71380ec658c3592dcd5ac87c7c732459978f1cc83433c4b03f05f7cf1a0b00ddc2b415aa4ed1b0afa09b66cad33200b4b0e70c7b35418dc377e2a3aa8bd882c6958c7a087086a1b1d3b9973907b7800f0b2e4bc29b0beb99da4eb6451b88189320f1032b50eba63751b496a4eb411b453f05b764ae5307140c9326a215783251c667822d11f675e28dc141dd949256416bda58f0da812ab3843f6e3f28826c2c84c7f1c6941c3b282dd280ba1a573b324b67350d3705a8126e6e525d8e45b56aa37a5fdf72e06cc25b16d0cb206a6c556f24f9159c8e2639294f0fdcb14f0e13d6b4c563fe5d4fd5f73b7af30d041c88e8b3b6e09224876e95740d940210758c169177de7c86afa255050c22090e99473315885e37483f6735dafbadf0f12b2c0beeccba2479283a099266bf65a8205e7a848b2ab17339648dda64c118aabc7125cbcd8d402723eaa36e969d7d5fafc64bc6f2bae17c6f20a2662246872d495ae3a53c9ca0b25585b2b46f4b09d971cafe465276da79ca0f9de5a64a048e30af56898b031294a964a1bf6a01b0a55af97bec822a5bbc21e115b3a60a70fb04a0833b8efc8611efd0ececb633b3a67113fcb0ddaacbaee8d3f64c04937f2e84dda793e2970ed8caab54c7418ce01d1eeabab5c063bb33ad9e53f9391f7bdcc1c65317cbaaf9f42b489bf60cd651e12bbdf2c7849c175bb66c0369fa67b0ddd52541bed6a647a46f258654c26507c153513ba0fdb599f107701a4acf912ffe114ed3495a684097d949b2e0f8c88bfadf4d5d0e72ad962d519346ee986bd8a77b29cd38b654fac3cd6d8a5e60064ca9e2b135e91841113bdc1ac269e348e86f419ba8e650497611263667c62e151234cdd514a95f094826cb9571e3a97cb0cfca135f87280ce499fba2307e2bc9bb1c218e04e45cfc92fc33e74db0cf790709bfb9a26425a98df08c459ae06bd53df2b4a0a24564924df213b126b50275f580c09f2eb74637975dacae50fc81c9b6afe637dbc4164119eedc5c63a3299028a65e9eebacac977d6e3c590e9937919ea5d11bc7b678353a15ef697e646036d38892336fc309711325fd917b5701c4414ed520acd7d09bfde2c10b90fc63ee7351eafe05f32aed700d7d68b9b45bf2f324f0be87152e720ae5fdade1e1c5d725834e568f28ef7b813eb420b0570e650af40e653cc75a9705f9abe6741f96973f7a942ad95aa26523f228498e9e1c5e38d867815d2270ee19c689b88d2d55ca757a7a00ed3f49642d83a1b3daf4df515191c842e95421d722a3bf51e104d3ac8fc8df6b8c2972a670b94ed19831f09aa7958f8d6447ec2ab821ab58c11fd8fb919a68c2880c5ccdeeef9cf5b385c014b5e3f78277bd063bebd4ccbaea1299a9a872d7c72c1b359605f8f46bb92977802e08d36590520821263cc4d046dc0e8a332a93d87fd8faa897e23f740a0bd618676e4490c17478231f1849302ff8c9c739cffc9d6d5d543c0b12eafe169d185574386a55952d18fc9444f59acd37e60743f1a80c78ef47cb92deed032b56c269442081d2e2cb7062646f2cf37eaedcf6396ddc1d871ad733a154c8718dee89807e8eafcea42ae26a1beed0096d779dd3cc00ba4453398fd4fd7ae226387c442590d506478d15038bda42af1a0d989b6c2a1c482a89cc3bbe8ea292a77b0fdc362397673b7ccc79e00a155184873b0ee8e279d381cc60663a19d282ff151f1541226c9d03ae24001c4c44d11345f47e4d61576f54c00f152c7caf41e5a1e20c3670fc4379080e24aa241e105c973c7f68dca8053878456760b2d33c6420ec210884153e881d19dc807cc94d02cbb1cadaea9348d428120f8fc689ebf58934f387c7dadda3af7e5c957e9c2cf2c4a0ff44fb33b7ab420f99e8428e7c2d7943958818ec36128e857d44684a0b93ae874964ec50dfc4d5e80561cc3de562257a79bae352dbcad5ba708aaff00e336b67427cd11858ae5b44fe74d5da52fbd05a3f5e2b9e17f0cf9a2eba1445e85cfabd221f3845854aab32d9cf15ab05593b7d48f448272ac88d691026303b54a26ecb53a82971f38b5e0d9813a483e7782a2c7d325a131187a3621cf74a735f11807335564d0ca800da11ee39efb1000c88eb7fe10ab213f8a193d4a52da13717a17478203bce4736ae33357a4699cb0753de96c280ba67eb9ec215aa77e06faa3589cac38b898ef2b7cd328e2850e24e7f67e085975c3f8c6dc5c0aac117897c05d2a50342a69f6838e07ec4ae108bc25863d097194b51de33defaab723cd0ba07426210254de7e16cd7d5ee5fe1ae4e3f039bd59c585650233b856870ebf268bf3ba942d9e3bd39fdce8937467524bb74d7f2ca72225e03898c48132b6e79a084ac7d06842a2202b5db124dd68fde3c1988d5edaeeb8298a27a84fcb3545332f0fb29a9ccc951a534ab1fd8904cd8ddcc14d497f3006906ae6e73a844b6c012600549b5a41f17c28c51f08f96042d9f47ebb1b676434a32bbc1035b37b4e5fa2a5b0d5ea18565e3f76681ce68a9c81a89b0fe37193d0cd0d0510b801e69d893a8c8dd73b5357ea70ddbc561c757a78efaa33a1db9b973a35545d8026a52479ae48061ddf9bffb04a80dd6be6e9b3defb123e289812aae63e6df8b3c1293e997f12c4ead9cca603c97560840fc43f5d17677bfb8d53ac07a719c7224fd33acf4b10af037261e61b378a2b86ad22640c5b96585fb65a642113431fac9c3fffefac60728e6e2f82fab26cbcd2283fd64287dd5951bf307b5acadc1007717c68e1cb0ff0e78a1e68ba060e3153224165e3da9916bc14ae0db4bf17db374e634e4035503464388134b53257f39a1f51cf2537a71e4a21814b26fc039342dba4c276ae33fb0de1cfb1571f37722be81f18c452472e46d1b8228e6eb269fc19e95ade188772456716754f9704ef384a9f2cb3a8fe9374002b4248e8639e5b4e20dfdecabfad60e3178f2bad4d986b9cc7d538757e68eeb12bd5dc2d25dc9f872104c5bf01969573f13232e220e033c0ea0a5732b8039b548964e53ae597935fa2c19f641a5a354a4c47b668b1d5d01c5c83eb28a9c30a74b737e3a0e8767088d13ed898c33fce1c74f330ef21b6d3ddc56f5039648e259ee2b685300ff5bc789f6ac454cac6c1615ce1390817f1b1dcfd320ceb8912b7253474a312aa1402f69804f4a358b8e9bb3ee3842734f9a22c6396e3e7c3f9aa8cda1c9b28efa35244c134789dc16ae9777bca37c601a0d7efa8a1bdbf7a9a6d0cfc9c0769f82a098f1a781ced5393421e4ddd11107cdc65e8855a2517442c4bd27b797ff9687052a50cc998e31d65115e6f308be998923434ea1993ed7b6864fdc41429d4c50b04a38c33793426e8cc8ca33653aa163cd8adfe6981883d55da97d54e1e7916377d9db3118d768955cc7f24ff3a6dec90bd7c7af8bb83da5dc65035ab126253d3aa042c2f56889e683b6fde58936c064f7cf49944e59092161b74ca68f26982e2daca278309481ae9d7274d31e8159613ad35cc4d0e6036cbf82b3995018c0ad74d1b09c4bf2c69e7b896856e9c490d6cfafefadb7adf7f87aee3751603b5274b835b60880aaad994f0b428024f69257a9c1ce2d00dac506bded97fd44cc372b4b1cc0a24d0ccfc8989655c960a993098229b514cd586ade54e370979c540afb55ac75c4aeff6d99e2bd8029b2aed07be7bfc5069c7330a49b333a1433aec64b9668b4c843dd53db1bdb64518251c29d6663e35449faf3ed468b602aa12cfd51136879cd305da02678a20b2ee533bab3abd058e1cd51d94e7b9556a50cef290be4efc9d0dcd678bea4ee186296e658b0a670a206822368b61c961f5e5f403710d985f93de7942343590bec28b13096b534ad4cff78df2cbbce861e17ea28c449b13b9bd9c5fbaa77ee5f4adb8479034ffb84fe47ba6fafd90d130b8e55313851ba895bac79b585235af3041dcf78ab10047492685aa78d9d76b62e1f9764d83efa67b16083e16bd06b468f93f2b393eeca412c2419deb3eb158ab6a7602393068b49c1ee2c8851a75115eb89328a52ccd604465b60d090b5e366b00461439b2281a66cac60087b641000cc10ff6016a8d054106b4e07ecc91a540c0db9780a96167b71c1e42af66d5cc8aa9afc23efe1599c7275b2514f4bbd94ad0ddc31f5820862de881745763ea59e093143396a35b120fba8f0a5a9774dc112094b06eeb9c828c8ecfa02f1408ceaff43dc0edb46cb596a4c1b1146ad71e6f870d027c683a1caeba253f9a3f20d354a867aed980e3d9d16f7d352e00d73b97142fc714541c604be2915193934800f75a684488749e32b4ee2541810645762fcf967e7953125001cabc0942f6cd1b9f0f7f4a8fa531dfd784db634ae8057ae9044c839e13c3d47fde170cc01f93b0ca6b24b6fe18b1f41b18c3fcec52017a4fd264432a17ba16aa4e4d715452310fe4a6e21924ee682b420723004e5a1dea8a2a64d783ec650d4ea3b540bbce36e17cd1596c32f3f762a886055370bb47276212cbb50f656d4f1130de3d12b98da30f4165b5eba3b6e192f639d8185da29e55ba34a88cd9ea6c98f82ad48525b138380718e84cde8b5aeffe6599f8b1b63add37bb10bb7423aa1e56399ae0ce2989b3d713b640abe96f78324db2e2bfe467a1f618a59b7527d5a29746b2b070baba6fe1e9c87cab186b570f799c032b0019c2f8dd3f9ca9912fb0f2b9d9730b782378598e237e8fd5c98ca345cc279df4e340c18bd84af190cd3ad86db511f58fd24cad45a64ef814af5406f4db2b2860a6acd3f5453f70fbdf02378fe84683992e09ca922958e8d30b6ff6b95c0d9974ce93dcae82c6367ac0a6cac0c0c6bdd6d4740915a52a52b4d64fb4a89e72fb16aad104909667b26631d157debd90c7e78837c2f1ed0023395fed2e51bcab7c17f82f1344adcd324d443d79a32e96647bbe0fadf8099cda3c3d50c8de51809336ccccdc8cbf6b08e75165fa3a9517a9f24f58299cbf0ccf89489550a1af9df145b6bf63dcf6b8d9388632b1650d05a394ec0800fe40365399bf3f97b73e7d73142f14c540c08e389d7e7eb120c44bac4bfe71683d0a8443d8a8e7561c3ef0bc00fe7cf756b3a2ef3d57926cb3b1b2013f0eb517101ba2e6e0698fbfcffed6cd8ff869df08ed3751ee54d61f9dea783e4eab08ab57ecfa5ce4151ae68875433f461557e91931bdb11fd3809b726f36f63b09894941d6e2bf50ebfb419dfe057eb1eb34869e20514603ac4d78d7eae1ee65be6640aa7837c2a820cdb0f8a4148fad2cd2c5d857b780e0b8893abd669e3c968c2787a84aaac86e72c43b8bfb7dab81f4a1430af47b5f03efd17969b13de190a158dedb4854f8a2d8d62c664100831a3cee3802a8f7bda333508a60de03a2ea4f844ed1d997d06909d143d597d090d356356962f9253728c9ecdfa653a21332b31d8b104a2a554a22cfefe08f0b11475d2458a85dcef8242d197e37d6b83ff2dfc5394fa0d109bbbdc316c33a8df2047ac75c77c02305cf8729f6bf9be2dd4a2f1e5defda5ce2d6fa75739c4b5cfb193fe6d519d64c61a460030b9efcc7fc43dd2e0038732b5a9cd1496347805e5072221456a1fff7c5d3d17837a2e87841d56a3788b57693cd16e1c5993d19a8665c558dacc751f441150bb58ba14c406044e690fc23fbbb26eefc3a5c721608ddc7b54d073eff7f95b9e53a21805dc925ffd2d084378d0208bdf1330dff69246d1d28703533a69022c4df1fac289a5120c48f0838667e0ebaec0a71627088dc065201e3b752fc27212886947aa6385e608a0070415e786e14ed832410ad846a9b220a0bbd63e9008e73520b55501c8c8f0e033f86e9dd0b9cdf1416e42543d1e35bbdd6f9a0c465091121383560b489b367e4d5a664e580017abb705ca6c4e8f96a2ac732f1138a67fbe636ee80d6b787bc3bfc1099608e188d4431c0acde36294eaa19418875f942ce30d142c7d26ba13a62a920231a4f6018b1ea76c9d497b3c21310edac7508321ab7c08331a5562305b1f49f0e9d67d743f467b54b4f345215694d4e11837ceadfe3681da2decb8e9cad70a2c6cb732c00d5d38d394a677f0c7f16d47687cf43713c464fea82672cee96ab7d786441291f26e8075a057ab133e5987301b4cf7b47d9e8472e7fcbbdaacab11a9ea0d8ec6e4272fa25da299eca9b5e32606a68a80943e51a62f4d9db0d42ae52fc7d56727a5b7ac8c0f06a55c2fa14b2faebe28dba8a9c84c9c5cb4a7e8ae2fb1b83e220538993a4552dde89bb814cb6c67d7cbb007431701b2be4e55a8ec7a95eb047c954a251e218a92ffa6f5b9601aa00c62c2dbb04d5b3e29913907d893f19fc4cb39144ec0193295ae369b98a03658d0c48bf2ded208bb720713fc8f1deaacdbe4180975cae3f4ce57a7c25d0f96087b809b6c11d9f5ab7dbb984bc67894ba13bdcc4793d5f9a3792474a27795e2db419860254c068ba8a3711edd90768db32903dd469465ef80cb05bbf463a6fa7a4035b7cf6bb4d494f5812130928ad6c1e7e1c1be37ab006d50c754eeee473ea8d77aa0f963c7dd046a14a92e786a6307d2ec1fb93fd39514375ecb1a31100f272eaeed1d246b672a2bc86dcb79c10d7031dbc2790d1a3afaf5d0d1fc26197515118b336e4bf379143177af76a233a7d6c59d41cfa2f3cf33d753408682f0aad239b43dcd91985eadb959c0d439fa937887a7056cb8ae11b146b5033e764eb7106f7bdc95e7bba5513e4e6da874cb338a9f57102ca9561d4261052b51714362d3d4a3dc45697a7c514fce47a0993ccd124fe3639db272797e9605dae3b0a1633f796a8d1b60c6e8df3673b93c1133b68a4c980bf22e96e96e53efe6b23986511aa4579f0d877cd348ee5fbd9827d5625b0d24ad52af100f06c1e01b1d3462cc320fb1cc62e1c3146d78ec005ee335481c3aacec1695d324112c3ff144feba30b198a8d1b154d28cb7e3e4b3caa95e48d2f25acb9b6b39b57ac6e997e084c09545bd4c64a20a45018afec6384431dea407675ed61ab712ea5e15ee12d68386a667fccb0ad5629a68ae9799aad16ae4d01645bd57574525d82b1110587e5909136da141ac331b3f61a623c62c0c8d66b8490de4e896aa1525f19e1c1a42820ba240ec74cf9c216b2d5d82a0b75f0b88909b6c7937054430d4e4091f147b7bd6f0ad6de8beb2e36ca897831bf04d861e015efa951cd0336ca71d441f7585279e7d908d77b28d38a8fba43038d8cc2f5b9612714357dcfe3548b051ccbd3e1ff8993dbf976cc84d3f9daf0ec3aa641770d1ba0e385ec670de0b896dc91e8337b2f1c6bfec7dd57d8c7ab49ca74b10e3d2a3f6201ffcc31489760762a97343613a718f2cb4f8d0d62b114a05966ff9c931c3020c0a0f1235cb7e76a6d40310056dd595796d5691e28c521ca63e5cb2aeb7b3433dcf3475514cdb4a306b86b5f6a7ba1a70c851705c5b84cbcebe4e43c28b8f828da008c0720fa8ca49e8b995f928d5df04692e39971481168189348c4a5a29b73be84153e8c91e6e5ceeb276151c5d630488c6b11cddedddcbfab94dbbabb3b854b4ccb6f97c2559c54bd474daf26d6d04fa31ca4e5a0824e0dd4309930e2de35031b9dd2b9feec12a80fea193ad4de272309212c5e96b19b7b0218d77dba96b4f494fe2225056b6a008eeb8fe536531ad50292633938bc57b1f772398a6afca7189ea39379b46c6c0042b647edb4c5dcc4772b428da5c5735db20c0cf8ca96d41a38bd908723bab33fff8876c415c8f6b4a233df0a887216c2c3587e0b2a453d90d54d37cb2a5c6f36b001a25cc016f7c70e19611272337fff7e1bac47cb8d0ba3d282a92443dd63ccc4c3b19f84ee49105d63c67814b1e55b3f542dc34aa0859d74977975518fe78b19050fcb5987e285377f3e83afa7eaa9c929dc7ba99da46d5e67adc0fd5c16ba88869f9ae059cf5d0c9d8a239029950152dcf586edd2bce920fa2064eb0c82551e0dddd2f77cdc27c3080d90307f1509610fc0e49f5a6290d225bfe443c6ede6e1d2d1770d0edc4742026c318b288ed7230a18dc5cac65a0aba662d3adda070c6c9d63b5ff926edaf6c2ebb64387457f0dd73a7921ab4e20abc4b8b4f8b4be118c20cf5019b935d4f80a210d4be6a6108070bea7efe3986af143f694ec93d4e2eacbbe5fdb0cdf439e14ca1fa5a119208af91906508591534b433a1cd82557e0843669e5e67bdde0b3ba8bac203aadf98388f0279b95f5b0013c80187fe8a3fc7646355876e1fe901d6025505de21c543419e0d2243480e9f9a37ee0d2807f0211b3e220965363bb03c4eb1c36403d5802f4718b3e6402c2dedcff41ff68f3617ed28a5740935f07215436f354b14566b9d387d952a5598a19fb7971b56e261036a758080bd72ff640437330661d4fceeb3e2acd2d307c43e13e8a24d69153a53d2a0cb5501df5126d27ace17b43945cb72d0b90900b67fbb29cde646af1abcb5fabf0d1e7a68e99b0c8f138624de454d01c4ce02eac6395a5a400e334a0745c3970628957f26ab396a7d9e1b1d3e4ec24de31ed24c8fb313b1363e6c1d3ce4c894d5851828781e22ce3c07f901f94fcc9cfbb1837e2b0fe41b2e2a3e791a0c8c8d6f1a824564aac617d2c8de644b29654a29a587055c057805a8ef9c12b4661f29266580719c17e9470f0add65de18afb98c78dc3c405954b24c7f20ee464b6032a87c229545a50a7692212a778e4ed7f477ebb4e6260f7ebab6da06acea1bff1f21700742b35bb97c0a0716c9bfbd6b99a10a918c514e374808e514246fec10441235cc4d7e91248ec0c4204b535953dab802032fa67c396921e154d614a42947525aee068c99f9dac8ddb0a7da3c77aedb1f8f9564d69899b9e56466fea19dea6e8e9ce3fc57eb76fe2d673363cc38ca905153e7dbddcdad6a2e72579990c4b8a9dc930c2154f89c1f81b0e111089f7329f0393fe21c3a97e2fc393f029ff323104a91028f40f89c4b81cff991fe02539a6d55b165667f5a635293e903394d1a59ddee0aadd0beb090196635e79c7bd0857a503001a9c73d0713afd62215da203e8015cba6809e1ea8e366498daa278804200e2e11125a1893d6d38b2568b9c89e20c83ca6aee4abaecbe44388d7e1c1c367ff152f75bfc1a9423dbb837f7332b55552549683554277f0333fcc329909b5e1d6968ac947ae7bb065ce880e34b193eec77d8973639f4675c343c74a9cfb3f420852c2be098c83fde060ebc3c21024a098f08a0f181f04a64302c9749f314ece745aa6531c064cff6e0009826647cd51f9c36a4621a2bb2f8f0d2860e30a274ea821e50ab7c2dd1d859247506180235a4e8e6852e34372e888e9fe435d4cffccbb89dc9c5e34a6fbc74ccaa0723c8d4ac74d0ca6ff068cb346a63f8f07e874f023042046a67f107f122c50c29439fd4d1082c26afaafc038a6e9cf02e328cb8996e93f847174f448319d00d3594fda68a017a6ffe2b041428ca32c284038ab882ad3b518ca61fa6f146719e1657ad1745fcdc838da061b519e2c028898a21443bc19228b1218740da1c50cef280e21460e4e080ec1c6119f02e819b1a13bc872e4826e60199414435da3d1c9073ad072c4226295a483295652906450396ea2b46eb8d9224f53593d3439fd20b3244e482400ae9c40c24c65f5804513129bcaea010c17c83895d5c3971905a96b74c2e77c878fb264faebb8f16105ac28395cc19ad9b90c3ba2ddf9d1aa44afc322b476f7f66470255d2a632249609cfe1fad3145393c2753ba508f7de6eeeeee99636e7277770f52093d530a2b5d11ca2c04d2047f21d9c5b3ec4bba4612e12f7da7884009c25fd288f43beed23fe79c7b25f83aec9c730ebe1277b683dc72142912ff7d910b48e94d40a16bbce7014915ab9ccf90ccec54e08fb9aefb78696abd01cd89fd473e4026117407fe05485033c630f2017ace7c909b1a91b07909feec70782fc2d27befbdf74aef7ad87befbdf79a8750975e11fcc8590fef6d331ab080c27beffd8c06dac36f14cde793cdd09ce5232ec46077390a3dd4cd181ba18949169ac3a36b5efff3d5b6448fc71b9aef3da645a28e71894a1a90ebc852b163ccb75ee6cb62be6d93e6fb564f1056bdd8e343c2cb7878cf5918a78871361cf3fd532e4b14c4bfdfe3b85134f5001db1748e36ff26692213093f4678bd0e735f113e97092dfb10435421422ac1c74a6948ded28c08eff947687eec47e8cf9e1f7bec3b95cd4460e74c0a93a74e1de6d4cc66a9991d8ce30524760d84f02384106a27688b9170de611313e9fefdbe3501818f6547640acaa823725df00d5aa1e91f3d87774d1ffd12b92992098c934348917e96dd39fcade682c5cf7e84f7ef338d0894209836b3f39ec8cc6a3340b2a780ee648f7da61119026584ee42060e3d3b18112841de67daccbe7b9e1d22f167560b923df6fb983604ca6a41b077da90ebb11f52faec7976744777e2eb0eb6261fac420a20ecf457c107cc0371afc31b53336b8a5c66bf1f21738f3d9199ec47c01e6a449c36b3da8c08f1df8f00dffd9b32909219aa147b07d39d94ee4017cf909187dba7c0babbd9f463d95d0633c846bc9a5ba624dd2eb3100c164d91eeb92765f6adfb5c462433f601e9eeae01f311594d57e2992ff57647249af175fa6a536c57549421e7a06afdbe5f5f2e3ae89c7b4fdf7bce71c09466bb9a0a28a66a444e53269aadbec50b684807c084f4a9014c69b6d5c7601ee6dde92455a6b6540aadac9da0cf8ac5d78464534baf25cdfbfdfbdd7c961c4b69dc4a5fd256e013e27b10b56d88a736592a615f7a9367299feea53d9d90c2107702bfcefd536ae349a1a7ff2bfdc6ccc1b87682be964a9b908dfffae8ae7f3ba1e9039efb4aefb2526ae3bf26a01f4dfe2f4bfff837c5ac6927e8eb9c00bf6a27f0e6bf2b4c16d2933fc0e60c08afcfbf422d2c8f37d644bf94c94777efba8fd97bd77b6f6b61c69ff81d17c9ee80fc7e7fe656993a8cf1c3ee143f16bcdd9f6e7fd5d5ae7d5994ccd89cf8ed80a90ba8b94abfc54cc57ebb60346df75dd31ad4a769ffa5fdd4e1f6d16ec76e04ab891f017c213321d25841680a76d40c5f008b38c8855da0a1dd21868c8820875dd9839437c8ad85d98e89aa8ec0397595cb5c05f3c06b359ebabbe6647737c9d0eeae0e4357ec9a000899451999b3c14d103747740a5a606a63dd398d4a87890c1ca2903c9555021b9c46a5c3513d95550225339bca2a010c3d74a3acfa7325ab4ad274da26636b9bd4bd612e484fac8eb6ca93d61341016082054e1039d1a5003a9888e2c48f134d126052620934aef410c3d42b3cac01e735533fd42cb1fa9472d0813f008daaca5c30e66a2bf03ca90dbf02dcddbf573b61bfa7f26c4da258c51368ed847d9f27eccbc8d550accab0c8ed74d21efbfcc5dd73ce699b73ce41771d53bf5dc6ddb97be69c735bb4a13e1db2df7289233b8eafaa17a1d36458d5ab9b0c06ece8c8152eee2e7f0059144628005ce8e28be9578860fa755d572c5d973b4186099aa051c40c408c80b960e9e205472efcc3e7b2256c40614bdcf0344ac860e298ca52028cb9fd4d5c82958690328bdcdd47cbad3d184a89daffa836a520a92a1921b3339728ccfe761c9bb78d8852653260aa95283e133bedae102b84cfefc8704dbf2442fecdec24d9ef1715e613f83b21e9eea518612946777db7fbf79e738dc3be5d98daeb7761eb5b2134b3b51460eadb4fd60184c6c4f1bbf3aeb9e917ba5b35f5395032328baebb5bdbe990f43193f1996c30b7155c63d7b5b530af8d47e72bddec0e55f89ec341754bafa92da2622669a66bd7ba1c9eecb210ec9b86e4efd24a32372781f9ee3a23266eb5dd6d9e5e6ec90f487e7546535f55db8b6616dc0c8d18c64b169337c82126048db0201beea38681125cb0f86001426699fd65dc39db80f12b8d3bebb0cb38358455eff9e8c67820c61a58cf6c20d8462724c9034c759867ba7bc733afc77020f170c4df16e320b57397c987839b997c2c972dc27090c05d98aa63d0e4a3023c3b188e23e46e1174562d91094bebb0db5516b9ba74c0d410cee1d11be5890e9a2dc7fe6c111e3c786c37372ad56b1b4a638155bd85ec22f795b583cfccca4855cf8aa13f54150392caa272148471e0c2a7611cf8905f75033750850fe16bc3e7571bf89811b9af7d450d495e7bf8d8d777334792bac63dc7eeeeee7649ed92923a679570ce319012a3269c94b4469db33e6be44fb609e3f8b82bbe4d5a85a9a76dd27a91d917999a3b63124e07abdc021c309d5328a6abd11a95da1895c0384e086780c8a1311d9020ee6d065fd5391718c38c07b512d4c6a57802b516b9ff6206e49c73ce391f8a235b5b9f2cb2af76aeb990fd3fde0f202390fd1e8480d3cf9f53407e302179a6fef811023485c0f380b94b0365b66bdeeffefe2c96da1fafa46d3c7c54aa7f148adf6ea889fa1d1df3a158548e66c6385a5402723b3119b41062c63648a7efa2bf0fb4c6bd73ef9c730fdd0f234d05539aa38f51caa1a1ced980d6f8bbbb0b44d3fd8706e93a14a394a8495dd3efee925c124abbbbb78465cbdc7668ccd5ededededed750e0c56a969777777af6ef76ef74b77ab71474ab36f9d8c2d5bde6ab6f20c732634fb79e4e41cb1b200cd4eea5ea1248ab258716e0dc661230924b16a8d9f3d3d3d9258d52ea907155705566167d2136a16f413e26982b969737f63c0025035f2529b7eccfdd2de18af990db507bf15f48269e734bffea53db4b1cd7848cfa002255a3b435ad02dda10da516e400b37d2381acab1acce467c70d2b3c3a99d1cc0dadfe67aef1acf8ea9088a20a0500206eec51600c4a11b848aaed4606589005c425b58444e840040afcc68c19e3370708203002ea01e27beba6283143f518b242f5a69d1124ad247ab2e4d9cbce0ca902ec55848d76a99c0b7500184454356ac50e30b8712c5666505ca132b50a2e802bf105d435f56405fb8c8bac45ca1814b035da1e18ca22b4966b0529afa3e05e85943a84876b4921284eafbc0a5c8872c2d1f56800dede0a3cfad23361e4b8a1b4c53ffd463c58a2edd63c5e86865a50aa16b09c9533f07d0d41cd48a8671940585cf060298db4fd515d0c480a200626e9aa579a50e60b61e60081eb095dc5458f3b3eae6013307ab3843e1c0c8908c63e20021b26abdb9e86d0e148464487ebc48a09514e7f0ba685499bbb292c9873f6692c00d08373332c087261f30c34c20dc4c7f78c5771f77c7a6fcdfbcdeb7b5ec5aff9992292102185a23c05c779719b2214faaaaaaeaaeda604c7de7dc7b2f665ae8430873fca3503231c6cc7b6854aaff1ca86e4dcdb4901b6aaabb91ec47ee67eb1ea7c8d53ed342aa9e4c1f9c4e46e46a9b8c072ba58c8989d91b3758753f023966b8788179c1bcbc571acc8bf7de7bcf7de95daaf42fb5d14ca8a9299b0fe6312fd2e7bb52a4674a5e6e18985f184dd5e67d54a15a1555d7a35017a6fd609508a4b2aaa4e9673320c8095f4ea8c5ced667f0c20b9d82416eb1c8c59f424f212e6d46b519f83b505458a7a9a86d34f3a7f644a0674090b33fb509a199fdfeae0486a21bd44b152ad4fec69e7f76477c4d6dadf19fbf77e6ec1ea14b39b71998d2e4f8afba4c655901d2a6b2562c98376df2c7dc7cb5e92f75b9b9e9978a5da3f920f7b716a84c6e93073ba34c7ecc79e6d0e4f71598351458c5399683ed17b028c4fb4268d7fc20e512de5921ea12a9526d92901b91dc0ad5587f4d70fd1cd7cfc1020b1218b51183e0c84d15ba4b0247345d92237244488cf4186976903f4662a4a7e9ea3050749093c0e51fc8491828aefb2a2587c494954aa592fb926902d82c65a552c9f4a6d49621c1909cde4ba65f9fe540be49fe95f9cc3e7b27b56da70984d2cc3468f2c0e4c32459d532a7144a7f93333298764d3a8d2dd0b54816c972f0e3aa6c91dc9ff733d7e47e7e0c4c692292359066134930dbb20913df9ffe4d009bef7241c2fcf6d7a682495daac31f605a4c99aed33f2aa7dfcd77e331a584f993b6ed84f9fd6702a13461f69dc9039887539cc9c77b1a534af5970bd2bf05a279cd5215938fb85196035d2a8cf31669c2946e914b654dab30fb78483a1531f23ba5c7be01fba554e406c4eddddd98621c8fb0678cef8111e38bef2118b05b4d3e7891743347305479e0736e35666693dae847ed78f11bc25a559537e5ceac82cffe1c19ee28ddaa1b3230a5d922e6ae6ae31737996e42e75cefcf3dda63dfb949d7f81fa92104d399486b23262a3272dc64735c5865c4aa2e529b7654baa67f3acd57afc88b20151be4e6abd9bbbe722c8c4314bd487f74809a2a1e2814e3b8bfe1dde8a8d9bafa0182ccb2238a9a09b6cb82507f0ff2a673f4695428a25537f6da39f71ee874cf53c26c979a3922bf9f67766a0537a3f676c77b4f35d4e85a6b4d06f65e035f229917f2b5597fd68f7df3ccd27ef6d79716c9ee80f1190ac7d677e81bff771a950e5d61eeeed111c18f18f63cd3a5b62f215b9e11159e600961e1840f3f38094389109622405caf7a9a772e04a1c4898b419b228c211a96f8e14a16602879a2e4081480187a414f14569eb476fc8b16ec7af15e8061a7016830570db0056033e1d85099b6dd38494cdb4a30b2f6599665597645f8ccd1b4ea8eb0f5273015b29ee9be7b487bac196ad38b02f97393b8b7b59a2988fe504b6dfae350a366b769842bbe9a48e899209849db953613a43424d396481b41bbe6c2fee2d9b9debd9b82607f6943ae5db54c143089b03b250dd535fe5de38ff9d066d94c101dd22b9e9d5dc5d79d19ef19d780ec0c95529bd42d0c0b956d6dab8790da8f66bbd1ec1eea557727cdf69ed91e34db85667b93d93ed443dfbcccdddd43eead13136f698f05a8cd663daa20fbda1bb7a6ab06dbb66ed4c6ff860c72db563771e8cf3df64cff6df96f52738f0b4d6f327d68babbbbb7bca59a1e3b85adea36cc07effb9fbb40be0cb6cc6ddda841f2aaa76772184546f4cc1e7a4eb0996c65066211217812e57a115e2fc28510531f197477a3bb2b6cc5fd61e110a49262e79c731109b9ad8f8ff6b85e84d78b10c2f5e99a4eb1a0ad35163a07a1bbfbe50ea1b6da628155eddc6e0ff2096cc238be9aedea315534cb9cdb8bc4f463ea6729de901a4cdd529655552d691b61988a55eaccec4838ee5342ee6b6f085aea7c7b9ff323ed3ebb481a665a645db093aec6a739e5e07707bf6f5c88c35c0ef688eee047c519de599b4cf6599fec47f64797da55a4b926a76fa369a4e9dafab4b6ad776c625dc6dbb7b84e7777df54d1926529581052c9905b0c15a1a97599fb5a17c6e926cc314f82901b6ab6e6022993638418672602cd733fa649c70011333437c66736614d865531546288e6c650d18eda0c4c05d933f51d0d09675053084d7702f81441d3dd4fee7e3485116e2a9ae20a87afbd98fe6cf2313320abcd88b033338223c2ef227f11c0d41bca53c148c5ab24632ef9da05981a4c4d63b22abee93dffa5302f7f230007aa3bb60018603e8e1ea636a67c1d3e9d4ea7d3ab294653993aa5dc51ca346dd4a674adcd2bcd1b1acc95eb5a407c200c628ccb499d7167ce3a4bda7bbf4d98fcbba8c93ae828913ba8867b44b45e84cf7984cf3974ee1c48511b725b9fd782310ac5089ff3089ff3cbb97320454ce4b63e985e057862847b6af985ec1df543ea1cadd91de1731ee173eece9df38837c8077b3256c360053544214d53592b2852862ca921c7f83203bfea69543ef4300c0d631c99623046d1184431c2e768543aa28bc2e8cb49872ac2382521dd54561547551821ddaccfc08518eeee8ebd705dd7a58486ebf2c1929891448dcbc5a004044a76c0aca862faf4477161050ab09ba7b1e2092b98f0ab099494a838f4e39a402caa2891282983622214a5b8a13df2d0721ab1e082075e62b4e4680992a3b090c16af4b803d10e452d51f001253d54e9010bd7e8dca21c4348cac88f985144c578a358306fa28ca25946971ba217a352195f6c8061005d2e24794754a27c253a38899232282fd6e01a2df2035143d0e05f5079538e341841ee4b134a68541d8071e4ff658a0dc3a8bf78c1e13b1023888da418e17351568131e304ad211760a2c8ec078625b1a0d38fc42102d30c8896e82162b540bea92ca2a22288b09099ca8a024805a823a8c420e1541615471851b10466257d2a8b8a201a950e140c1fd0a2e50b794d6569e9e2648416a453121980a92c2d675001e45cd9f0791a024c656d614d9fca2a2ac1dcfe6687d88b921dd0002451541cc1e71c054510d34d6551d104f422764c6559f1336f30a1514a19548e7ece655039584556160153595318a165288b9ec8420926002141e7a66b9807abf8bb983c21d0010b2460d001065e88d8612ff3861a26882205135b5469c10e1ff110a1c3134f58c0031a4933d8e12e7866687206174160c1da228d1de662f2eb744e4e122272482a810d2a086287b7c0401153ba80a2093272786187bb4c6ee9e0c5931788c43802861dd6a2250397a1153041811765ec700a26ef932d5bb6ac5b7e383d4db6c8cfadc6ce9ddb06acb4f83c21b72e0404e370166a02813d9b4fa8f40d5835b5533403000010143315000020100a0704027140241a9297457714000c7286407856349647633110c4288661188651c410630c02041883884235740300b6a5c4bcde1c82540cf75a53fadbede0604b6d44a7e44c9984f2b7c4d48d87542d4c4d8dca3c4c605237d92934073b22d3df8d1bfffdb53df3cbab1dbc5483f66094a1ead6a5f2b0931f64f0af8bcb973eacb8ae1097edc9cdc38fc34943641ba734167a79934a6ec12ac4180a230bc42508e81b764dfdf8f56528117e3d6b1051ba19caa9261dc78ccf2dce743933139b90391d5fe50bcd70f13219a3ba0870eb272af211e15e4f8afbe6df73f7d9e88f51ca467c21403b966562d1e6f50bb6d850d0c1c986a308ee0a8411304e82eb9904d1ca0532b236c7ca21a8ba1fdc1c502353d23fd332f5b8e15cd7a78e86ceac41cd106e3889261b58a6a332944f7c67ed52a5e4f08634c2a21fe456e069c271a920c12efc6cd418ea5d02117bb0411cebd1c969593a28daf84c8cb789ff68a36c766def755d3182e70c3da8e0f9f649e6a43b44b00fc99179aa9c510363ffb1775bec2f18b67e0088cc0e0087604237ecc27a93fa3f7f751d2c18baff8cc9a199134380d0ef6e53f83202f564cb81c5e9b1dc35e8ba9f0fbddc0a723e2c8e1b09b60c62436e7e5ae26f62539f51b5de4e40486ca22c3323d6fab9248a856fa2a00a8889977a29e7e119d96f566f15a39ca4e7f57293e16ff8a54943d2b92ef3e72189504516102a74d8793acc361b9d8fd8019bca902ee514a4ed3ca6312e09f60b70ffa07ae0ec7c71135d490aa3791b00e6d9830376e1f872155b80348d1d87ee4b84923526fbf8e03a16f2f596f828f3281a3000229aa40d38a735d169bf813f673611bf805352f3e5c776b3cbfcd1e90b69e17421946f8973435094b51c46923a2a9a759653db8b7765d3c5cb118f9c158108c4f74e570130511712e1c93fa26bdfbac155df237ef3be060f4867734ad6559e8766b89ae3b6e9b4ab38571bdd17d41a026cb17b433978495d1b07a19ecaf00df4092cd49abfcd4d7a00541fb9f19fdf5d56ce86cd56dd77fa5726fb55af9212b48aebea73e2c9ea609e16e801d9117b4f456b0c9ba5c7eafa08ed9667cb3748361b79380dae6cbaa1dec36afce4691fc154d9339df804743f7c35135cc4d5aca5e4ec142f9ec24c2417a8e6d715f22cc700c203b1633be805491205d917dcb18110b22ff0b8f9d4200b90529fdab6c27e7b203c3bdaa9f0f8385a36322d5b54394748a6b70b462fee8c432085a70ba4f0067ac14aed82442c0afb465fb70baed430291c105ecd8f3193ac146cd5fdb739a03a10ca0dd3eedf7f2a4de84ce663905b968eddcc862928bd8122ba93ba4adb53e0839881f7b522cefe35b4136dec1ee33b7b385cf3bf97e4f92d97acb99b5d3bd10b252304d8a2b400c63153b2314d09c27b2dc4d0ac29f7b18b3ada0d1ef52abcaf8a2b6a6584f77e36187b272830d5ee49dd70055bc4f514742d24099c53fd0fc5e1ff9484af11439034ffede4da549c10efa40b06d524118f9a6774ec31040f910cbd0870c923b0f9ad084a62422f766ca08d824b56ac69cee980d8bf293cdc231f5a455f718e5be9c903ba5258f1077324e964e99324e9076bba1e1af450157fc0389ba9cd681a8864e4d4ef15f414941012a19675ba2014b3c9df5e2d2f16f608cff411035e3ce187d49b4b35eab253d8017b1146c195448d18f7a317e30b59c6612a12b322d96c79325c53e00e4b91136d6abfc3e44eb798c9d2693bfc69710aab199e5e94664c77df4871d1a2a0c2ce939de0d1738fc6143bbae894fc57f9e1cedfa90890870d1c48369bad1ee1a5e37dc8b96c70befd175b885ad81732c581d241c691d0389ba457960072c314fa88e1d2e5d65be7522ce72e05e6e1e92bfaa448e3d2b2d9c24ee65f8a22a5e6229bf6728aecb83e7e9a0bdb5164c58f22994d7392c0f8d63a570fac99949305b2c70d0d28635546b4c1fb0ae9941a25abc54e87ae0f0d42ac3912baa0cf566b6aed7483f5367217670734e61020d3c0c17f2107842e0c39ba81de691ea6c0b6ac173c0b4f1c93fb927a0ba33cc5295705f793aa4de911700d8ef9d8dd991f5ea475d15fb98fd18aa4a78b93ccf9f5575ca77ff02d70017d68349477a08550f39cf95a8e3c8411ce6433c3dfd1e24498003e04ac84cdfb38a3160e963b32f545aea951e0a208262d3a4578181436ca9ba1ff855c38b4de3b04c2e364888be85b3688147e4216f1ef54369a69541532b05aef7f74d99c7af71f3265b4f731d784f3b5af913a0dc74fa92600f88f078129306321fe89b3ff1d96f920bcc189cddf3095c483ff5a3a381e026ef6f1a6a17581bde4ac208e0dec28e0aa495c5defcd6016c0cfef065f2c15d6f1ba189b2d80506a95489d0d1819ad355dc8f71c96f4f5c888b682dbc3b7f403d27f2b28d63c3547d8cd21fba0629ed22b58554a48deb2bc47474dfaf5d4bd8833f412461f7b9cf2f154f4c77919f5401f74ac2a488dda4089128dfc6176c08a58f6d061e579a7bacbf58eebcb8da11de160807026b1092840d4fa81b6bd99696da9be55ec7b3670a793b5507014014788c8c7368f4e8d452412bff3ad3ca540530f59371468cbe49d0fc9f4a996cd5220e4ba73b57addf99e0f029240215c5e710704b5e0180914235f3a095ca3f03e88a09f208a561344bf5975d1fba23174ed826cdf3f758ce9a8168dfa737061de1e32871f63bc4ca2557395cf0ad365a40fb24db3ae10a74ee413e8cfcf438309d5b09c29c1388c8f284631a07a5b8c982296660406272a82cf49d30944637e0f153a5140226de3bf20ec27f836b1774b26d07cedfdf14f17561427b50a10ce21891168acc81b37fc067a81bf600e36ea765861aa32dee1a319d3f30eca35d9a93d97112178e3336ed99751452ff37f1c4d5917f75d4c3909857ccd79a1f97bad4ccb7a0028ec0b196a860d764efd8718cc17f167bf0b0a1023b9bea41a712d0e8c1e8a6d2ed25abde3f1a0de7fb42a245d97b316a69b97688f73e4cd6e326a7d32c9ae7be76548277db616c0778d352c0bc6eda03fd8634e40df84a39bb05f24dbd418adc47b8fff8a5f132dae1b7c5329802868bb19a09f452cbc16f6bf075d0875985015ef30efc403f4fed7e7bc01bd1aeab1bee531dc8c3ef814f14701393182cce561e5a08286b913bab94f9dfe392cfb6c784d3250b79df3340407c9a241d7a388ab11f7c4f6958fb27c6056638fde97b2414f26b940cb5b0966cf60b51b5af8f5a96e86e4b0cdf15bae40cecbe03ad2060460cc62776dfcb0346b2a120c3552c1fa502393434a5582207111d07d3c4b68115f4001dc3a67f428c1c588de22a4a5d6b9ea78fad982ec31a7a8413cfec0d4f13444a04f796065a472a483bf3691f9698726ff3bc45a8b149e8daa5641e773b4ee97a52a119777d0550b53b5ed05c9c2edf434da04a867d999098cc03c96923436466fc965731b147d1d025e4cf71bb3cc76b8938158c55004fcf55112be6e9be9a7f1e291efa83263fbff31d1abcfd6d2dfd88020fe108dbee2d5f1d8f41ce157f66dfd0d77b64d806eac1c826e025a8a9162d51f1e001f618cb92040044e48ff1da39c7bb187277a053d0cad4412eafb582ad72925787a8edf44221b24cc8d2aa7b4484b8d3f0019395e89b94bd26a1d62b1f5647f6374fc3385f8e7d6c8938ac64f9053bf772dc6181c607763b6d16984f0224f66c592c4471081a9a5931c9a0eb98c2a81b013d36ac53a31652868e4e25e22443fbc40804428a7d874713284cd05c3277d9b972caf432fbf4b2fce88727411c9a85d44ae33250673ec4a3b0674cdb9b92ca89427f2eebcb24de3a630583ee35a494893a863f62e014d76277e8d45877a0f9d1373659e1a6fce0a86d8807c196b3e2d96da85d7a6131e629e2b606dbae52d60b1e7a0dfd1735a0f096a27807e00681e15439ebc5ace6d7ab16c5ff3a8fe94a898b20fe0f42ba9500d0af1502942ef1b6cf1efaa4ba05c4da07ffdfa21775f65a165f9297a52da01826ae0ab3b4c9e7c0ab3ac0fcbe63649b4802473981223dbac8dfeccad5b9febc0b5af0b54a47a9422557fb18e1497bf41c733cf32f09125087ab9bb2e6ba96473b1dcb7702519878ec493dee0ecd7864c3a0b67d58ce747772dfd4a89fe69cc5ca0dc0c1ba7f2e21e2529f5bb81d976fbbb95931817f1d989a99aa287c244bf2795c0d71e967199287ac6973ca89c1a4554d7418f2ef32c7f05b1ae31e618dc7e917510db0dbbb89501ac8aab5fe2a66e766750c3079717d3ec07cf2eaa84063123fb7f03028be9c1ede4096600169a1d81eb390062ba87b452ecb91ffe7100bdec6b2406ca11b7903d5a8253b09c7e20cc2ce6ec9f257162c1c9bd20661cf2c4b8b4effef42d4d588832dd8f4b7cb3d0f7b86c6fcbb7bafc1a88ef13255b3d2ad009fd76deef805bda8761bb7c0ea8df11cba31a373281ea1d73538f7ef3cd603d804f02548efb0402737119ec308d705b6d1addb33317dbf4b667922bf2bd132ff3236a07446f57c27cd902a86be01ad0d428615a96d9b2e758392e20b5835c1e698fbe0ca7b7fd59ca7a0118b739eedbf7e72d51fed5a764e0af649582119f1836730e1c33e1a8744f32a432e6dcddc1b8dddfb20f84ae7458942a16b80d441956a759dfb541246200f3944609e21b19f8f88386ac8137365e07c74e6290faa8541faa67e36db620eda234323732a9162b2cae280c535cbb3377e542a37fd6a254d7d37ac1cb43cd9948375b9d51cbdc017826da0dc7dcad88699f5da7d9a6abf91d6710fab29e42b41009abaa902014757b11c36c8f860290992e65417d1908fb62278b6800daf9dd0386acd1dfce6f22f3d0870ded2430ebffc40b10361882829d90c9c00f5b6679b9cd5dd04df5106b3c0976d387b1e391a0b53919231fbc6d868897d581b579ee55c4617f38b1c444306d3dbffe7aad4b92852c19bdfb9d72b0cab0bc5739225d08d1b4c6a967ee982e8c4fa3fccb48ec0e210c4f5e079044310c801e1d0859c550ed6e7c8086e1e34c7ef8603f0562a16e932c4fd4678d31e7a2cb5c42e8ce1982ca5dda4f2347c5f385cb410021994d76e283e8a27597e890689b8455225b1bad974c1c711e4ba21eb2780b0bf93d36b46f2aca5a161128908819f84e250735968403cf346f8780f683cf64a335eb2cdd758a30f67ab079283272102f28bb60789d618c7f81c8a2a46885313de6aaf9b5d1945376b31047c74a92b6a6d9e8726c678e376acb193be94f639a3692f400b4083c37ece128e83f4ed8f62c53c423e4ca627e8ba47f1c38639d34d312592a028f9ef30c16d039bdb624fd89d8fb9b9c7652d6ca259633349d9c57797fed0288404492367c42336e9d9015627d59e1e9da51638f546b222fe41753aaeab21a17193b6eac6d560df74f190087a089768bad17ed2e0cfc0632b540c115869eb9ee000ea620c5b69e4f188774a443cb78fc05295d6382d0cc91a73cf558aa9b347807436f4790beedd360d04eaa6200af7c9806c37d9b55a3ee6a5e64b92efa7109150014de209c2634fbde245497aaf21b04ff53de251a8e62a9d8b9a57ec1c9c13d74209eaf1bb5847dc7ab3cf63691bba8e2b352a4c5711003957601c51aebf598fe8edf984e2af70c6cd6237c32701fb9926a4de14d16a2ab781761542cc30d9b64a8b72df1d484c5def03ca699f045a1c5ca24ea9fd12e8faac9b3d611c2e984f3f08cae0b39577ee06134153ffb4fca02c2b1f7fe55331438520085a8160df79bbbdcc2d297a2ec3591128032ddc37fafa18475e35f694b211e28ac74647056f5b3f93566d8b91496c0baedb2d221c15ab168c20e81e76d85777f87a48ba5acb12b92cbd5792bd93d7f8093155d83ccc26c3fec3459b357cc81707132f6068c4692fdde0270bc5a7f1735214a7fd9571a64a71684e6a0a5d7f3feff18ce4a85b8e052db9f0a6788fcefc8ae9c3af07dbf5c3ccc14b6c6bc3b02bd3d93d2a98b32e3a74de3a067b9f456a26e552cf2d09961bcbe48710418edc8b4a4e8bee4d93f9cbba57c93fb957133dd0f2449da379d1319ec648cde51dfde6b71ce1ec6c86023b1c1a428a9af3e783b574cea419c1870f324a5f4fe8d633c9edba82771bfd0de7ea2f3583510856a6ce14b25c9ad75f51cf671c5e2c3737d6ef9f21ca6598d220b34327fde53fe2bd47983d3cd649f6b8a8ad0f60e5397cb2f4e1481d3f53a9c87605d68b841efbec36d03919c5626dd306cd044e5c753d99562c95adfd6b12fa374e830778406df65c1a89a0c394ffa167337b8b2fa06cfc28fcf97326b9e74064bd95a5aef114b6197767b9b2d0cab5f929b24b30d3f5adb299d9e87260e7f1d34399cbd81f170847e9022b4db30403192e1f653d54486687b88e57ff5b912391cc1b4150028e44a57feae9debb69e9af2dcca367de1fe1aaf682072d85fbc05ed9bfc79abf3aa35688c31402f241dac5a1e073104db08d22543100b546a8c8d3555372bb5581d597cd0793f655992ef60293d4dcbcefc96d0a836b7260125cd2f2ca0d38e3304873808a7f26ddca43e0be040f5d025dd301d042ecb551c495d85badd2220d87846fe45cf24183ce0cc11bf7f679b55513cbeed7a2cb0c1b2d22fa2df3affacf5117c645d9f0f2c6c45a92153d4047dc9875516ef2d30c0aa8cf2dee14a87600816543b71893c5477f6a75a3ee00f28d757fd5eb9ae67ffe11811545c85021ad0f7e9e02442b56e7a2a55e8696adf895e25e3e1861f3fbe0f1c28fbd8a96502bd49c1fee55cdcb00e74f19608af9e8d19b74d7f2cb2bc0f611a7191cd33fa7305caa0b389cef8afb260ee9fe1a64b138045d66849bc79fbb97f2993dcdd2e7ae64d1ea8db2f96e4123aa7f3263aea7c5039e94b8ab6af5f8cb8594de54c7dad6283ab80175eb3b81093b4e9ce9d7ac06f08accf724dac89332d7b6d29bc44ea1bf6789e32ad0de8681231ba0a2fde3081759ec45d36ff30b68f3841191d75e3c7850f153b0bb0abf8509f7339e94be00675febe13f4500436e7380076950fd36de18a3e84b0110686db04d5a157bf1a4c8bf15e0b8dc982860caa08a379147ecc0391c6a446d38ffc699e58f3c1a2a37039bd0d49dcfc19b5102f18fd724346eb41f135ac8536ce0dabcfd7d9843d72e99ef75317b942052c81cb87d50761fe1b780a11d4d581ea6530c0a52e48574ee6d9c5bcfd16a4634e3f173ee8068a363d845140317443308a5d5f48ea2d917df8c2bac2948f81a5b526b008a7c3c65e58f064d698bf69793513a52d46826b05a051a288efd1f244536e626696dbf0ec010bf2791bd3f01689e8809d3ac28e139f67d29a2ef4f99fdcd4d9dc8f9683034f7a1dd34bc54c920b01d2f6f502371b92dac1da9f719a85fb791fcca9ff93ef278e05b812fcc6504ffda05406fac9eaea7a6fbf32f09dee93abe358e42ff26c0b75d6bb73212ce6968b10ce1d8ca8515d1bfcf8308af7db6310085d0b469fd4dcb2cd66f2e330c1b4012ea541297b15b906d1db378c425bcc57787323d12a14082cf164db67f02c4b9a82f112b6096cb0d38a9395603de25f39bb185bf26991e0864595f0ef7686483ff1eb3adea39025497d4357466114857106b4d8cff02dfd8d06d037a55257a54b890661445f89af6e7382422bf003e6658bfcfe7f3d7a901f2dd95bddb1377d76978a3e305bf18dc282cdb95bd55fa3c6876e45a5c64e1b279947dfefc242efac483ae37a4599d429bbed46569837e5d2977d08bad02da63fe556553eece3eb92b6d9073f70287992028910187bf641d60cbf88df0fa46b44f20364b9e2414bbb8552f46390a07c02c718572b7dd00a8b24773589714d988cad11432cd8b5609d6368fe8869179c86cd0d556cfc969392954f5c949b19898c6cd1567a6110c1c7c17f4ad30028e926a0c1f13cea87229ad1627c0efbfb01e4ad6a47c96b125798d854e96609dd8e3d61149db2c830bdee05f25bb137303015f27816852a4d6078138007b82909318434789d892b806084e86c7669a9d8dd7ff0ca134f23bdd235b2a1f6d4d91e45de6a0b34251b3ea8ed5b6cb85cf402821dc5604085436ea3f44b0d2926039300759cc5e9f1de6ec815cb018977351c28cef03adc289e713538289cfd0adc519af56a38289ae94a3823a577c6ebe140a27bf5f9f10efa42f4d1d454edd5e04e69a62b704bf18cebe0a0e4cc57e04469b657c335287cb3af04478eeea93b1c782d679f22083ef6633282085ec20782c276438f16b10f4680577e59ab9d9a11a39012d7bc728a93a6960bf41c4c1bf07fad0513e6968589b633b609fe34f71d8de46d2261f717b648e8da1d674e1f5066992a94368777e799aa131aa7f7819625b7e337e1c97889aac0aae4119df58da5d2a3550e72e7aceb1b8c1d814a9c5d7be377f33ad290bbd4521fdfb8d7f6ec1b412b633353c140b141af45e4c128b1ab7a8e5bdbfc375acfa99b38d22dadffd1666020f406341968423e46499538c70e6b6d8bebcfb216ff0651301f14d4a24a96049fe0cac89b19f80c82f40ba26fc9f088789ae8e572357e44612b6dc20186a61291ee503c7367db741d44eb9e35c4113cfe519ddb4c7619032e6d6c25fd10094a8edd95189bb13c045868a2a747b6e350b50115e952b03986c76d7d84af2c6cf8c0de16c2486ef3f7a2d14f4256a2aa6b4c1181f1d4a45b4fa00ebac0d10b597cc785167651ea3e2c368c3002f8988581f5461d5b97c7cfbd6499d9967ce00b69f0d9f0acb32853e3a8cde9ecbbaa84c934b3c153b68d2ef64b4f8cf251318e2bceefe1382a7061db8d3ab357cedc2b8372953e62501e802daa0b4f1a5b9b90453b0b94d1d1e9076bfbbf53d1644e6ecb034d6a62b913103b91b0610a569882d2f7f037edae0ed862cd6dca66672428dd6a8aec7c1f3d51dfdd53fbd4836b403f06d7e9d7b13ad50119d2385615d27301ee92ac706b4876cf96e7aac50210c73a2403d4b9e06c5471df7dac8524e0ea14b5fbac0c4e8505ba4559da4d2086e10650b8280446677ac32a4babbd513806881788763b62f51af3f8b10a5f2d5a28769c27c04fa9d333cf66cce4a159f36b3d467b5a03b05697a7b8162f47061cccb531006077883cc93d07f33c925d810af200f0169f4c5774bcfe2eda1953fbe357bd69932eb928a9b7552dcdb6c01f6cc20d2512e25da0140422a74af9c3cbbc7f6df93d55d08476704412d33b86bc88092c9b368d91f4f5daafabdf7b6545a1b33bbaffc5756fd9cb8e37cd1dc4c599b1b51a2d6b7e1c0821213397eb8780ea4d9ac03b1de75b2d7b19b8cae39505d34acffcddb3a490defed76f3ee4a107b8ff6f81ed58aaca6a7c9847a24008afe26e2f3c8017d036c110277178f16e028c06354c29e2ab185119fc79f4346308edccd83bc1f0827f76bb5974a0231bc372e35bec0f99cea4a55aa60027dd151be4d48a8648b5dc001e379effad856782dbbf033db771ac34dede1f7c06df644bb548014adbe7e474fa729f637633b14e0c5f3f9f6de13e8370e3da92eb33db1c8313f4342ae018e724d1b71d8743c9b593c6215bd4327f229e03889617c0f7050e04ed163734f98d0958cc924ed58b09982aa79116c49a8132eb297c5c2d516a01fa90d1d61649a29b0518fb7968e16c70fd1e994b4f58805378d75a31ed276681b0c10d9d023cb3026ec3ace34567c112df982a28f16f01d60756caa912467576814907b8d26aefbac12f4f93225caba52cf4211c7812a0b9ede9120e9dfbd573080d2de0977afae545129a63d603d66042d249d2d7de3706f4a175f04e5bb477e3482e8eeb61d4cd3321936efbb924b653a8b298db0e09eb2c0eb008ec9100450272b90eafe6c2442d9500d7e72313ce423aedadf67433d7c1e16b4823e41bba6a9bb31a9dc5708139b9baf5c91ebd07d54c472e212e8df244e50648574eb52887429db8a0dbf8486992486edf0825e0794f1e9f51b2e35329d233a8041056cd28bd4867d8940cce307f8e9c95239c02a3238001a8b32dd273498e1604ac41d84d326252120038b75720f4bb93ab4db9c7a7905a3aa9dd6abbe6480bcbb5c735308cc8898d6e0330af0aa3f402042a9b84a90c925d662977aa068f9ce07979036d4f1c5ce4585d01cecb39628a6b20deabf4db9b63de50497d1e47c25058ac1df1f62517d3bd8335fa21cc18650f44a9dcae1b4bf12a8ad019f1d4384b1738c6c652b7d0aa75d8235974252d7b36e87008c35676317334e2dc5fa91f7d26f9295efdb8b2c13ad057bb138c2128a0316e7c155a9b392c80e31fd924b668579385fcc2f0b6aa80b4b23e3fd3ee093e809be88510edfc6a01f537d58293fcdb86a79150a0b1837bb22a598df5278b03f09dd20d54be0b7ac63df5c0ed544400764f9f87b21ba7a59c9f7a60993790f4e078aced766f763c68dc0d55125ef07a3b378f923ae37fb5de8547d88f25c2a6d958507bd773b80636ffb9dbba0f0cca5ec32d6d13565794cda02d3d551a8d8fa557fb50e826d73488ed8e8a81c0c5dd44278c6ed96eaa44f2a46bd93e2108c1710afea7c9135ae60d10971eb3820354aaa7b7803cb79bde54eee52cbd8308a3c57923129b3b2a4c635668e9ae526a2e53a2ae5aab7cd8eb52dad5d442005e248ccf9b88e9b2949c311bd3aee46b29a8a12f270c0f52c359e8ca99191a31b6780f0e521a8706b2d1319331e8c4ea69e0a989cd8ee571b50d3af657d63068e4707a019fbafdf530037cdd8052039f584dec3c908ae820fddc57e18f10628ee8c4ca908ae2df7ccb42041aa663b93191ed4b1046ddbe8b990a82373444e186a35b4b168624ecc0b876df665e858d13191615366ac34067580ae0c838b5abfc0552a3e3e34f07b528bdb13c1bdc686d3746d3997da0e990bab503b799a4724885030f8034cfb3d2703c4179249ac1fc11873a65d13c91b521bd5e2dfa61af04b2aac0394c3f3a82565d81a51783fc0bd2a8ac36447ec5cabe0e5c6b87aee4102e1fd122068afb4d82bb595b9b081d845d0319d385f4759f5b61ee6a8da6f9cb1588cd8c40a06b39931f5386f2194ce967d9e8b2d70528c53135b34d721cdc6a4e75c92d21295820baaf725a28d9979c19021ea8cadcca18a595d2b58daaea1e9db86433006391efda745aa48c2794c1fa8340205c9918594d18848cdc64c7b7f78ffa1b9d8f7b53b3a992466603d46f6daa539e43ad217be4cf36a27c8f191cac01e4575a41a141266a4c10cc7c8ef8d8e670bc8bc460b943b8507c942db57fdabdfd52e4c281c9aae15b080e6527e56d72ecd353f46c2a73a02e9a3c5aba5ffc30f3a1a99d5714cab11c22400be8329b7c278338058e94e2b3dbc6f1b2e188d91032a1cbfe6550e3c4e47a342c54dc35264995952faf6318ebc176355efa01f560cf53146ff12cf7b3e7bef2f9b991514f91ccb6c0712063c94977482ffd463f874a445fe1696c02be0e9b03e8534d92a740d069a5fb9ba601d64de9bea1c7836830b458a1ade315ba97854576817121c497a6eddc53eca580e8f202d0348f3aedd7c0e9574a96eab1bd7e591ffa03e971df27f805563f6b615a6ce0e47ebece85f13e6c97370054a6105984508a3694e6a2c5be0a38bbfd3027ea2b58f4a8b22931bdec74c4e30683954e9f9cdd6d8555ba37eef9795e9fefc351eaae559bc2fd4bc279d1cd981affc89a4751adc0a09c81eb510428237d800c3e0af71a03726c90f4381683e049893ef81fdd156c2b626e3fd5984433947e42afeb96a0e69c55088b182083e39964252d347c3797c6b2389ca6a84d18094f34ad6c7554018376a8ad5e67edc0bb247fd2b02e68cc938c9805eba238933c6da48f55ae4e3d91f69807dd298ffc811aa10f51103cf72a4b485d7a8f47ffb58d0f30473abae8c6bd30810fe6dc4e04852c229daa9b6ee1c07d57a58582916e918186d9fd1a1f8628d15aa5c566002d457214759affc07978e263d4029dae658fa5687caa5ee0b26a018e02419c6dfd4aa1cb5073536446ae0bfb4ca61b7e444420314d0f8db317a1e9b78dcc714ca2d055c32957f9cf082cdc848085aa94c1c19e53686fb1d6cda3fbd71713724587b0f0659d024876ad7ee1c98ebe8981a750f1a62c55c02235fba13b18864462ce221a90099862feac2321972ee5cf159025a63d0736400c8ab3c3ecced6074fc35b7ecac7c86a205dd684f8cf36ba41d99233c13c7b675b01a4476a7a6b525335fe6b03a153c24ac6b5156277312cf473258a83e44a1a7176dbbedd68c220bfc47e891c10bee02c377d6d86da7abc57bc5631d9fb5857e923f7bd1a7bef98816071791b53fe0f628e88bc4c2861dfa769aee13221bd89c3fb5bc9af0c1628d1f82beb69975d1f519506ae71971b6cce8479040b438d5a6141e3310fd2db2c7d707c68f68b538c46aa2289753e7465dc9dd84c20ab034cb59e63e682088dfc7235c9fedb04245a4659cf311f5a302315f27d3159dfad423198988e847e0141b60caac2e9d0bf9622da41ebfb09edfed055678c05760074ecab0ade492cb97213cbb8b15689872bf79909a8f34c2f5a2dc1ab144362f203388f934e507ef20f95910e08fb584ed65ae6fca6c2e765785c46cbb4bdfa05c02414e68c80d0d6f0905bcf3e16db2443f296abeed16b48e7d9b25513e2061cc3addb4e47373aacbf150c0528c370a3e31e208bbab2eb675dc45102e78380dc5bf5233fc6c7bcf8b6222de24b1d6fe8eb811510314a03f9b259ccfe756ea83f4a0fa6aec9a7696ccb8075581227c46f3fb6284dd2f27e50bae82b6b1836e2a0705cdd80813ea2079ab3c505f0f9e92802fb0477a2c0f0427349f2e849ccd2044ea5e3a849388feef7e678d979110db370e1716b8086eea4203d94f0813f1c842b84f364e18ac804a23ac4e1ebe2735bd782e54a2d983dc7e9f333ac7bd2f28c98255146234aaf1778510e1d4f76687142843e4df0c800abbf908ae9f7cb80ed843400b0265882ee20d1978c2533fc8c109a4d400e7bcf6a22c6efb7504cb63e7e48e7a4b71c79a731c04cbcf9de1f6452a2b1bceccb325020f83ae5820d396000e02afdae2f2ed7a6a661b0f61d76fc08c7b065cedc5af29441d95258c97a9d689494e314e024e4b48265fff7fd757cfcc2b408b7da31b4e265067e7e2ca9810b5e1affbd8fcb4ba143a8798150cd867484f3868fb423b9a1a6a2762cfd57e5c2ada47ebb908f900a072ed08bf8ecb52f2deb6e4d994db80534c4f09d379e747f407879095fda4154dd4107a3b936c506566d516201ff110f4ba1b3d5a50d4b2d1862b527a96db8b734b3af1b36bb9fa61ca60523a261f6d354f79d8e54dca9a89f3e90948be36a1e6e755d476ad191fc155509dd480404dd792532e431b9e4d3df2cad38163b1b40224aae331c24879043032bc370f7977303341beb8d9ce2b359748b25830ec96898545c3123212329d256d403b03b86284090df9872a5222a4817acad9e49a623fd671facacb8957dbd04c021a5b10c32d6eae9b05e26112587f620a317b1d2fec2a92f03261d1ce170fbfdab09bff3266051d5695dac95dbc8039f4a362f17ecc1bddf87eab875676e0b7ed81fe77cc16a5ae12e2a2c1894179c2c4d183610f88d90d3faa4825b4254aa6ac95046fe90948ce5f5220a94672d73a852a5e4efe248bf6951d61816b876cd50349efc4b57bf44d0100cda15561ebfa22726e6b523ba22b00971f462a3529cd82eba951ccdf41466a90d963d97541dee8c1abd61d4a8d962d81c644877e7193913d24289a39a9065af65751e2af01ab7fef8d667d0433e9bc5126a5738f35c7fd2aa70daa7b2379970bf8b7a2a5b45139869db041e093fa9c5a20c56bd02d44d697a902aeb40c5cd5a1c160565480ac3f269133725ca44d4692f3b1e8b21e1ee61e0bf13c433d1601498bcc7a7a3896e00364426c9713139b8924d746a4cc1e8316125f89c59c1cba53c0ebeeca048cbe20780837444566c172e581bf4e3ddc26731bbb0cdd4c4fa3a8fcd7ca85c5998d71bdd9658eb31240f5f27900ea8c856e526737e849931d1cc13b66af8caa18d36021bdfb255dd8e696ed33e8c9a1b04c55b6a5a0b002b6e653937179ffb1c554e41e2c98877394429bdaa3e954ed88f9d6f8fdfbc824343b804d902ce7195b45447a4bb84137b23ab22d16fa76f190a88cc0087b52f6bfcfd4710e8baa15d0f289e2e989d7c7adc35e3b5f51e05ec8ad1c677f61c81b98fc553ab7904f9208cea88eeeb71b7725dce96d067a7dc92c9d9a39532be2f93b0bc7378e0ecf5e4e9eba693d861dba42cbd2773dcc09847348395e51e235557fb3fcff74233195454f7ccbfb62cc8d349c8f467f1f6b2b67abe4b6eba226e36d6650a48492897c682dfa36573685d9269fe03b9c1d333cdaccf1ab39812ce7ddb700a174cd86638c70d579d4d1140a2163c04b0269679706907965d5346e76027c5feb230724d518299f466d20070a780abf5cd0ba58b7dcef9f8706145bdd924d666929da33ac6d081a77d7880029b8ee9a1d9eeda3066ec6e70dfd6a7aba520a0d45fc67bbe815e91434653ced825f27c68362d49f4579be1559b4def0c289c53b06eac99c7acc92a9bb32bc79a3ed4fb13ab0d7b70de6c1467428c82cbaf321835c2a1a0b44d8f73e58641c7a6951ea99e23fadd353f1aaf27dfdf818c13831aec05cd2c181d091f7952981c7e1e88cf8e60717aa7b7a25c78e88ed8cf726c2e9f1f20c639bac2996fa3315f1fd1cc0f5bcafb659772276fef2f0b6ef60c00b7a236c5e3e34f75e59b7296b3148f7a657119cbe2483740e280f72f6ba47c308235abcc88460f420923e6b965f8336deb60f3c9fab5e222186e0534cddf7d8a8c8a6d541a2fda6bb3e6dc321ffe6117b1d1e8214c709bc2a7f575c8e12800e0909c2c5633d253cebb77474d57bca08b00a3e911d8f4ab73003cc69360753054160eb0bcb359e3d3e9bf70d7c5b65986546e0e362aa197d6818f52d5f7f58b63685a838dcee7103fa7540345f891d18a66ef4c58c9e2c5e5cb4e68a3bfb5ff66df815068988e9469de1e314d3d9b21bcccf5790c30ca0e5efd580e40d435e328bbce21d6402c53528d53f5381ebde8ff29f35d1f8adca3f1cc28fa217d408b073009a2968e66720cde2c5811e5a2d3388f3f05c741182d79145cfcf10bebc937e9bead10a1fb944014e3ecf795c54cf3e06e6688048a339b1816d61c8a9cb2949bd0e8232eaea4e509941cb9b6a39ab71804df3898360e0e4d46bab5d3089abb6ca59179c056dee443392ccb644ec4703f3952fe1a71d858e011672d4026200017755e72bd3482e307670d83bfd67cadb8f50ec9535033d4370a00448318de5921db3209fab908a31a46a298705670172fabfd28b6c0880b2e8a4664b45c5805739993a9fcca6c879a88e1f082c1f2ca4d9299eea838a13bb71c0cf17bf94d82b98837a8863832aa05aa741b587eeb33547505d36a5b1b725590d92aa3df9538379bd5a6f7a750c4b50528511067fb2148f7b08a9d8372a24f96f49818cab9436e6b084dca0a3bea9bb7f4e457d801c2acefa5693b5b21c064f29fcc01fd9e06d424be982985a417192158f96ee8d26d5d7704e0afb1660e1ca918b5a2905de62b0b78bb48e4f90146549ce4412a58c81ea109d8882a05282abd29c0afc732208701da8e24b29c1ff65bf2a94ec899a719548bc0b217e5af929114813ce2215853671ddc30253615042e609479e5b7b9fecbfd5b2e829c65b19b38ed16ca8063344437b66cddf70ff19a65fc44e451244fec28bd834bec2bbd7134a085626162fc85d0297dd147481bf597fef5ee37cc4eabe4bbafce94c8fabd21ab7fe53681d99830840b4535567e3267cb7bf3b5857818d4d85a71229c3e14abbbe7542297396243980fccaa2c88f71f36b0ceb335394150dd80b4566dafb62c2c6535cb7a8b3d34f0472efe27c03126e5fea6b3c29a0a99100f336c238df4fe9bcd8cabaf1d1a903800e591835609e0e8678d448cc77720258057dc195f8f59853a20ef524149ddde8b9148cc2ddfc80c6424b405ee1b69302110736889c427add1c0fcc113e0c803894ffd57dbd5e01103ad6e5d3d18ec68f05555c93e9a9bb277fe86684aa435c2ed13a604236a1736581218b15587d6aae81a37b83da96bc504e566dee87fad29ba5f17cc7503579c18fb138d043e457f0f4c1202267209a842081b92a86a58c9ca23b1492190afc0334452d7c4f18ef0f322edcd615731ef476994562077db6da4dc816fca09ba9c47fca0014f4b4ce0bebb8a7f985967f52021ea898ccf13dab6b2bf2b541e3c289a61c809a29e1ec94fe085790396215257a0c73cb79a452e532452ea6ea32da0cea8e878358ec6ed3f1a084237d4fff0264aab712a1551afa117884a2412749309c4bd73a85e2a9b87d658762348551d1d747971887f3858138cad5c784dddd5670e2efd173792da25b5465ef600d408b2b10e168d795908825f808c7d636e2b1c8fd3b885fd0dfe6e422c0e04b10cc960b7b8a8c6c743be5da2c6b411d5c453488fad414a5641a0db5447a1fb8301773c75482490b48fa4c1fc3e42be737854e399a7d04c863d4cf8485ec5b485271dd2b87ea43102a9f3f6e4084bcb017ae2953419ff2fa1aa42330b2932f28a73865a9412dccd4abf4ceb203e2c11ca206ad13fe632400e0c2a1a6769f16f014a7bd2264760391499cbce118238dded3ab3ced0a1820fec33494feb7a6fa900ffdab45de59f23f15fcf33600e194a9b10f8dd33c8e1e623b742c53af378d90f8823dfb92d281235cfbd05a4485f1e5d04c28a47aaf1aa78da4554b8d47c691225f8a31c9ef0ff65c964fbf6484a19a074b84e6738227bd91e0a0b79d014ad09beb1b5621a467fef8b7cd6320c441fc56c1e41b578b52fed0223749ae6616d2414a516e6554a0019229dbae9283313c02625b8f767410b509f2e0306593efc311d06d7eab78a97d1a3aef85ea5889173f01fe57a2ee3abf8a4fa43c49bf83da3e2d23f24dfc75b86994f33e31803d3e59a334390b1e4315085d6f560615a92a22b3b33d3c0e4dc367c94d7e48c65c1ebe08529f0f232f11394261e6a94f681599ed7b28acfef9d17c752b6e3899426ff7d374c255bd7f6dd900c290433baa5f4fbc1c07ee9c426027f386c3b59191c25c1dee3f307785828febb3265f1cd6f359d4a97c013438e8c2e5cc47d9398af61dc359e3b03107299fc0d5171ee950812273674b8e59e7ef7d5f90f929682fe559c57ca029a02ee81cbd334b2147d5a343d80cc147184d74a99241a244bf7b9684198dbd53323acfd0058e76e43c68c65ac5da8d5713f07845985972734f75ee773f637bb07614c5e575c5bb3b7532f2917e6ad4485ae9d492025147bb9afe8b29f85e19bcf758fdfab56e01f15ca1da349666c16106d11b3606a277320578fbb30aeac7d618eb8ed9984e205c2c4bc23e43f212bbe6fe37df2a4b95fc0b3b6c8083bbee592f14e421ea12713098b984b8f07ec100c0bb0a7db3762d22d34be36b2e32776a582bc8e0f7b03a3c49be2940343a69fa0f89a5ea4f80482d5543623f732365b3d407c1393072bd89f57105d4d9b1bc0eca800a496405dc777c3c4a7f3e4843f5ee19ac26d746cf1b3a66c67fa52b6f93c6a103e08255ce7ac52c64de026cfccac1bf00f009c6f7081895233c638288cd37b5991df323714aad23e4b67d4fa56def8a424bc03f7caaaf15725b6cb7de0548abab2654a9e6ef32939876eda1740217d803aa18be2acf0cc8e5ff01f9f5373a9cce004de266f0c469001782d41c92257db2c47042db3d129dcbc406bc1872c1b819b395b2023054132c003d785e292a0acf61e82d759b3a10b1004283a150f44f2396aa3b75125a20897bba206c2b2bce85e3db6f576f5970a95d83c217467348b2f078508248c34875d434256f6087b572e531b86624e6dc135507e8aff0e39dd1443e95c06a29fd3ade0d7505ca353f93a6d5a68f88705301f46f7903f76929d63aaf3d641ac09ac05578f480c9c1dc0560690c7fc87b485861dd23d4f8e4d0a582fa9059564e64801a3eb38b6136f915e7db440de195c7f381cd6479a07675d3b34572b20c3e76698f02c3c500a3046bc6bdc6bbba66a7499307cb7a073047077ca98ffb1dd6170e82d1f99ba2095c5969ce0e2d7b0b934479d81a276dacf9f24e31aa3aa0904c3139a43ee3074242e85025ab87957dc907e43a5c1309b18d3c995aed8b77e744911901e530c03629f78dfad0bf7f272276a1944465456a47aa358468483ef6339b64341ecd730b586b5ab40247802198e44239afd05670bf16f7f1a396e10b8bf631bcc3cea05b29a64bfb165db99dbccc0a83913b01f1cc1948581ddbe76daa2ea336f393ab4dae9c2aef90881047826a7e262dc03c07c0cc0d00ab465a766eab432469931d7790a60c3ded4d9f78671b3e113aaa3b400a33be1615acf194f93251a55faa2f80454cb228dea72adb340fd5a3e6a06d0a5fbaed86c1a9547a7c3e99294ae362e45f39113a961d3407d373c1d18006c0655a432fd292282b81058bfbbc3c5bea744c086356d0716cb9261380e359ca1e7c99fc94a5c368a45b5c0787fbecca40c638d204ce7ff75867b1739b41177034d8b4468878a6af536564289461a1ce546a7aa3c3bcfd5c4e6a912ddeaefac97f66f50df9047b3d6347b4e218212e4ce7280b7d61696dfa3b1dcc15cac03b757f578578538def21b3ca073e9f3e07d49956170db6099aae195fe58bd27428ab52a579220ae79c92ebb9ebc4463d954cc0623b16e452a4aa476f26d16e5b9753fbe45742624b5c841dc067c82c2a6eaeeb3987b499fa6b9eb2154b03cf0284e2c5cdb25bfe3ec96053284f0e18558bd4e7be86cce00b8edfb8c3ee76f3c41405832d464f83e968ec52a055590630efd38862bef1c21dfa21cbe5991d5b9a888898aa0e4bd15a09737175fb111f356f1e527f225079e39b7d62c41b157c78d4d9ee89c187e0ad22295806940c4641cc4337e79dfcc8693a84b1aee12013fd2f9b1e0641b886f49d7b00214731f88afb8ddab263e628b1caad7896ee75efe3641759fa11fdd097584cada141d603661f9473935661c11f491975e1c8943d58f223a5f329472085a4d0166f873065fa8ee43162d4d9be74a60562145cb9e7701f0d219d2850fae5109703ae739be0820db70a348c57de42a779c609735b083f8c03a067921551d94e28094d2b25359cb20da509331642c49c6828d68fb78f6e96f45e486661aa04ff86b11533d2ce068ab01461d2d0a8c469c9f57136a466b1416259c5075368068e86405d5ee34122f74bf669d776f8b7f435786cdb4265dd808ea2ea8d93bf0179d7f41dd18a30283c354a61ab11744a22a7149d19529cecc95ae5d035aa49aaeb960bc00a1bd6bc0d43443a8c2312fd14f199962087d51df825de6021e1f9c5476bf2a8791fd1f0a622de8838d36c848678ee8b61f93733b23b16bc5a318b999ad295d169c3555f1c3ed00b3c53a152b8bf5232772f61016b123191c144d479a948e5ccc197c30812088ef1af120b559e8ddf769ee1a32fe387e4d39b192618d1ad562af97dcbe4460045c12a17c58624bf864209fc05a0ce21077f5d8690d4d3e0619766eed1a10d95eb40f39c1d71236c514192dc58e6c97d26cd13b9f04629908453641625f3907328d83539350cdb92eaf9b0708e7bb3bed0dd9dc416788f2b1ce5492aa8c6342109edc2568f02f81229183272594c99a516ff461bde96c357b3e7d47c6d0baaa2330a70204070be49e7679a458bd53aca376ed9f8f4d3cb7097b9e1cc1f0ce07eb68ddcff00e074b16aff71e269a0a4c5f1c8777dde5d8896143472d79d4036ca1ae1a6aaa241a8da45b053164f50f69025997df194ae76defdd00c0e30537b70d90c6ee1b0fe786ffccfd23ee90a8960e72d059b6c381f5506427c3b366c71dea2e7b2c29e7653722e0f31ca398dbba5c8a8d3f799d743df59bee3fb2e9f112f164e0f6cdff036e3481e1793c72fd19042ac717c3544e094142d48526704b920f5732583e1cc3b8ffdae4c4e333bd97859a49f452fe73b8161ba8a2137d65814955f77cd41283e5c87d99c2e465fd3fa3e03399d20ab797f4da97894dfa6765dc91d387fe7c5cc50d52a5848f50e0a4d26d71728a57c865f560e698b5ed32abe1eddfc9f108e94bdd7255acd464760e393d33b93e4612eea85efb9c548cdd7784e80211a489b095d56e284fddd5e0798d5f5ecb6e99f17ee62c56db873ad6fe7a2450e7c75388b6ccf31d602fc2462af8f3f942847a4effbdec54f345ac83bb5908b6531036e0ba4e2200f079fba830d7d2bac6fe15bb861f4ece29c8b28cedb9b809b311ea720e9a27c658d69389bb80907e0b0815701f5709080d9b62bc003b2784f8af4f1e93ed1ae164905b34f6332a7c3698210f35a10b915e92dad525aac7f176a35d84fb4bcc21c223d6a080904a49d82900b683992120b31b635644840fc9c69e5ef4de04ae804dc6da411062edc7d05d1dbb64f6e94120fdaf402d1bd13efc2c01198611d5678c801800f2ef1ac894ba258618dd6b6e0321389507005f2a61567b85098d8136bb12243f1c656cb0029379c6147f59c67d92f66590cea6ed36d72d05e880ae13d82fb28386f8163bc546275431c08746cbf3ce536e1c866f85a0424d5aab5b52f8cdbed064a8869220567f91b18766727cb464950388e6749b13a762c3c825459134b7f5b94239bebfada321e9f9fd8e3ed33aa3fe93ed8f3b3240ab0ea2a1e62ed7d90826f8b976975e606d101a143b8bb4cfc6a9746e22122a012e64aa1dc64aa0b9cb0e39c401771e8c00b1da0c9b0dd817ee90a2bbac4918b50f18d68a015ebb79259c6a5c8e73c02c9ce2625b07ea808610070c3bc4d88033c53f392b2bf7bed00838e25126bb88bc0eaf95e1115bc4c1c10a9e253f46fce00c881646bb2045324dd19f38e9a65b787f419ed59f9ab67d9f42edd9ec3ff0583c8da5790ad0fc2003a23b3400097b7f3c89374683d78f8b291dcc6f252dc259dcf543fea0d891e79a62f39ea9efc278de231ad8479647c2734a4a6b07c08709a3dd719dc5c04fdf653269325d076736be1ce8259463f72266dc8ac1bace34e6ba2d830921d02e848e588fa4e8b27fd4e2f448236cb15955eac4f35a871d043b17e3e6c0dd818ed4dd18bda61c0c41f020acf304c30e9aba3608c0419895e0ec386e991ccf63c2c0c738fea2629c7cd24835c7b109b3551532eaef8be161756d0eab22a0f901f2ab916dce7546a804eb3f57ea0e8a04e04bb7b633984ada7747f5cfc63f84d9e43b95075d5162eb926af67cf8cc66daf7247c703aba84981118a51baec475336bfcd5c77575233680ec2a33aa694cffc82a6d5d41042b5304721599d98ae6201c0fb722ba4559494c127aee763fff3aef69dcafcd83522abd24c27cbe1198a40f413a33d8dbd8695cd99eb6d30650b3b1a1eff2ebc378818e42d8902dac326800251b11160c218788d8515aeb00236c602e9df237837914edb48d7eb3fb5337f18233cdb5f5751c63edc268d864538d2b084d2e8e7033260e16c260ebe802e9bf2d08e156e7216c570ab33a0d31b6c83415c6c60aaa9dcd8b81644fa8f836d6d377adbcbb946f2d195429228465576d23ca9b53caac25452175409b8940eb2835e2bea513dada38903102580adc63402159a63dac82e02ce25aa3af49e424684c93372e29fa0cdb0c373769f68fd8806064eb36496c7788c686e4b40a5036342b9d6a672778eabb49ff07e82f481ecd8f60925d3022c80c603c6b2e32ce2b92bdeccd157f0388d3ee16412d381600e16375c8476bd25fa63f9b923e1ef96218a3f717c4017dac066eceed07f62e4ad22b256e2f33b4694896e5c7f0cd5fdf05c2d43b7b29e38c4a6b319928e105025e748556d2b0ef93d9501f758f858f2d76050e648afe03a0fac9a427301dabcb7e348aa789ec7b76aaca146f9bf6660f9196d7eeac5d10e818a87182b7dbc22ce10ade200696c1da0c5f29c4dcbc7e8db3267aaa10df2465371908443cb6c74d9871ee56d2939d0ae24b13d37278c2b81347c41e73d77b05a8fc24f9404272ba9b504a509647bad829e2876712e34a61461ee9167683d52cc0617cec808c18332afb54a6466f4d7258dc17158c257177efd75fea01dcaa52cfb1918f9360974c453e62eb46e148cfc448cf2399b3e52aeaeaeb9e9525008764e9a7083d887ef5aaaf7e17cff1819b2b42fa075da05ef38dc58375ceb02ca279909c62dbf6a83eed34215bbfd6ac6b8157111c3073554f4c220d5e1abfc37dbabf05fd4052ad73882d670e4d7473d433bc7873757061e423534342ebc2525d6d95c8522ebcaf95088668322fa7e6c1dae9d6919fd89a7ac4d4736360240de799626e04751a25bd134ce5cfa99bd4cad7e7a74081ca0c16b7b52f13ed4d8a30249631cddee8b0aa929b55af0b46c60d411dd269e662382fd1636110d771914eb8b140841296de61c6cd334799fc23e268226a7b2e6ecf6238c34633e21f33e85eee626d5f0bd3ec928dee7d5d05424162ac016aa76ab4fa476bbb12743e427c242179dd2d2405087d432c35fa96cb50782f7cda5ceb8ec936f9c7b46fa29c4d114f0861767618764750af751b85424ef6d1bb1b20c96d1a7d791d61d31a5e40786d5bed108d4b10744b4b6e169d3bf1fd086bd1c9476e7b6bb7ecb0d5488dedc30c3184ed7abc11f607c0c02cff6cacd74d8cf4a52cf57dadcbf32a56c6f85b14642bb6dd683e51f0d1817fe80fb02f5fea0990f3b71720adb67aef31722029ab938d93d8d0fda3ad33cc291dc04abf09f82e827d6840387579f575937cce28072ba8e58fecc6de0195621c32d64ecfd712b3bd80ab2a0af00c598a334e878e482bc4a7c58b486ed17b67f5616b10a40efd702b6281af5ee8a1f32d489e777f854c080a181986f22256c1b81859daa4fcb7dc5f9d54ea804e1189ae3e1a3daece9691f5919177c5871f1036efd93b0fb795438374527f298755e3d96ca82fad2b83caa42b55f00593af9cbcfad4856585d78fea9e762145c0a5a9714a128c42aeaa2c7a20909d2dd9def000c0586ddb08dd36f6b0a13fbd5d4c11e0224a845b9622387e35ad81fab0e092cd1d488495dc9dcf21511cb9f4dccd0679e17580834c26f73ac2f54c7c25a136fd2d496f28c3e960fae1de54625a551e6b67e3424b3d9ef2e436c5f02d36e6601d1ce7d790b862262760d7d7993d2f6616c195ac8ea52cb80d93593368f444133152d65828215801e13c134a5c3a75d23f41975fe0b5557099feee066a1760acb8de393cee09a2240ee70e5e77c92b883169bc3568f86ed40781444beecefe061f3e4a8f9b5c0ae6793848b64956b243d15ed68bee1059dd28a343d5541f38d5c58bdfae611b119ddca2bceb203ab3b8071835dc56da3f0823a8087d1536a3e00d2187488afd06efffeafbbe80383383da37352d990772b728d3a37d88a7690114b55af25c90c3f2bdd92c0611a36961e5363f484bcbbc48867234541cb6cdee0b29534e0aed20ad49d80f34bab329e88f0148da76146e8d29368a4474f44f53f74ab54a0d1b5ec9fd1ba1dee3b051fedf3885cf83722d800074ad5e2efbba76cc473b02f234a1f5a4b1c59f490a88ac6ea217a654650aa4144ea9b9256177f877e3deff060ccb99d46c24bc3f7fdea3babf6712a41e0d38e406bbf1be48582319077be5908fd00598c5057dd14722c5debb96b7d74487e286d465816a8f54ca0dbd067d784c2fc42a7977e6468c9f857e24971ef8ddc064eb1aa8b698dcb40655bf2429dd19fa7cbf0875da9afb9094872e20f58aaa8284e79b016acb98d005b7944f0cc5384b9049336a83591a0a9f4524f3aab3896db36d9883af018a03220e72a358649f217f6c355c87a4cd1274882d355ec683be6690c72be61b76bf1545d0219623b712d346fde8619ecf27057532716db253c70d2cf6fe3fd0d68ac09f756708b75fee12faeca77e91e15de2083b87f075a98847d86670d1071ed4ad1365b63b5d38253b964dfa11c22782ea8c36ac5f05a9f05e32c45a987ee5bde6335c49e5108498dbda3478faff27b5f8d9506610d4830d1305341f3018f39727b96c9d32ba1ee88f0e6a6b52cc3c9b1b001e01bc0f04bae83e925820da554d07ea83865c3f5606fa78abc0e385f86d38a78dd78ee1d6987be0593a3e5503586910ff837384128096211ec601073408a27491c7913a8857a8a7269490d29f2ac77679a7b6d8847513926477db7bcb2da54c49a60f092909cf08b7a68734294dc0982e4ae189ebcf492258b080a504212c9a90028b29376effb7931f25381943022e26dcfeb8c922573451e3d23985876c4914c0b8fe5489281cb94d4537414446016eedeeee6e3944c483eb1faf88d245114240820f43f0a0d556d8a08ac915f563b1200b1211b9f6fa3bedadbb6e283001509882fbc0c6918b4395c4137c2e2f112aba5f14baf06d5c5e624594957fbcbef28faa58f57986125596813e302ce33c372c03859c79a0cb8808e47122cd7297759e66c112d0e0c6a7e1a4453fdfe02ed07966e0e249b3f866456a3fd3864d3c28d4306e41b00b7c228eb8f0bd06370984429487714024f00b8c11eeb40c4ca2a88a8ee1255618a9b1b5be67dd68456f9e8ef9665c1df3c1d053049f25493ae6e3a00bff6326401df3f1162ff810f670e107772e51c77cd007fa300e226e04091e2598b8713fe761d2c48bfb7915171addcf9d347fcea3e2812ec8e332dc725d087b103ac27e42a0171b398f50083ae68623a31b200818076783baba81c969ad6e2614e217c8135f1128c298c427f65cf8cee32eef819ff3f8d1853cce037d7c60923b045fc660e33270480842283497d8984ca0a66a2b7a2a96ea320e2e89bafa9687a163562c1bbd8281e1b97148ba45b29192a84e9f7a01cae58d2116ceeedd0029ba6fe81868571408cbe860977ed5cf0c7435e9425f806554332c2adc762f04cdeae715dff00346a1d31fa40a246e777fffc2a6fcb8c9fdf8898e2e6e73c13604f4720618c70c7c4f9041927b2b1efef14d4758121b334348ea7657d5c0ad2ae59c1884c1fc31c7bec6a4ee104208614308e177634d18c29c0bef10840019da6666662caa5f7e8c6a34dd6151e5973e7ef7dcd3342c7d8bb33026b71a512112cf75ac018537dec86dc40c5d34f1d811326caf36abc3a89163921b8de2b3e5da3149d4f72a87bc686c542e15cfebb5f2823af4551ef62cde7c9595148f8ba6e1eb753a75afd7eb958ae1f52ad15377695e257aea4e29bbb3d32a9577d79dbadf21bf93749a94c41a2dd79f552b0cc8ef9e07fdd38d53473303c3cd82b9d941ad30d075a6c741ed28fdb438a72f7df7d32bbdf4e6530ffbd3cc227704b9498cab33a79f8f9d7e4a4c9a3089493775f234b91a67d06051a9aca46878f267d067f1b05ff1b457f1b24f9d9ea6553c6ea5a2a93b4d8ea341490d266f6787befc1decbb9f27da61df7d4e8b855adddd2ce5067cc02c3d4e9d5fb23b24dfec907ed33ea38fcd97bfe540215a4e476e0b1bb76c7dc3861e2e53e3dd9dc85db007009efc1a1ef62baf7b1a5ee95533381613e76e03511349ec2786bd0e138b4444372c18663b407400c7e606f8c5fd8499ead7c02d8ec5eeec742f7fa7f4589cd5ee903fb1d206652acc8d86a9bf3909063ea079b8d5cdfc1a7a9ba448293d95fef4392de9cdc7bcd3574fdee680e480d7f435704b367723bfd6b0e1552a959f41bb2a7ef1ff548d81f9f2799c1e7bd3aa25bd55db9b1b6b43b4f23b34dddcea66d552b24b5add25f5f4735ad58b97ba8cfc54ea5754b097bf03ab8f837accb257bb433eb53834655718903f9f87fc89bd0e28d34bec4f56076ad9aea2e7b7460f4c87c7d56303d18f6f393dab155e31340bd2ae03dd7fa6297c34e3fd450344e368e5883b2a1d998ee61176549ff3547ec5a35f324d6cd52b251aa097108d100e9db5a1594e24a1d4fe4d42a92c2d28336d90245c8f47401c88eaec2748bdea2107a47b95ad815b29bbea293fbe1294055203b7767654fef43bf4eb77f395c42fbd5df5af7c4e8bc5ced363bfe3f4d8b33cf639b57e4905a9f7c781882052ffc11d8eba30aabf6a580608c7c038fa9da7b8dec5f5db16077ef1d7a2d4d3d7677e41bde94b9ffa8f8f6a5637384a70e6ab50ec79b8932bb8a08cd64a54c26277d45fb138d4d6c751b1ab78fa94474fcf63fee97530d9eea75d451ef2abd5a1647915ed0d47d3341d73d0e171f5f824f9c1e1a4232496943835853e2e2380b7f9ffaa2a003d3d3dfc7a4fbe00bcf901f0561e005ee96b78a85f792aa6a7e9f7b8b5d9403b0363e2685083a8396019550d5886f441803db8ae3557036c65bfd9dc34ecb999f134dd148032d342249a7085c323f8c555f433d2c64653d2d86f627914a86694be658548b454905bc5f8188b8df171566c2c591bac8d8a97f250272fa324a11993c717c67b3a989bd233077b9ae53f0512e91868e4c665e6fbdbd870430e2eb3bd1f419d21a222a3239d92edbc483d6ee9803e98030d11c19e9bacb39ca6a728f86fa62a4c4eaebfc949c7c0d6ea615c55f533fcaae1cd5f79f26978f467785886f2b64779dcda60dc1dc667aedbb88b9b884c434239f277cc97cf2f4d38a79c96909c303033935a1d4a2f4b3a74dc724ff5ee5608f5342d2703a08cb43a268eebbcc46b4ace52f7e25e5372963ad3a9f4dcc714caee34df6816d5c2a8f136b760d7adda761dc55e7e4e6b7aa9971eea4f5ef726aff45e70168eb9ebac30303ff53be6a7ba9fdf76877c94c529bd6c993c6e6dd583905bdd945a25aff3b81b373c36eee251c854048560b374458cf16d5ca6c6ab62acaa67c1dc95ff66f805008f7e0d2ffb95579f8677fa19def62a4f7b168ffb158fc4e2716b25c618637ca53ac6481f7b1e59363fcbec8adf631a238c372b198559fc48ed8dcf30e63b80c7b87587cae834bdfc1410954701bc2b7667a73efd9dd3a33c6ea9d889fd0efa184e7dfa6d7764279abe5bf68dfa9cd6a6d22a2a2958c5cacf9223a4a8b008a3ff0c6111493e4362c4f82aa91e6a72633fcae2981ecb2c4ee9a70ea5a75f3d1d94ec28d9553ffdfaf47530953e7b1ed5d2cfbe647feb5a9d077776b8dfd9647c8c6646242ec648735a4897d3b1e3c146a1a8fe3a6c62a0a90efd21121d63e3320110c07f550580ff9ec67facd57f30aaff66667c2c004f7e00bcf900f056be86977a1adeca5b4595b78a33bc551480c7ad0078dc0280c7ad1a1eb7561eb76878dc9ae1714be5716b53b9b79d8181819cf75af1d3779915f3904f7f3e467fda15632fed8a9f5bf2b9253df945fdc82fd54399f94c03ca4c1b43bb401969edf56718132d1fe0bacb9c58fefbaaa298895f2c74695a8c0a9f03129fe356fd2c9e921ab8a5b23b3bf2577e673e8bc7ad8d860351e1c355db1bcc522ba5cae3ac58d4abd81d3385b2dcf294c43779ab56f556b1e4ad62b7b343fa1dedb7dfc97e077bc9797ca767ae43e32efe100aa1394d4941d836861b070988320419c4922831f025c0f08201b79fb64a7f61081883c50080a1841b7f7a0f524bc010220854e4400788c9912b86d0bd85e6babb6377f700b6d0c590d41680b080db94c6cb5e18a28447e0e8074eb45064005af0c1155b2072802c8471fbb9d301122f9280048991d9e36a1684ae9ef82ee31fe92aa142178450cef98fd939a7b4f0b5a8f1db53f10bc31f1b904145111fa8284205ec8681c160b0f830375a06070c2ff8e226b49fdc8efeea1858ff7e3040b032ee07af3fcf34ab87fac1c060944e51a7853e301d336937e523cdaa2eb30076e97ed66d1898c487a86e5b82f18521ee576160364412496cdb16050c32548a6e8c44514a8c441dc3373ae998f945373e1466969721d48926af07d39fe6349d4cef3edf6432991e7ad3e4c529474cea6e3199b439654b29a5849e3c52336e8333468d71f48d1d2d31c7d8a3e20318da2149040ada218ccb413b4021810b813bdb9b36121dd568a459374986a0d3326d266fc5c3f4a73f599c1c9fa1293a2d9375a2d62655585654585854dc003e3bec782b3ecb8a4a7c090d4526b149c7c02a3a4666ed5d7f9685fa45a24804a563eac78f45f14934ea181523cd8a2a36361122ad3c46e9980f46a2e8a46378491936b8f16351c7d0902275a0b8f1254fc71029c3c98d2f8db40c017e384887891b3f1af984421df34523377e8c404d3aa6b57063731588408c231681cb01b9b69149b35efc82455163918d44cd8a5f81fa02c206575e2e420e98dc8e3d2e93fad3bb9d298baaa13a45d3f6d5fb52a54814bf70a30b3551a430eae810cdfb41212e126144a89f1fc523ef7199d853d4ac48abcf4ccf8d918805f32cd55727b0c94ca2c2187b488861749522ce0180a40c24dcd233894a9bd564d4396d94241a6ddb6817f8249a6a02db7e15ca46a1a8fc3a6c6288a129b2d18e81cf27265498516ccaf6a237a55b46ca81b0b7801cedf759c6d4213be49e0b4d2e9eca0fe33511b9d099d9b4e3844f45eb43daba6245fd2a7f0f5a10bed79cb8eeee2b1a4ad7bf74fdcb09122f129d150fd8920f5bd2f2905ad152777177f7ee9cf606629b9ffaee49af3dd69033f24d69ad2ad5fffcac2c0d3bc3aa2c8b5db12a366551dc66a9fd62128752c4d8185154ccc7e097f9de0556c0fce66a4846aa7cecbbc00f38e95c9deb63300ebeada3a3a3dab4d67115b9cb65b29fef4c5a26a3f355f45a769749273667e6505c0abfcce8844f55349fd25a55aaa11f9a2b1e3a94a448d18187ecebe354bbe251fafad5ea50bff4dcc2c9be5a76ea2e50c98a4ee8e383c3d1110ded2aa7a28fcd27f1ebfde532954e157dacdeb922545d4ad4893cd16793a24901aa9fbbee74f9eb5dc86572725a3ee446137bc59f982385c01c97281dd6dd4a577c018b9fedc29f4ea4d02e7c29852228298a5320ed800a32e82efc864f217c61c1897f95ca7fb02ef604cce2c2aff055f05f08f7d435d96a7c8d1629638c3146b7d5bdb071cb177d7845a8b64ffb683a9793e12ceb55a60a45b5cee446eb4d3a5bc58db6e7c628ead757e097f89fbb17ac8068c42f463e1fa2b456952a7eac78f47abd5e2ff8822fe8822ee872411774f5286277c945303ea546df7f5c66f3b822548d8ea4b83e951bddf8fe93d38aef2f27621cf0a34fd6a42cb67899d425e0f257bacc2280cb2d32972570f95300bcf0a10dc4ac502155b91f4ba573f04b3f0dac001ded0281db4e603e08e48043b33a8ccaf7e3a31d6c27f8ccd31e421b97e9d606bf87cbacfae90d2eb33d8ff9dbc6aded75c09aa7ccec57b567aee6663e4df7775bce05353e6b96c453e3cff0b430eec29e036384c1c62d1b7492a740c02ff303ac80d9835f5ae0e2a422aa74899a657e39e9dcb6b109557e7cee067be6e62caa0d7fda074442df0daa4b7feae08bc4c4dc094a5f6bb22c2b79f06ef439161e54cac920ffe33e485f9abadbc6bde46ab8971ce578a39d4926b64acf7dc9cb9ea98d9c17b3e47d2419d3535aa24490abb6cacb795bc9e3db5cc9a32fb90b4c93b763fa1fbe65b24ae8d328d574a4aeeb4a1d4d07f960743968e7753fd69d0f733968c775b78c542ad9fad4f356f4e1a56ff256d4726b125149a4eac15b7ae66a4aa58e560f05f06e52d420f7ebfe07e9d2a730769de77da9cbd41e513f7a276b94ce87a46dce8dfb9c4be23c582a957e06be25bbaaefa3f4f44b96e6dccecbbc8f74b3afa5ac7bae23090115b07d469a3f67cb66a3461ffb2ca394e7d65c0b68631886d14cfbcdeb6193db6bbf6ddbf6ed91ec47e5f6f4b7a61a9661148bd09bb03451bf0a3fd3bc1eb4e748a4ed699ad348af699af699976d9bb679f3b52cba0cfadb637cf71835961f44969292b68da6495bf4b6e86d9a9669defc6cdac0eccb124bd1822d2e733494ee8cf1878dc444fdeafdea1ad8b8c571e3e7965a6be58ea91608840f60d83086214b7ed75f1f3e842b27d841edc29fa1d26866ef9300d09861d44ff2dce1c2e9c385405cf8f20b2f381ca5975fb8f065189f2ce34208a13ce25b7a9ae68e36abfbee99ab96f9a5e4759e2c22792f9947b179f9345dd80f4ea16391847d8cb1a9fc7e3cbbbbbb24dd40a5505d7e19a374f7e9ee4e630544757777a79c915c437edc87c04489ada0da60b1e49c383f437e86fcc8213f105a2831297406c30c6703397edf68567f7330516241541b2c969cb3486d794cd6506134a66acd1c32e4a9f0ba65664108210ff9c0cc4c6bdbcfbb50c6eeee66663885c329d88b6e9fc80f9f748c6674e4042edcfeded95c8687ba5db00802ad34210a61489105161c01a8d559b8fd70c80593ecb0a1c70d3818d1ddfe1c707a3219643471c412b05042143badc6c2ed873e5dc4d06be332aa1984e0840f32e002085c14f145159cd8a89380065c384254041451106935942aec70fb535d1871ddbb28c2f54ff9ab8a5c6f727d891116d76b5cff8cab71e6071ce06e4fb99aad5d59b8da6b8f7135daab5990c4040a1f23ae664da17eeda2f1b58b8b1bdcec633eff76754f1b69564fc778cfd73d2ed831bce4ce3f15d19b2ab0e01ee09e8cb4ebbada48cbb07474747878e978d465544fa6b8019750ff55ded7e81e77f94fd5aee76ef805fe947186dbe7d274a5a2d2991c4c99fda4238308b3d8273bb8fe2baa2f3401aa40e1fa2f7972c4f5255b2cb9fea9ca60a8900104514ed0220bd7fff4042cbae0a9421033f0b1c1154e53a698acd00206d7bffa0fa08f8e8e8e105240c2147a4080293ce1761554d00113a83086036e3fcc8d9922266e3f0c3459c4b0248b2b02a5273de93d8c647f3ce67da99e1d244dd1252198911098171031d45292735d0c2f8ca210054c4922a5c5dd808f14e9175086d0d38a1ec451f238425c4788bf0e42ac0e967577cf960d3b76777777777777777737eb36adf53600fec610d879b5fcc723e989cca30f5882ced06a87f3125c2db7392d39468d1d2b84220a20217480a400a28a8b80cb52fc002484abb91b3b45a090c265e4b214431cb93397a56081171772352c482adff89554c3b253e76f947a1951b3a8f651988e8199f6d96b9a8548643e2c38a87c673d72e1062317479783822862235c0e0aa227081e1e299783788ce0f71097837e20ba1b73352c41a83f2c42a81d638c248ef428277db3624b9526d2a3bc1e509f3a9d50cf9771725aa8cdbdd3a35028d4bb77b2288f19251fe5260e85c2ec9fecc7cffd68f1df2857fa4eed5742a1ac10d5c94f5e7a77f8305e8f66c1efbc1f39dc388253322854783be7d4a41b4418638c48d85882002184373fbd1ee66794e2e4b4e6cf393d6aa79cdf3f6547e8b0211236962758802a6bbdb3e3f1878ad53b4a6aff07a75c9dcbdfbd233d060e831a37d932f5f963aa107dce6b287dd4516ca02693b84c0ba9e46f9554495fb99a2ec3747035ee5a436921975b0b95a4e9353b6d1bf18bfc2f69ff43a57dd434cad9d0f7f1d977bfa5b62e7bd69c740c6c8752b26ca6591286939466c9cf843a26fb5a9d4ac86596a6593286ee7ca31ebc3d8d44fb344bd21a236d847b1a8f9ecb3f791f9cd271b4b38eb1a67cd3481907bf7a3b5649562ba590727c212703bcd0c55b77aa1a3a721e52ae063e8679def2ea6ce8181b9a158465c339aa23a7267b8c6294d42c9ac1dc904132ae660544cdbefe0fdfaa34cb32fa3f5493629452cc22a996db853e54428912b51f6462db7e68239b66c9a79e0e57e9a6597247b3e46b46a8f0834023a2ee39e32844415c17be4812be5ca68d5e0d45b6942b21932618b64246d5befbd2c31ecbb7d3384abaf03564da406b1b5dc9c4069781456017f99409350a57be9c3636cb5d9707b0f9ea071b0b14f5f44392188954ff18fd795a4895f95b300e7bac846118a9a4037b9b8e213df6ac964f615fa3e4751e0b106a3fa907a55a2d95baa7e94af24a24af6439afeb3a52b6518d66148b62e31652c774cff53beeffa32ddfacac947d66ca32ec4d3ab2b761b5dcecb357d53039516357f27a28fdc9642a3ddf52e799be542a95bef34cb6546ba97adb97ba96ccfae0a122c4abbdd3ee74b2379ae5d9534a297dadebbad76c62d8a1f6f3cdec0c4cb3b217549c8ef92a95e15643b9fef49be59bd3f4e6b1b8ce7e39b7abffd1afcd0252baf5551d435594dea0522f05f1664fe399c75c4d97a99ae5b59a6a35d92fe7522dd3bc4cfbeeb3a759f69da63957ed29ad9dc79d5722715e7ceac9d7bcf9417ad33c162683ca37b6a4a669b69d66aa4d0a952f8d0d022f666be6fbc7d0c3a35bd45293c010c227c61889a811c2d81281d8b845c6e99204a107d98396b78d3d2716484229a58e9ccbf283dad3d3d3237b7a5c060af540a1d842405000c1308ebe125af882403dafe832a316be809a151f28c31cbe80200c0baaff0785342c3e74726351361dcb5c28d6ae919984854a3976113fb9b40ab8424695cfa4f61f615d7ff0438f9411ca28a594524a29e54b29a59452fe46a4c69792155d1b7bcfc62d4d2241787d90f2030fcd9ec4d5506fa31d57b36df0352f480e38361c0e436af7ff503d89ab71da1d391bbe9a5df9fb808fbdd38729cacde21e7a37fc889a116a0fed699cfbf8244f09c4b00dab89d8933a12b7bde17ed8ff505d08316869586bcb30a83423d4ef558c83bbc4a7710bd231dc43b5d14137abe3e3a11b1f6b56fc21755a176908cfc2825abd4212508d2c4c54edfd5534b6af1db33d7d871be6718c31ca896d94522a3fa3128b33ca0805226e7958f9c57fe399a11fc29dad552d25eebf753710d85ecbc42a24e526448d4f234aaec6a393e4fba08744ac21c718b327d48e0fbdb8f1a76c58d3835fa2a5ee4340a5c66c6c68372566a00362f4199eeaac68a3592c3d60161711a1b5f4fc5b89bffad9d366d12f792dcdea2ccb5d2867b722f59bb9f4b19ee9984f0543c77c2dac948e8ef9de86d5f229558d2fb124a9b159b4b36761a26696bfa587a66dffe3e692b8efbc1eba37d5da3d9b3c9c9c56f79cb7655efdaeebbacfbc6a3baf54ea4adef61dbd9b8d6669ffcdd2345bbabd0aa27afb904c626337a2c666ad2484b03b669994592633299dbaa4ee8f49e9ee446436a9fb8b362b2641962833b3fd7ecc19896851c3b0672f3e36b99ac85e8c1212993c3dd6a88d3f2736d92516b68da30c3957868d9b44a4ee602b61fd3ee2cbd8ed4529175e788388f0dc5981e7d8ac1e6274f9ca501e3619bfad7fdbd58e8649302149ab3fa7455ffcd8634c05c7550ebb9aef91d87e30174e272aff86c17c9f35a1be2abf7bdc4fe56f2f735239462c6cd25bed9038bc9964e0a01e78563c608b37f7be580374999b81feb7b10f6a5734a85c7f95eb3954ae0fecfb6790be985de5b029e8ebc39f837ae0b92d3d6e454fe5bac7413c3071dbc341e5320f47b238e2b3c50cacde669cd31930148228ee53c6999816430c0fe37edc8407088f1f9b8bfafd6d8b035b5d93a8f3f200016ddda27e7f3b603bd02c7f52bf87413290721dca2d8e1cd998794ba2766f496c504a191fb9840c29654b099580b1bf4546b99f8d6bc338e2df687da83007d8c32ea5e46e6cdc4512a17e6c74256d97f852dec0ddddddfd7377779712368703185b894add057e6c164738893812e39c73da2b3d10fb4ae82ea594524a96ceecde438cee8246b8f041ebdc7e1374412358b5047fcc9406b79f865fb81bd0609f136b2ee8eaee8ed332477fcb91ddfdfb31310cf3c9d0c5d5c81d8f25370939960b1ac1aae0f22861ec3aebc20c4ce422865df8629b320677bb20e54a18ae94d20b4c5c295f35060f577e0df93f8613aefc16f92cf936964cb9c1954f608ea13a37be17418a0fa46c81d585235c7f1b1d63002e68e1faeb468e3ca042054367768004d5ddcd5588628816af258e70448906a351dddddddde2c20e37c618b9b0831b8f501355a96d0c2831c6183796dbdd51acb85d451839b8fd35c218e33691184e085ecb6b35108192253031c4c40964304184153f563c312f07bd400b318042a3d4291d82e6970d7aa3c2a062f24d58555c7632d32281c93de724a56ac4282464ca652570c763740cc354ef0ef3b5a654355295dbac784112555553b56e242b8e2c511ba6a6b2cb412f30ba5f7d69036ef966ea3ee109eef2a1c4d0ac48faf83948ec9d44f2e722fe54be72f9d46556debf237b2db37607c5e9a79652cb51728ad5acf8d472bbd08862c3fc994926d07cddd8fd6a0f9358fc0ec44823043c51b8089b12e8c66719e17e366e9445d13131be6b01184b40b14412a60fd9123d58c289ceb5849325ac30bdc8123758620753061501847bba1c54c49129dc551de17fa4eba369d89c73625b93342c8b51fba861d2690e143b220b29b7cddddddddd5d92f829a9a6543c4034eb558ca33b4dc31e661a764403bd98c5818bf223e3684971685651d633097420d0ad093c870966e0ebe3073f716b027e7273d430f1c4df3d13f803f132a6dc1ffce42261277e2e0735c1240808515cda311ce4840c6e3fc9074840ce91c067b63af1f9391eb08c16b43ad1ae78b4a2c5515203b77410c24f5a56879fb4381ef0063eb9515020a33d41dc58011e1f469bf18b4bbed04b41df8802786dc62cef082d10bfd1527ef195bcd149a619bab8f27ef47af4f83e61e3962fe6afd8ebf68f1647be3fcefcfe9c567c9aaeaa2f6a737787047b9a86b0b6dc7bd87d45fca2f66ff0f9f9fb255ce59893099dcb413f38ba08b81cd4029d5b05134cdc2d929c9312b300df48c22cc077c28f8c83af84dc0cf363bf27493247cb8a86befd2918c19773fb732e30391afa7dcc8f5bf4a494b6e5ba60db2209729c2f49927a2d97b90b10407e3442ef83f793c1f9b196684933a83e3c8a3ea23e89412e78127f6e6cecc4610d46421c0235220820364c8c19c601f92572c644ea07758ce6910e4f536c4a8a4de952eab88b3f4db7d71042487beac745416e840026f69783b9b031b6d6b6518ba37df6d18bbfd1e6f888da0f213757f3342e9fe34f6b6b6c88052a33294662950f5397213d0f35c9db7e73480aa26235c85841f81e5cc9580eaef46eef90c027d9c860af4db3ba052d8dbb48a9dc823f8db029654e29d3c8657a48574f0f1242551c995298e8017a35b952cbe8075d57be04c095d80e9159c438207cf93848d5ede131f1068c7b12aa9413eb3941bc58cf14f56a4ed8e40c885cede7d6da9ce4ee1886711a86056c86fb7e183ac6c784199781847213c3beedb39965daffd436fbd559b91ab845dda59f33a27ed8678f719078351ac30ab394d444b5a1a12d92a57072b1c9a794d2c9ccb363ae67a665c2d0ac9e747e46d32c8ae345853a9687dad2ac6edbb62de3948a36101c6ef63ca357efb41f0e37631f347b6cdbe006d3311f443dea63c7acfc86f2603c960b2aea3f960d0bb362519bc549828a38b449c5e18654cc7cd307514fdf0f7f5c86e5a18e4ad5cd7855a3502baff2284f05857a9a5641a1502b2baff29b8aca33578302c22b302a96c5d5cc40d920f1aa5ee32e7055cf5c05ea6b4fd333bc7855de2a87e6a3bef67c7970cf627be0c837a2aafc7f33352b968584fab1941c3a26f59f319b84aa8ae576995ffa94877a7e4affe499de44e355957d995757fe04f0594248a71529fd66750c7fb3fa5b9a156f667d501baff4b81754f7688f4185fb419eeece7121b0b3141128827a40e4ceb81cd4039f3bc4382011fc327ffe6a75a33ddf1a725a5fcb651a7fdb9e5a9c557cd6f638f41f47e59fd3fa1cd2675f03b7aabb5077994fb29faafe37235bdc22ade2dbe02e33da13d4c0ad1b8b93d37a6eade46b9f337fc55377502526406275b0df9e5b1ca0affd8eec4b2fbf445fb338b0453f16bae9ec8a87b6ca6195c477ee06b32b1edb8a076c610f5bd86fbf45b879d0c52ff34f40d4c813758eee34baf3062fbaf37b687e1375d19c3a3c5ccf9dee73e79c73cebfc1657aa875b21fe431d94fc55bfca2c6873dd0a7fe2cfab99308723dd4ac993d7dd31dbad9d9d9befb1ded4bf5735a248ff43bb66a714ad67970ba4df3364fca2a3ee9735aaeb38a768581edb5dfb1bdf6f1358ba324da1dda93ac5113b9cb7c9cc92dad87dc65421b691c9a39506e43a69f7e28b35a44552ce44454d2733f37bb72bb727f1c7eb73ddca5df5bf177432bfe8eb7dff1dbf69dc5b130ee221f476e2c3f42a5fc7282ca2fadbd73246dc543e317ff3badc1f41383ad752ef49750e6414e9352f9ae6e76fae19b2ebcabb99aa4d77eda1dd9e6efc15b73b3b3a33dc9ee64bf591a77c1324ff36056726625ed0a03da67bf43fbece567164789b43bb2df325bdd05d32c7b2bc3fc0895f9e5a3d27bf0ac0ffa34e7e65ccc3d08e3d978ccae68c03e8c522ca398fd41ba48e2bb4ecb2d77033e0f0ae916402f52aa1a3f0c816284280b3e37d8228b217a8a2c70441162c46009145d10c1822aa8f04388a8e002108a24a10c2244303f0840074d80020c1f984c49c2902953aa1af10a58537f3988081a5ced72101145dcaf3ee645aacf68ad29558dcc4b55221c052aeca08521f8d002231d2190a0e2464b8920028dcb413f42dc2fdcaf3e10a74c358572872edba3c738879873aaf888b2dd87fcc820a67ea4a4942a538a6560bc064c84524e96b3a31260be8565630b5188a8d9e520d7159054bf1ce432faa1cacb41ae2d8c08726531657435894db8ac2005b998b8a4cb41ae244760684663f3a0dd98655996edc063059122b2223940486109a39f22aa90040c3c882aa254f18508838a30c2d6c510567cc1054f784104125268459e1b838878ddf831d8c4bfb1218b7b8beae5a01740e1962e07bd40089b81165a68e14343e509978eca132a4fa868a1a28591a0223b347810540448e50995277e08d035f5c6ceee1dd63d474f72eb4511ddd373457a2e7cec87f05cae452a070d11da34127333f0eb96345795e9e12e93ee50e5c3473d7f8f137cd393381a506fb25ff728d44fbe9cf7c1cb7de7cdd0177bad48fdf8451343c774dce4ef574b5c4d476935d9af6a34da53ae46cb9a4483797db5adb90bc8487a6e867abb47799dc5bc0ffbd205ea4599ecf7e39a50dcd334c65de0c43d8d97bae737793e3afb2375b1af8ff18d5c0ef8980ca41fa9fb73ce39a7e7a39ffbed9dcb417aeeb78ec432459defa3dff45fffe97dce7932a13c78bbae7be76aba92f7b7fba71dd33d771e0b126af74e39af9fe6e8e71ee5f1e5bc550eee4d6f7aee3793574b8f7a93f7a1ac0ccc2fdd33bf94def427af76a66a573902a4e1a8f48db6543d2b15cd08000000008314002028100c0744029148281c12d561ee14000d829e4480541749d328c8619c52c8104308001010000010919981280840c9ee358049f51a9678b535a90846d0f620b1b7c2c2e1117be99c7f0d56570e2913e9cda1c866d0d5025495f99510d0af8f0d78942896c572244b096aca164a2c6780993f265ad0171c330a393afda9097fd5c20b3c21a32d04ff96d968d0f24ed8fcae811ae6fbdb43557c0f6cda5bea1336df2bfd0a029b4e94ace2f3feb735c00b3afd627c82804711b10489a3b3ac3341eb42b9fddc5487e98441dfac135669b7600e316c1f5c2eeb288c044ea0583dbac55f3f9c1b8f988eaf27e05451129e01c34e74ff45b3f7aa37051b352f5330af64ce5196e047c187bbb054d76c685169d936b3bdc57b2d017159b3724ca968cabbb36282308c013025742097e4645b647152ed800366c6e80474878d9e9a6ff6e421a8d5e9060381327d62c1a6ae74f32381ad6e7e2581868e2458c8bf8104f2add11fd77006e2ce296be7a57565174e13d4428ea3f608d09c7c508be3e53bc802327b241ef5c9746fc2d992d596d81137c1a289ab42b95383796ffb7c4d903d731a2a4cf3717166c74652f80b32afa6a31779587d0a2301183cb6f119072f30d946cee847df9523a11081162f88779f0587515fd9624722e7d0d680e5cbbe102d969736b602928c4a1f6b00b62a82d897b51ac8d85f96fe6ad7332c00523f567069d2bc0266f31e5f11274470e993c1d22d4166bdf6d51ab246ca686c9999c993a5974ef5e40d1d1efa103e2dff3ad88113ede0ebf1efc75b0952749d71d5788ac99ec8d3812a23eb90eb21001d2685009e5de325d1fba5dcf7b88a4eccb339089e85a11a2e8b2484d2a7797cd78f569c7bac40cbb11baaefc80b14e03cff629f2b18996d8999823a78b2b504977cb3983f8b80090ae2a6fc66c4692c08e42f73064a96e317b8b83a390801df20781fd592a30f0982123c91b7d74676f42d8425f82ba59c40ab72c9fb034f6ac6fe4bffcdae0c7dbfda99ffb123a4edbed9a1efdfbb8c290e930e2299c3a8906fbcab928b3a8ef83f772c1d4651387ea14f6dca4df7fce5ec05cd6bf4bb6b475f058982868462f6ef5412a5a454d6108b5e581cf13753f282718105f25f03167259fecdf1fbd922e4beb595d4787fec69e48511271d182e6558fcab5d9cf66bd6322a8b22bfd7fd8323f2a7cedf9a53830cbb0832e0ea2971707e2a3246badc7d0fff733fbaeeb4bd6d934a3c67cc21d3e4f851a2f5813c2c889cd0311b45ff476af0ebbf0b9ad7bf9728b33428013619dca48c44a44386ea78a7cccaa5a10282ea642dc77ebdb349a635097d1ea1e8f581046268b5ab0bc18239db8061cb38aaf71a11e5b3b069b6a72d27aad41de3f237480e665d94f1ec190ae35fcdd85481e5c33085ac48e409fca001f18b5cef95a03157d0bae5b948b1be365951596d25acb9064c2e193a209908d2a3184cff614597d69dc3be9e1f748d603697deff1eae977fceedb5a0bfbe2da0a5ccc8ef346bc2e4cf19f2265eba511dc87bde5deca277b77d6910bade03b8e92b3a1ab67a6169076e9374d70c79d2de4631f7f8696bb171e3daec48782f355f669386b4ca667455f9169d02e6cc7bff292894dffd163ea2210b52bd04722a66a67128045158ec994ffa6ff9da0f280423ad3d7017f730c51ea169f4e365fa1b7fe8b74fdc7a0960429cf9f9a0bc9168106789d6668804b85b4ce2a8a900c7e9e112351aecd1f2e3bce05011aad5d437838d14b68847e2adce4f82ee7d4ac0b5a09bee704dd41e214504ba5bb51d864347c795d4b10862f122454018bda7858e21dc0c013cd39dbf5b71419d16f52f3adcbbbc2aa5dcf614e73336c7230769133e5dcc0fc4fb35544aec0b84c0a0a7eb5af1eddb93426d0fc52c64268e8119ee4e65e446bdf6766e7b12985d96f627ffcfc3305435287d39ef10e4cc088a9e22462162ffe6ce93dd1c0442e5bea8f340cc791cacc6504fd900dcba499301ebd2c6674969a41305c89b51ce484d9e0722352b951ab0e741a30cb43a30de1a0f9aede8978b5fada682f2e540fa88ddb7c68d8bcec378387ea9f9a01594f39fbfa3feb060a382dea9da6eadcaf7c39fd055d22a5cefa28a6d61b310d5996d6487ad630f5646b80a430b6e6a636e89c87c07de33086fa3b1f25c3d55a357c9f0df02eb02b1545b1823f65565f302dc75e0421d58f226feb880175fd095ddb3356070116585cce8f4a9a7f7900fbb8e2fe1c78d5d43a733b8c8ae9e5096b647d123d83f72d2e01a422d627a6253fd4179b3ff512b387177e4dadcaee3d0624769d944028c06045e3ef5c50afed0eb1b9112f99a4101176fa3da1301bba9ae25a8da017094e951854c7b551bad143d857d80a96c8a1714c3372f3995683ceabb67656f08134843570409b541cdad36fbf13193932f9622b87d64b1772f87b5f9eda80f829710cb9e72d72c5b6938b3a11831f90e255797586d9ad0ae58feedafd934d6db3a483b7e52e007ada8faa84f1b03054568d4bffbba06071d151dcb664b5dc4ab69254b871d6c14011aa6a6e9aec585d31608857ee2bffd72331bd60a80d62bf20c239a04f8a1504ce62ae582aa804c63702d3130b0f0e1ab528c01f90613148bfe15786f127f24df2feb6aa16519dcfc67b447290eeb3071177570b0b147949326807892d0d51cf861b43686e60753b18c2df9460852bd3500fe0e126cdec0c6996fa24acfb25bf8b8133076bb668c1b0c19d74ee341955f83fd33f90e0e5b5dcfab214b325179ea17a2799d8e637adf3b123a01f0fc1ef9261640079c5def93f7fc65e8635b7ddbbdc073edaaadeb53e3fa2638ed23b4da1823474c10410e78271a0e0b895f91b64add6de23583e12c5e3d8f3d569057898086a6cd9636270e552d028618c54832d8f1320ade8f34a9d0bbef7457c784332a63e0ff832a65e4a4fa4be885f585d2b4cb35c54a216da31ba877c66e2fde3d4636ba4290addd9feeca0edabdb36da4b7932f9049b8ad32c0e61fe37322fe3335236fae6db062bc4272f48d563a4f604423cbaf7f8d87dc14cec2ec8466704bd778c77f0ab63f8aeab37cba936f24cebe547e6e422aa637baf1d3d684300e278ff8d292ac4a841f65c67c21b00146a56b5576545750fbd9652a56d9dd0b782be085af258b263fad8d512a71d342e94ed758962e7446b8a8e07d30fb8122bc34a01a4b7dddc29612241dbbca7731e9f1ceba55c2b64581fb78788406d400288a73816090ac56fce449cf4039a760e2ed9d444d4d275769b46c72d60d866533e892483a1bab3eca4eef95e3d1084b6cb4ec9cbedbd63e0a92cd729c1d46bcfdb673454c9927211221dfce8093e4beca29c41c01c199adec5da53775f4b83bd4c04484eca0fdd6e2d3000aa26df6cc551f73e28d01caf6ed004ec22544607e996471417168192cd67a69fb0614e4da81f19c857eb0c5847fb5f14ba5bf97f2feab89197ec127c49a35ea0a423eaa45003a4697b71df6980dfb68636b3f573d02f309fca814d6f8ed805619051f948a137838a3c4c345f31900494156f6f06221acdc7df6d728a6a78829329ccc98ff4b2c972b619ad5392b2f17ae29a1492581d77e376241352d9ab468d1dcd5684128496e44a006d2096d9c62e665c29c4ca3912f575de27bf7f8d89337028685244f329a79b98538918b58c2dff303a43838e8114843b1ab69338d26f8d7cf4bf400c2989ad6b10083cb66638b6cc66234e5053a27451def916b86249a4d9cf6682a1575f78f5ec71342bc6fb49ece56290f16453980c4f2de0137e44b12cc04ab972f03983bde150b6c02f8a81dcf73077e5040a069aec26cdb91c7665cd8bfea5ef209e66f19b314fb830cfca75887e3e248a2677eaaa536018e3228fa36c9b48da1d57363e820baae34c0bc0538a4627cbd192df06a10838e5cafdee0b2b4c37f12ade13d28a0ec3ad586da6188ff4ad71eb86a26e108993ac681bbee68484cad80a6c750986869e551913b3030666f4aaaf5a8f4db2bd7fc08bfb6602465b41c7a9b05b4586b501b94b2367d503f31e5f58406407370361c3aee0a7de1ece75a5e6edc105535bfc696526566bb23d354c9682ce93fd40674148afcf039e7581b1694a09122fd4d6c5498af250411f5421c9f91c46e67f7c60056521a35a914137ca7b01b1db596c932da332986a72529c176fccbc14a2390f8a28f05ee5e33b448b76028648bdc1e42560678429daaaf4835d904822a438f9446ce1fc2f0cebb2cb3595046368209ed1da2e8df5c1ee608add1e9ce8f07e269c8f5d5660049dc01d37fadcb54c7867ac578013b8d76a246f95ea8a02634242dc5322d1a3353c71bbac0841f0d05a6eedb626d75a1037f8255d17174d591b00eafa2b3ab65bb2efbc15645e40bec7edc69bfe144f9874fbec48b1b862be00db7a5b1e8e9eb14bb2882e5edebd72e7d483c5e2a41ea124947fdfb15af3355f3d2cf08bf58315babf9f8e8e4de72718b72df8440b973dfb4acf21ca1aa9eca8f75120f97d431fbdcdc98341d790ecd7d15ad1fc552221c1977861ce918b72422f7bb51719538c701464794d317ccad2bae5d1aab0746724da81afbd6a8ae741e37ae1a07c715f5a7b250efb6534088a25d07b1af62936fb4247160bce26cf0220bf35f28a345422a5ea706d16a4dc4d106072ce2d48131821cb36e08dd4eec41a25d679c8cc0f90a46aad8d105e3d6dfed513484ad3ec974dc143cb6c3a475608bca480a793676d0472ac4d92390344d7a0a2418d1cbff3434a00dc12e1c54192ae1c0d674b7a8f0c30c0eea2b6fd47b452983d038f217874a0b207b0490283a73030f046ce043b91a2d8a1b088d3fb7f8f208a9de83c6b752e737683c3b870e3259fd306bf6241b2844f543987654f507d3c68178283364579390b26298640d68253c98c0f83224d7d80bd53168f1d5120c09ffeed527a4d869b5fc6b60cbb19d9d792de75dc10d58aa07a127d1228ba0f1977623838d1f661d97ec146e1408be3657d5f7e5f238191ec7d3d4c7cc6298e1c20b9d549c20513a2197b30b8fbefdeec10e274ac9f226347bad245a5331ef027977a6e64ab38e1e0f607fd92e748468b872b8d1f38f3eec0c1f8d9b2a00fac092f413fae199a737f90f882fa935bfd7d7f16e245321ce0d98d43757580c5cd3d6b076f067c711c904500399d01108d945e2f5bc6572b201143af4267efc994d87dd5b04147ba240cc6e7ac4e62a6aa68605fc55f03cf49ed3869369717e09d6f990f127e130ead9baef47411db8e33d0bfc0d47fabc803b0b1871e780cb29349171503631184b3647e06172c95279543b97508794c5b8a3193f36e9ed16e40f72bfad7aa04c91dfe294057eb3cf26e606906fa5565b05c57f834af8f7e353c3e2a7bb1fbff65cf5153e150a0aed221fa393700e3a9dbeded3d982275430edb94b2c595570c14b20de50246f3a9175e09f0b57ce4bbdf9738e5fdf57e2428d7f7c339331dd0303b47f57e98999b2c3c4d5339595436a93bd40372e059d31c0df7162eedaf7148ea95ed236456fc05ac6b54cd81dad0e56847ea46c7f79f817832320f8da62853e708be71d0148d9a7f8609479862fd2c383d3d36a904062eb4bbbcb4e4e2d4b7c750bafb44ad225ebd2e3d08a175881cfa7f0e3ab37ffa96c24e7fdb21e7dae3c20db2e19cf1d0df43a5bf8a0b9364a2e9063b06399751473371bfa0c14fd81600f094d199c6d55f0f6db4ebcca2456d6c467f1867f9479bc817cc2939d03dee4370a36f61b8be4a6d2a2ffeb6507590e8ee373e4c8407ea4c74537177a9febe6d6d07c5cc650037d2511eaed3c80f033d477da102141d4bb62d4458545e499cda99807d1237a2f7d4605eaafe7224f9d5f45d29d0f01886c0e1ebbaa0e91540bf0eb9e1d2e9196f4d44aa43b052f276c0b5cbc1b75e5ee12e2810013391ceab69f88ecce601a33f105b07b2c598108c5cabd57d80fd3e5b1ef483b2a13ced51acc5ea111b88d348d470441f7412e60f40809b26d49fd345ff9a78265e6896608756c8df63f84d317120d1bb21f7ace1c596bb4e0094bc5ab0d2ed591131a677dba06c26cc7effe58c9f416bb57b7b9b07633cfc2e54c31f35c8421f419cf7ce34bfc748da956b5d8d43544a6dc69239ec6d535c02dfea042027231f8b00404d86ed0d2eb3fc398bfe24824569d8b8463a1e961a71141a0b2f52b9558a1f36d57e5a11703ed28f26e6eaee19889e707f75eb496dbbf086ab430934c431b4e6827be7119f74a98c632231f1389961ce2f7a079a384492c33d20189f6e3169839136681f54cca39629cc4d4953153445651c2cc491d60d4405ebd008838513ba128631b633b587df2f00ed393d586dee3af454422615f04ba148689302fa7d6b8b56037e1f07b18d88a38948599c09f02228afecb6132a66f7e67fd2108ad7af5780fec4bc1373ca2e348cde4cb0e0fe0f70cf8541a86fcca40a076e6ac437cefa50c0ca24c6d55af86e7156ec84cc8d70c303993ae1a8ef74b2426098dd5a1b40541a111bec62bc8693e2cc71a1c84d35be65e308bf779222da5e26a18532013830553d52ae06a2dee95b411c7402b0a4a5ac545dbfbb64df13baf43333077b120745c8b1b11b995e78af659034984482b016871f8c407596305d0176716bdb6fda1ed07810131ffbc61a33507926ac1b031fc3d79c53ad30ba1f313e16cc32d9778832fdae130b576efa24564fa581d262420b6e5cdf63856b933b578e69ec4510a0a58d569b1262a79b317cc0bd75f0be2b7553e5d4a0596e9ed79826aff75e4c7cda63b81c0d63d5e123d645a5fc01e14e73ef0b4298ec77621c9c398476da537a0d8699083a55d288b0ec1c2af5b0d86c9e00e4ef1f62f714ca2b67848d7116f905bf54435a37cfa394ac06b32252209b59a78433ba067b96505048fe76042ccb3ea6ca055054569402e44795e9356206aad67010f0d178331b7c8913d44addf67acce77ef2dc0b7ee2eb7f0ddfbb35b9a3189bb542980bec08a202301e7cfcf67d3c02d82c94b4f50bff0836c017eaef3ba574e1bd1932f32916625cb9bc00f6b4ff53379bfb317f3b0930765d62052eef9fcccbf68e0dd6cd5b5563be5d04b55c19bda9d4e419d1d6060da218083a2d409ce53abdbc7d93d39a086bd3cda6b57f49a4eaa5ef961f3854be4e46e0b60b08a3f17b7d970e413e5f3b684a7002994fc09a1bf7da8625b078cd3172f7c2f94f9f48cbdaee65be7cf708d1e7a695da0a6a301ac6ecbf75f8dc65cd57aecff494e5583fbff1de544d4e47ad2536ecb5a4f8a4a53df1ccc00673f549582a5dcd272f8ab77b523f87960b3542d1350a44da39638e0876085030b88a982b7b4eb13d81a0b884120e0bdc413386ca039eb2a63999b672245d92e126aaea1640a26a1d66b861865ec79c0aa88e940f4ba3895ec981abb5b1fbd52484e29126c62beb38f696ee9609fb29e31bb764d25d4422a9f02a9dbe0a3929ee29adccdc3da607b0ab49e1098d5017ceebfc73a960e5276449def5ed3f781a6e621add30537d7dd66dbff331d15f16eddb16900562fde5557a0b153d8426134d1c1a67e4ef578d4641f9f05d6dee2fee1bc3ad2c4e678cc7f3852a2b44494609ef2c9ee370e5475d4811160b20cb605535d852e413ac5e23c007da406b1c95e07bdbb04715e2708318bca5ead0ea646b19b099c248e65563dc1957d0dacbaf2bf00383cb1bea498e4d3987c49015f5b9a8892d5d427d2489b232fa49f0722591cfae7622700b407ac4dfa6500b452144829cd17fa22c8def53450af8e026cc03ffd946c33bdff3ae3b6801e71116bf845d7344c4f5280083747a168ba8cac840da1d5e90e59609123f612b526445f8a90d5d1a5871ba58b844a8320f22991412478895d4448de308c29799900b4e0a8fdfa7ccfbd471dd2c3afdcab3beae63fae2d0b3689aab633c5b905a8632daced779fe3480a6bc8a44d5657ab03d6a9c715a477ec7217a7602395e80173a0ca74684138ebb860700d22ad06cf74a21f88238af9e894948ba947509b6116624742f6a542316980fb91f9fe67e2764013fa2087d3d715ceaaf15d6c17180cdcfbee904c5a35e3e5355730d844975b3a80cc24f26cb16f8b0f1d60c1d4dd76e23f9aa64fbfa1b1a36711ad3e3344987704e670b9940588429d539c6ef279de36e6604d024c28f868918390f273ff0312a3a2f1081831082d164b2c9cc8dcd9836652309587113e288ac55a22d5e3814a827c21c44403f449ce5a5722a339fd9ee3c498d2f7c2e415bf7cb06f898f9a09431087fe21f901a180c3d81d3950cfccb32219a002c791c7060aa2d297452693aede78fdbd350c83be891c9bfcf269d8d5e610fdd1ddef2e6c0ae06f6d9099a7218b7c48eb33c0b65e7e1dc47fbb209c75739e40e3408675921a76d80c22f73cab1f0600230c29439199370669688f280b8490152ddb688a8703739a16b7d1edc3a1150e47008526171d7e51f571a3c24988a2f2c3ad736919611c9125025540d96fb099dfc35f1eb1e1c720ecd84f169a9a4cdb7b22f6980fd7f75a24596a6cd750317356b0d6726cfe479d7e6a7531b89f4c861883b1ec3e351dfb2e5dfbc6164bfcb8885c6a267779099e1ef53b3bb7bbc21b335657a4ea0802c0c6576081f1df7214b3451514a646c9695ab8d8ce1b7592dc0a2923cfbbc8c3af1f0a43e995748e6321775d4e37df381532441e6073ecfdda8ca5956dcd78340f4a231dbbdf371589f44a096a5819db3bf1283f1bd302259855c2560d3d800ca1976d81f31754bd1e951a2138937943f1f13ad9e6ed531fbbd333c94872997b1c74a9c4cf84022d6ad9e61c417795566d03fd270e15fc6d9a88c5112b9ea256bbf871e0217d0384e2e0f8231e90a8c743586c734ca10119d06f4a4c13f73e7f30c62a0f8bcfcecbd4f87401749e7375dac8df017869b8f1206359670d9c5593da60d180b8e8c8822dcd35162555e31effc5f758dd2417dc5390a7c036c76b563dd1c50dd2cf18c3a40a79ef7ddb3cc2ed71751bc8ac35e9e8c8e9b3c0ed6550e4a0b83a5527995ddfb24df8bd9e0a71e0308dcf1a594e0eb5e15d34d9de6fe95be402a5e55a0d69caee259ba9b8f4f367ee7658f13c854dc7395a47e42e4ad31b9e98500de6e73498b1791a10bab1b68c1bac38d7f7971422e4eeaff3603a841314d1125382f5a8aaccf8e5e60e911681c37d24ea149dc09a95d9ec66fcc2f26b26c0ad8b98d53ace4b145635dc2efb5ec2ff6959a92579cd14c3bd39e758d867ffdc1ef05dea447379543e4020d2c91d746652669d9b55b34440ff579444f440a5b395fb68965648f280dfb47b5408a1296becc83fa0979a339513a2a953954670419b594464e558f227aac214e337129685bebd7988ab36528a05785f079d5a8aaec9605e1e44e8515e45ebae0319f6562118042bdd1be05cb8206494f82ac83b0d78a68b1991f063a61880448bcdb3bbc3149c16a7f9ecf7191365a7cb3035093126653974db5e3fcf99bacb8781581bcc1560cf8a34a20e07489185308ff75e3235e4f6b9d5b23c8b92d09bf0013fe0d828c47a925228f9849a16885f0819bf8890b5b116f574d5e7fab2a1cb91c05bfad96d38bb73e0b885c495c06f0cce882c11957416a4694659d1c0b019fde342bf0efce1689768b14a01ddd51b4538f3113882381810840a5b48ddbdb646580d4a9d40955b920d0196150a2e25fd9cd8627b6d39525cb31367717bcfddb2ab6014ecb986ad47656796ec9fe769373d0f28e38068de5092320aae02357ade792ee685c4392e76f8bdbf72d39f4bf0225781cdf717912233a7fbd677f5def55cb104c3bcb40b7a1bd153dc9cbb54ddd50cdff486d7d2aa590e125a6cb84e5748acd114ddadd51883ebb1f1af5eb7b699b3601aa9175096fe1e6480a62b7d6ec4b6576023083708b23075d54a765d66606f21c4ece0be10c265332560531abae544a276aca2cc4290258de087422da0770c7fc4a158e05cc415fc2467248853dca3dd6df3769e8697fe5f4911eb59a2ec85576ac2ce223a9f7d107c52e92733928472e53eced99cb338ad444dcad050ead3b68934550cc57cc4ad5fab746596ecd54631c14817f99ca0de72a23b77e1cec988ab89a1fde2d80c1e0d12249d9b6991b260be447d743b2164008cfdca5898bb6258053b12838a1561d03754bdd4a549d700368bfd7d594e7687c5842a6a958c7e13ea55832fee22073ff782621d24170cf052da208d04256982d26145cbf2d29ab27628c6145b636e73e6cc0b923ec4b087b5cb0774640b93704dcf3d0249aa888c1d246c64bf8a10acc4770a895de345e1bf0e5b405599748edf63e3d5b8d3e190883813ea258262d1e02c9a00f443e92a119c5f0d7f83ea356fec3067aff0a67b4cbe9a27c9ea8143c33302bd819634a1aface816a787a8a4e5869f5a02545faa91b9de45854abe9e3745547a1029c6ee7aff2a73bb02f4e8aa074948a90045fab16eeb20fd3ff280284377d600d99a22b1628713a51a3399113f087f2d5ab314493d2ab5c3ebb4fbee083cf702c75838a6755a378a517c8fa2a15cdd9a36110105f98bdf408466de56394b17f06173e467e976a6f8ba63620581bde5febafec209a4aef4ef0fe94909a190b3872bc96edfed783bbc21e497d47b5cee6307b2a234dec4aa99b0adc7eb1e200c1494b67a6f6f93a38e0e867b35963ddc66cd927932e58eb92530a6361a8e491123a62935aece4153254dea75f3b546a8ba8b611adb0946485b3b0ef132188746fb5b8b2042f0cc8bb6ddda9d9b523ce729712d19cacb83e3e739582de5ff2cfd0bc42c1ef8035221c65efdf482fb47edb406cd168263ad848611fc5aaa26b359f1b3037fd7db2af5ecc614e84b0323d2b2ca234ecfbc86584e32085fd7eceb9ebba4199ec86e4293105161e023136778a282ee1ab8c7fffc988e11f6f1b9a996a4ea0d6698b13bc80a549cc34b89a36138a4f1ff27059f239d06140d06d7f549fe192335009d06665beb5c3b91d5a0be2fa910b72f8a2faded57354b35d5d47f398d2169c1f80abd427405ee4d58d850ed452c207ac3066fbe7a1bc621ec0f7992deec717dd397cc091b168069ab117ed3b80efd65c8f69f9cbb9468f96641e52872c5a79d53aea18210c70a288881f0ac7ce68899745aa44a6db9b42a47099b69f0cf16ac1bfa9e8561c63cb432f4a3330e392d48bf9922bbdd10bf3434f313159a7cdea4a2a67cfd24904184c5d1b8246903d35996dd5e301a2a8b88da5870f82cbe2403d4a491b9e20a0b9101266904059ca09d58b0117ca51a2fe022c008ac8ac751d038c25d80320a449e4b2da0a2879bb06d940053cabfa626c1b44bc91564250c970120256f4226beb4ee40d702dfc7bfabeb00a7e76587a065a06fb14e3482c81e838b2f987f9e7bb3ae95741e1e33ea6b04ae66f814cec63c12e64252ca338fe4fa9a4ecfb59402b8700dd9f4a3aa9cab95d240d350a1c840040986fbb499a7bacfd5e29777b8c489a4748881472405b1a7d087468e153f4452aa6c5455becee82bd20c66319eced23844d20ecdc5a4449710f2e22a7ed99d105fa0c2711dd6e2b9ab753071c42883fc5eccffb25e33264ba5c8321d34ba21e3421cef23deb58f92f01484d4116aec72c00d801cc2494176c27db2ec452c4b04c3916d7aa13ca49c308af3e10b9948914f7c2a7a3093bd01a182c65c388b44e56d4829e3ebeb16415142fb7e9488b73a71836af2b7ed7e688e8da96c1b8402629f495488c6f98849efbb13a220aafb405779e2e9ffa6ae4234700d1be275a9e766106876a30ba8632d434d8ea2c91f8404822c871a75d9287fa5296f2cc0f74a08ad258be410ab1bf721c6b2a1e066bcf5edc93d9b157624581c0f48cda390d006fc39e014fcb32ae63bc39190b0f331222c4d4dfcda05cfb4024d71f0a0aa8bc129921ac59f5a538117cad0e04b989df09af9f134a1309e53a5b99d0a0d7b938e1907a302a04ea09a057adac6f0d3bec07873213fc9b1d81977257a47b7b18022b125b4dae8125ea9aed6c2a6ed1cf0b300e17474430fd38db15e9f3c02c8c17d8bf77f0fb85b26a2be919d4bcee53f69e39a2801e2df9331a5fb7acfa5bedfdca294657a2877d07fe5bb0910f13305c3956c1847b82dacdfd8905fd111a2cb04c4771883b20bc2d435649c6a33530abdee46fa00316efea876ab07454eea8b904fbf9e6e44bee7982f6a0489a59ae50d966850d532f59575a98d30e3f0e1547db8d0ee6cf4fb7188be6a4ac1dfbb47ca2c08474d0cc2a3d7c68c1d8eb2e279408c550b7997405dfc4dd630f99e63302ed046bbae3bc007b2eafff529762bec25b080b5a41d4a02d770fcb2d83179387f3ab4adc0958320d36322c97055a661f1b8a0d70a5d2af19f91aa1c5c56774c729f64afccfd8f2ae2e6e054d9594ad492086e83f8632782ee23c32598ff0754ab938e273789ba3270027ad704d9d7a8f929c6da55e335afa122bb98cc632aca1f05a70648319a43667f7fa47c35d15bdb804cc38c4aab2e7fadf9048bd7edb2e458e525cbac0746cc6588ef5ce0236b02d8e665a36f5f916010b01364121c5d89f2ddc1a1b2f7388abce9ef5fd609a47dc1084ad6ae36a6264e493ac2b36fb80745515eda44a9733ef259d091de92ea476360d82e878486f203b807f6239fd61a5e7ef6177fcb45af59de62619aead386061c74388224e902737674b8d0e2711c21ebb8c22521b76d02ce284020292be5b674ad5522d2794b8b9934ad6525bbf2ea625bc9c921fec729bab4f06ae14af49bc2ab0990f3641007e34b66f36fba5cc5df5500b8160aaebb8b374c616206626f406f69fe9004088010b28c1e94e4b9b79e42f3ef115d7813fbbf8a10fb57cb4e9188a1d01e27417be221b46141a22c6c8546051aa1070e35f64302729567f32d68928105205f7fca18288fd01ec0eee45e8bd6e98720e30ddfe55df836b80bdecce070c727699f6c991550058b3d79af48b0443bd7292c74a225f996e8ddfad33e886406bf88cdf06cf156b9d814040f550b6e3e176b19367500f66de6fc99f7e7398633d503303edc48c8eb1bc8e562d00fb0bcb62817fedc449f9b750c5a37613581d4a902bc0e6521779c2c4bca5ef864b06a6d67527255655458c6d76db62ebc4e157d4d90a73a2bd71ee5286effd16a56b22e2c0963493d3b0444acbd1935861a98716f095bb501f44b91121f61a2283047cc3bbbcfba8c3386246f34ab4993989511320f7174ac1ae90e1acdcc0c18c0c22ba33399d9ef73ed3a40f65b8d8b13b18a2f33b3a06ce2d089cb0e88d30865d4091f3ee7c63989830e6d63653e333033b3f1490c5099a9fea7d631e2e9dd2035ab6979cb02ab3ac3d24e4f8df7a9d453136d99628b64d8ec2374b82f15da1cec8ca041afc96a354a32b4cd3a792244872578c4e93ef55726ac39ece474071107f2537fc6acaeb34782e00238bc3b95e05724af9559677a1bcce59b051b57c8bcefa40a689546ee2cbfff1872207b042346df7d7a852d115c3c866fe84f3690a749d029905e89eccb69093eb116560d6bd476111a33d3bb1f92287640dc5dbc3c8f0d2269cb7a0d6572c307960a231a8c7a00c899525de9b9df9df501c9285d040c18c5a0f3521ab3c32bfeedf1b7c98b0e24d44bd63ee54d6293d009b2a6b3f6a44a6401418f56b0765cfcd6a7b92ad5dcf277fb614c8482775177418a03432394593ff887dd43595fb0b41d1a914aab438f1fd229ed1165ade7664566eee5ad65558368a8b0fa591a5c07d167bea66337d329151a61b4f5802c2a2443676172fa45e81a95735ad80169870f97cb1fef04e93f79b943a4bcc2254dbe2f1f2a2d1ba415885c0a0083b1e7deff04b43f62be8f3a45e95a74727aca3782479ffc5fec1d2e040ed6d4a7ecd01edcff08c53f7dae21e2fddd53506cbb5a0ec901628400fa78d1ef367d56bce937c124a06690b75b59a04b81bc0d35704b6c67dc55f2734f3489afcc7f4805d8157e186efc48a87cfd39590d2f5e8690bd369a9ada766964730102478ca3e8d25f08ce3b40420176852d625819d6e1c431c48f88fa26941509c99f7aa4e0f40fdd8ad935fe799cb5385430f1d1488f1df64ac8cec7d96661fda3b8b0256ba9bd6078b6eed95d03cbe1f47e247e15290067632cddbf76f0f30783ee8b1b7cb89a2f0198b45d4dadc43f6a555b82554b78b321d73592b51d28257835d84b520cabfdb39cb563ac0332c21bc4e1ee8d9b7d6a0a1eb81cc88e7cf2f90fa3f0934258bcfb9edff87ac1b87379898409124b30336ccb818000215746aa442cd0382eb747adb78aa53eaf765197e2347b39750b4d0397f78eeec56a7ea069fd61a826c003b78e5265808a0efdecd0c9abadd636a1971106795e0d12006c62f26caf42c0fd0bac10d57bc89571498b44250ebecd17a32c520dbd9d5f34de40d1d528de42e21393e8216a0e872b3df116e293172bcb44ec9d0891ed06db4326f3e1b38475438655394e1bffd50a8e3854de57fd96760f6898cc3596047f594af542bfb9c58dfbe68e614345a0424db6698062252bf99eb1aa7ff03f69949823bdf5952cbded388d119bc2d62be59295926b68d8d90302261f1e5ed6608de496258915257bc8ccb826fc4829397b11096baf11fc30613c11e997e48f16ef233dedc498786aff0e40f4913e7be306d2bd9049a6e2b554659b4acf1a89b30afd5f49fb20ec792fe129c98833d48e99ace3c8b5d294b4518bd1ed9efc964d23a7fffc16e5b0ec739d56aad06c4d10026478cfe13d7e2a0d9a84a567780a16361a609d38dbd34849d2636244244d5c0e26d8708601a5c4d0a4860a2e18e3266c73d20a0146e1976ca6f32e0dcbc74601f12e2f12da1d0dc2efff00ae57326d43428d1a79381a8977469f7f44c968e87b717a23ffb14481a2e486994bc90da72da004385986aa204921628065ceaf155f30e0bb58522741588fd83abdabe717914d92385c40aa826718c61b29a715c658606b1b9fb24a9f39459fe7c7438d0b1bc98be21376269165d2a5ee1ded5149cef2262eb8ba409b4a2010c4fba406cec45361880fa7f3a1cf9689058f21c41202c3de94872357e76a36704a349267109ff5c0c891732faf903187d741632303dae4e310f4e03a1b684c80627e7385600ef141a0fbe430f3f744adf23fa083e14d9040177deb48ffcf9afc70f42f62eff06c2fc635d5c6d5d9d33012e898ba3749568733512384374c72cfa05477b1d98e6d2f4a6a9548731d9ef871ece8d894e40dccbe86cfa014f7131dd29882a421747296f3ebca50b77a353ab6151d8cb44ad846b9770d83faea21ac52de36d8464f7728e7a564b4a34d56b748c246624b31dac69fada89d08222c0408b140ecd2409598480784306e85da1fbffcddccf969360f439780703821ec1e4245e296243f78ec1dfef6c1a05af6e1c517e7a2c60bd3d44aaff0a76a850cdd3f785a462c3c054efd10cfdbf0c6a88db3ce5f9846adf2b00966bd40d9901c709a33ed466ba0cfbf456cae93ee1ff5e0b78e58f814a93f669ae026e49b56ca87aea7e79138dd59c96f9da4ca1ba05b7d5c21041589477d60bf8285f06046fabb579ee442df9ce3730d61db9c89beffae128e20f488710695f020908ced91cdb6073c8011664b65551856d33c45f2c77d1c8ead8c03130322905bcd21e8fddf70a9c21a1607c90ae06247f6c95fdeb8d357c72c87f3f4ac99187c6b351ef05f2d51733c57b0d706221f0265bbcffe3b7e203889ed1a0668702bf4f5d789c149b5f8ddcc73630355b24419e89e87a9a3956af19d4863d1472f0b22ab31f849471928201ceef665d35e35ce92342c7af37e9418d32c11f1fe557ed3efccbc6581cb3786ff5319e8f99cc88bab885a9a1449866454f89bd04941284d24b34cb821bce5df41ff3fea23260ffd263735ce8f46902b8cd17c7f93221f419fc5c8b009fd5ea2fdc730d40602b8fd411e53959e035d61923c7b284128cccc5e878a89a38a75be212fe593051a165c03135b8eab4e2452d49967e62b6e2baedaedbf0768dc183502f917c30b1131edc93ebc2173a5f58f127ffdcfea0f244c5f996a97c530cb645a2f6872e18ff77cb3bf6180601f4b8f4de201538c3cc789cff602388ceca2c283c1933f05d4897d183c812a2f6ae01746227e58cc88a1c36ebb683f8e6f17b2d2849f5e72a0429b9fa3e593dc6f92443bd08480fe750d063e31c330037a73a9bfe9d9acbfe9eeae9beea300e916617db7c299e90df3a2bc2c1b59c6e891693b7105e787666fc8ab11f22fb20a407a698f1ed8ea4550410ec23a0478f84f5360aa51879f51c5115194a23b1901166c1f716800890dc06fc4383fb5a0d6e244dea71d345818f7fb83e6f0f717dbaaf9fb5f258b9f8f90bf29955bc271900f146f06e34200eb4c19a031b1a66554f690cbae756708d974f1a047b90c14eb2b07da030580d1053f78a96b023c7f6fa6cdaa429630324c2689873d10209352aa95a239fac4dbb9df3f28953d1f19b85120492119d27f3fed23191e2a1f9be329125f6fa84693c161016bb779b3a013210328079406a76768b3c595c94f2bc82e9faef9263eb7d327d08cd5b4e7922cd5325119dfe8030b6e58b5ef4d53a2e574d36ee52ca2c44431a043c827347eed101d0cc65ff43ca196cf7c56788e4a74a3acc6c31f44a118f62ee6b047cd5f4782b5ac3cf67d1f2585448ea6bcf0c36f42a51d44d7d519ee8dd6b9fc0a1195ffd80374cbc6350c838ca710874eaab09801185e9a663681034d3ff7b8327a97b4122b797956699032ad950b73af04048de1081d73602fd8bfc1a9c330ad65010885eacc0ca62498de1af3c6e08291c762ebcc4c4b978925983a338ffdcc26e65970f29ad91be90a681a72502785a9d2b3a530797cbacb621c28045741b05c45875aca6816e6b1bc21cbe225564d259b7067dd2f1f13c964a2649a4dcc3163f957071a1a312620ca55a687c51320cda2cc8c1e8ec48d342e211e8334c61eebcb737ab7c9c7a60c53c93a9890d50e3b943852663eb833d9eb2b306716406198eb871f705d0542e824f4a52b5eefc16f16d05a31518f28cce04e8af3230148f1be27f5ec7b3e4a6f14a2f19afc27fadc877d823bc54b7b64d13e977077fc67a92a37bb4578c847ab63c48fa37eff739a50deabdd9551ff021d4cc87cfd13f57f10bfb0db167df8542ea660dfe8dcdf5deb66bf2eb2fba46ecf44a9ddb18a6f8ff7855f4a7ef0ce4f1404c9c1b720f4d9d91851c529ac8b87d1c5caa25445eea92ecc8922e3a07d27747fb2e9015cbbd761843c9a64d820456c3db00ee2b610d027849649989216e1d39320369ef62ddf15c8c0855de43f6158c5f9998c5f6f346c010ae307d9bd2ed987e957827bddaef49291c9f7936016ac3f575b5a1a97d1bf43bd44ee20a7ee900c67bfb7b733dc57da77d1d4b52ad630d3878a5ef554628ed4553c916a2c29f5ceac0c2d2e0e0d27a1de85e48cad912a3869ca148273cb844dc391087f5756cbe7214f8d89a31e216bcbdc2935a6c207e997d4abba9b2a737088ed589c575f2d0ee276152da2869e802a2481e03b4d712168780c1b81ebe45dab8cf0c4163521af11e5f39a76e44635653bb8d1ab4b041d441dc058eb6ff50006465cdca112ddd4029529cafb332d6f580d468a64b797348027e3f81b3a19492491d03620e1bb79d7216f49ee6e348fc4470d9526c266c348260f2b98045ef14abe6f923d3aba0e323a1c855847de0fb5db13a96f9a58cfa83735b94fe262cb3c7ac8cf4e92f96f0e8ba643811d2eb09c009083c15340c6375fe92459b950f0a4a72129919a0ed8e00a06f01f301022c88ee0329ce1d54c1024a5db96b5798baa7283a379cf688e3cd6fad74f6780765fbb729415afd8d99671a99887fbcf4ff9cb5b122fec09eea36beaa9ca834572a99b78d97832d546d108d6d617ec55b483ce8e00749dba1801aca493f43b142f1207d42e440c718f9892323840ec0da87437f19873e662d01207845e02f00063d8a30940718bd3549f72e80ab36d45445508e3fcdec0ac65de0d1443f313d1615af8557f0ab2177ad4ae3eef55677df648d7d5f647ab0086fc605490527d430b4c3c2f7876dbeab7bdfba3c6d10fd870e39b4b00b2deb574a571f95b0cc35a902d4a32517cc6846c2008c2f3278e3d2432ede019510261903adb9f7c1eb84584f120b34337f00114ea41ca16c9d9315412c2b919aaa5c263782c2d1c91c05e7be1667fca46e96d87f423bfbef7e8a9be57b97bcd91a59f10ab3ffb6c24dd8e745789079818261cfb039dcb5e12444873fad64469331c91f8ffa316bdfbeaa482d992cd4e6842c75ba9cc2a565d90e568f27d14b2eacf4dc4640ba8286fae4c7c59d6320156f3e1917ce3e68ec81da90ab45c572f21c3265da1ee15468ceec8811cf58fb42e4fa27ffcdb1fc3509a71546c90683a5dc6d89cfd98713ab4a4db6c36f59e041477cd8f1404d702e65e733b712f82ec620e1f59c6c2b28d57bb19ac70841b0158e8de6bca43ce258efbe674d006c6e701da964f5321846134a1dfc91d5b2be502faf2e04af482585ab2db1aed52c1eaaebba35ac4f590e50fcd4e8c06b6f0df93f580c4860e719b665db1c7cf776d1d4caf210c15845f4e953b36b469bb2e46b5717c85e59407b3c0d7a4778009fe1be3e1d5129b9221befdc9de7a2c467b28cb0f9a3efcf0c8917fa1989a2c8711450447f3c097750f995b0b49c1992f39da45b694c1136be167c7229e6a248209d9104b5ab81c8a9edd74833ed8d949efa929d3900338c05e6e87cdcfba61dd9e102a7a13330a4083c50ba3369b7a138c935e120a21ca54636131c1951b9c791364a8001c6af0bd58d81b8552c7717228b8128a9f8a14f70dee015b21869057cb83b9d4f3cb2dadde0c3238e7324a59c8ddc15c7bede2012fd398025bf5b0ee74de4db6a40a8887547f1cb40efd8f84c906ba0769fa3da1e2c8800ee5d257d78fad92231fce17374e1c5ec5b6b59abaa5d6d8da98be7fa86d30263674cfed8ea851244521cd5e272c79ef3ea1495f96cae847cb72ead2072480b9acb122489ee5bb1ba046bdcca40e1a9c8682a660860f51889fe989d598884b96d8dddb0e09afd2868c04255df44666cc6712a657c4496ed4b8e320ccbc480385d9be0306965a60941882025b8705a7e79281411382304470d5fb79894573c03a36407856e38d7e7117b98c4f7303e0d9288c860ae57806c1bcbdfb3951b0ad9cdf1797473cb9fab7d5e3c7aa61d4b97b929fc33b8161e9329a365752ad5721ce9081cdaca5939a5623ed7690c95ad016196aaa3d0188c3fb9d63d051300d7c0a94d9052e15d05cb9ea2c5d3bc3fc914b13ca13d8a3014582713eeb62f8a7d24ec3e364d15c4f514ca8bd9b65f67c09de4bfca22c35b824ef17c1fd5b9401e72960ac012c9d12da1a2d4c218c23bff2a7df995c6f7694d28ef8581b14da3079aa4baa3fa6c6b8ee8007b75472292b642f887397ad1936a05841325ab78cd4575dc8d4d3e8d343eb4cd62a22769a59ca073d6f5f1eeede4b52f773dc4724b7c90201e866cb4429ca0bda5aecc948146add35776c7bb0c5c32ce79d61824127b7599c08bcc1d791cb353e891e7cab867d803250a22b516bcf6709c8320b74063413e5acb04c37e5c41ca091f0dc08108a96656a93e2369a3a697c98570a4e80c89ebc14833b8b560c1e442eae3a8f0d22f0bebbfe71db30a6ad06ebede55f6d59aee8e63dfb3e4762c923138aa8f2acb7fa8c54048eaa523d1564e0573d9e300b6ebeec8b160d7148aa3aa6f8df5f074bff3401a9dd2a5eabc5c1eacfa029017520a59bdeaf4e23f9c4dbe20ec5648b71240dfb21d561b990465657eeb480c326abe922fb442fc0c61fd97c90dfd434d39f9988e2ff08ee4f783eed1660ea57bafe8ef3310a5bb2d1e9a54844ada7482c561ee7324c453519b6118f73ab38f77021bc2fb378a067236d937d910382c1efbd3888984b936c83b3a77800e9246f5e3bb4431d7de5413649ed31d8cb115b0cc1c5a4e72e269f76c0b1ef414a3888ba3858b786490c7b3a04881f48d0f3c80cdd722b4193745dbd35a70d66de1ff3405afac4daab6e0cf4153ee87b98ec15b6e221d10a2bb75ff906620976acbe22746230fac17391f3dba0bceedf2edbdc95b21624cbe84e77fc4fd31101d3f7320fa71b17109806feb30528bf27bef586b26eb6520788ce159deb4fcbed87d3541e40824e2ff69d928e4e8dcb149cd53a65e26e6e9ac6896bcf92ee34ba9b0fe30b4335843da7f4e0c6bd30470dcf2bec7aa33d2c39485f63b037bc6064b460745630832918502758dc4bb0c6082608c112adac0d6eaa85313f1d44a4ca7e414282fc210138ce038f1737f8a2fd15313c163e4361b5af1d4b10854b41fec157ffb3d42e0a470ca1ebab6b92fe2d2d9800a2989b2f9a8fd91552c85a887c35350fd887440b6f85c17595b99f38b86cf6f71dbef70bd03975cc2c9936a08f52991ee50dc169e713fd6461cc10b59e3fd78e2386f3f7f703bc88e1c2dcbfe7fd9f8069f51e860b76f7c418a92ba0ef98014059f9d31180bee8cf67003ad19f01f9bcff1a7f92511be1d7518316ffed7894ad3196ce66c8091b8b65b039d03f61b3b6c8479e849ae0e54fea6bffb03d813e71b77b3e46eb3e696664b906e272354ccc332ebe84d3f0752bfda95677ae4d21026777351e48ede8021b53203a68a36a5d76514aa4661ceb1c0f3e69afa04398e66f50ac81ff71227c178efc9afe3bd0e7e9ca9d64f4098007c67928caa42511935ff1796beac01715102ad83429cef018eefd3e6df56be06d68334d1621c3f3d7927b832b1f06a29c93351c272d26f6528c2ad8930dacfd285bf29c75b11d8c8c133a1d25fb612d422ffb9cac759c1cbb3863ccdb138d10ef00843edbe5174e9d90ca85eebe3bf031c9d32240adc984ce1dea0a903f206c50b376c49fe261dbad85c4c12027a39d6fbcaba71acce42ec2753d191965557c0f0d332f16044ceee7b4f96b561171a81aa6a602a20db87432226dc6688a10d7c52606da7c8b821b12ab4aaf4df76660c3179f10915abb0700ff389e615cce931b705d874186aa94c5ee63f375677512e8dc71aeabe85e3f0a457d0faf966eec2662d2c34601008771972a56adb2464e36f126e86fca3055c866f67dc93aae748ee9e7cfe19047b182dff8fcfcc62fe58b7c9c42871ed1d2f8c4b4f6d9d7d72e735fa86d385e85dccc35607d21b5a6a1c4d8f0206e3afc0f97f948b708517c409c0312341d3387811d8ea987e9b910732cbf62a56aff385eb9683d169f1deb4a91dcd64e196e4520d491b322b294afc0c730a22c82a2fda7a7123250a47ce997e71237b24c688695080427266ef0f79587742ff369f6f490197034397f459a3972347df2c29249869b89b6bef34464bfb2db626872a347393c38ed709d227314b75af58d4b2414c8f79ee03f23299afe72dbfc2b9404f94810c63a34283320d9b80e62de067b26d102b6287f1d10613e63d7e4f5605babe684ba9a2421884484c02e8bce9e9b0ec019d7d705491becee5816b9880a4dab60b5507a488a384243b01630c71a2eef02eafc547c7116e2038e051c3f6c575615f6219c3e5c93df6bf00c362b58f0b5576883d8784187a49fa2c8a7089cd9021398e4d985448778a9dc24014104c264e53189c8c1a4d92b37bb70e83e7594bc8ec1315d37c1ba7e5e79c9578619babe359557deb177a3b9bf493dd0c295fc4dd4e2912ef356e050733cbd4a4a02765253955612881d23ce7cb5acf0379fa9759a7ea03323b3e6ac65f1748518665649f6a6962deb6884e6598cc215c55980f0b4f929e100e829420612cbbc76a111be46afc6db2845015399e5f5cd29b969ea32af673d78c742fe6f68cd31f3f51c24cdf70a5a052b9c4ea07cdc64a4cc7e2c8fb54bbd74806f36e42cd34dada530c4e62857a80b7f0bcc59e68f022d07d7070d2e3dabd529af062dff7f529c39ff6e96e73cfd3cebf0452091f7bb8f26d12d40fdf66b4e5a5f6ea3b18ecda936d8254eb9834a5a6c21fde56574952575863730acfb0ed8a0084f2778836136b4d63bbd0937f00f5c8d425c6cf08dcef2b2e03f538e06343e5675f41ead2cdbbe6e2361e6865ee439beaa7906606bb68bcd18d7d6baffd65be91b613da1bfd002f72e94300866c1c639199beb9ec684a6d4f4bd75550ef5ffa4653d9838cf61244a157b26daefe2eda5f10492d09191c91a2e6aabe635a9d6952502c480f8c38edf59045a87f82017bc4e8827a89165a6812d8408b1eca3cb4291aab774347d1d99a1e490e5577f1f40d1c01580ba98c2acc02fb5a6871946c0aefae4ad06dfc2a9f0ad06cb57d823e07df3dfdd4b0f7f4c2e0d34ce3bf9afbd4789e60077e93502aa7d1ce32c3a3adae6314da0959ca3235e3c114f8fcb0d29563cbfe663300f12dbc404a69ea78a8470e8a5ddd8607d1cd31a62747168309655d2bf33a8677dfd4ca4f156f5123705e5dc1ffa0f2c419d3f9497ca064ed6c19279282390e49f99d47daa936324938fc1d550e4bf2f2e17217b684b076907b656809c7783066b29a662318856ca31afc8ca1a5c0c0e22d7990c195f331e6fe8bf81d8254dacc8da87014832c15bb27846b95ff472ab20cd706f73911a678c10500b1d18649409552a3a96c82d216de12a3e067c4eac382a9dc79413409f4385f48b414e15041b1042e7c18c3fccb71eb00689b261fed29f8235ca20f44204f60bd0eaa2b5798eaf2e41878fb17751b05e47a289fe5601aa2d89ee921d9d145057318024010b8b3bd68390696a09616d4168e9855755d2943662651415eb3673faa456ecec86d8a76f6f369f52d712104e1a2e2059f0c8f8a783fb99a7b6276c1c499fd33a29f15c10ea5d74bde1aecd2f9d6fd100f08c008401af70bd528dcdb692b3ba778ebe644da08cf2d2e1aab49b181c2c41ec071b1202f3c57cde66fb6217e8d171861993a7054de04b6bcc0abb7ed3c5e334990deb1fa7bc6896f590f7c30b480e08184b3db69e54ce93c1eb3b6f8c5f66416a3ae5a476fb3e3edbade30e6f32ddf169c0879b198ff6c637c741fe7ad63ffc513076f9be1c137d0ba0e5cb89a29b86665cd6b1606cfd1af384a0bae6f12722c0749204cd7ddd920778b00e94ca51747ae668ecd574cff8ede590c5c7ae28e45a6d6320a7310f137b1904decfe979dd3848e30e269a9e02a7f70dc2cbb73c0bf7f6810e19e721a8cd2b422b1dca31fac2ac7af0d23953ed6c4dbed7addc795300ae8b9acf7ec54bf08c44bdfc16ed86844648470ae53d03c0eaf25f4c6c229b7a0d5c642d4defecb6194fd5cec76986d854fc15246b082d6554ae5f5ca0fe41c478b1e537298b283291ebe70b815bcdae461e59bfdd2fafdac5499767eb2457921035f307840d4dde4f3d9d371ca08ed4c8534949a206be191429e94fe5775dc67989efab1c942b535f01e6324c620f15162dfe92b9a2d90f7e2e921bc6e879d5accc4f10c6ab0b9171e3530ca28c106921795e45836d946a04799e4e4eb1ccd06dba15c634fab6330a0796dffe2b1b2d6f4d4424a2779391f309cb9f8e28d5f8b5b417f3f5acd27c9166852b0aa7a33f6ab09674e28bdb0da68e99a5cedc35413c9afb99c092c71c3aebd89c0822f13862eb41ba5c41648fbc5cf425d136aadd2cc2b7998c51806ca0e047bb829ea8f1309302d2483b818ac8e1005826e25086a307de48193a2c18c76fac9cb5cd6f733778bae39a53af02297c97dd8e7f82523e3457dc07e052a3570f60892e8602f496711062b9408c4b56eff2f547e50e89640808b4e8924fc95d3c8a072ba52a4ba06c28af937070b7e4f8b2e9476c3620b956cdc643bbf2a9b6004e5dd0da86c9d49c686ba09eb437ca6adbd6794f0444f41c5b174f59d2ccd5ea9c2302dfdcc040de94cb89a782a61f27b7a87506239fcbe9bb35051950c5e51de4456bc965790abc9bc437a726d2576037079e5dcd50a0cb6dd857f70ff37ef3610954a7420f32a9cb866a97981e32857d6c68786e023409a93f0d56a87fd3e64fdfb9b65d9186c852eac0ca366c45a35e0a230eceeae08c6abfbb6dcf038206453270941d9b5ce69a42bc2176db180f33c76ce2bacb26f39a0815fcd5c7c128466dd6ecbba91de0d7c78c1dad7623d9bcdcf8185a1859607546e966406bcc7525d470f04188213fac349ce4f0a444b9c33e9b7929a37b410c3246c46223298150a72427331012d79a91cc5348d8c1b55b933fea0d53d3569aa7614a09e95d6b1b5cd438b216221194483cf39a406064e33236cbb449a3730d260a4d8917310ed808bda503f1606a43101c08d0b25066069b0fd9ad27987b233d048ca65e8adb6a58aa47ac24ad5c1e14531da097122bacda64993f90cac4ea5ffdc2678ba18c19b4569cd8c627be58cb58428206750792645e19e8761cc7991cebc4078c0be0087c569eb71174c4ffc17106be584379b5b9f08d582fa503a51407025ae727d0a5f7ae45a3c7ea948e81521d08f08fbfa876cd1ee71274b40f1056d87a82db244224994d04716db5dc28e03984d0a31ebc481100c07256a207e89ccf3f3a3cbb68c304a6855a36840e64394882305afde52335e91ca7c7e49be89ee9a998d133eb9f6379a04d52f3d15ba299932160785037c65217a2d61d36547d27b08d0aa75af906c4ce906b5f98967d92d68909b00731d13e0e058b49a9382bf2ce996c8aa6e9b580038bb358dc3f1e23455617e69ecd4541d623e8f57b912f271f8e62f432734fb23b3bf41223a5e9118fc9851ef6b9ed3b298fd919df41033656b7149e7aec2e4a3035b03c01f156192e48d98b3a131907ef7bd20796a7842274b24701dcf8a31b47bfd19905dd60e8b8a802dc6e699124016505f0ff810250a69bd82b7c0399c05889bd899adea56a8aad648ff54b0bce68e149181078344869128facb2ce5236e578622bce4f4e1b8aea059ee9a6f37274cc532db6d75ec78dc4384e54005ad8fe4ad9878db35986d256eff15b0711596512641af9965edfbbf51270e9878dca6f6cfd82f3f0354a0d0b3a2ef4c705cb2eb9c0c16a268d94452a8494553dba882ccd8842a5d77a43222dd863c6298634358d81a0099ec81cc0b14de31bf03d019859afafdbec17327f2b1012135c9250541559164ba655e777821c36cc8858ed0132ab27b3d5fb0af462181c42ed6e66a35e39b1ae490f49175aff22654715f8e21e328ee6febd49f3fd8073ef651af347dc4c2b9e77076a1cf9ec70c7301f1e037acf28036b5e512d9e4255bafc72e46b5486006b085bd1aa718326de8c36929690d96b59720a5590f53d8b1bbe0e9ed5949e40bf4af02049d54f438041f9df73255c34ee4020f142eba887c2701202d57d69d9421b911ccac3b04a61c69e3be00228fa25d3a070707c883f399f2749dbeb69d78183c60c63624628cdb57f7dcd50743efc81c892244b5de01fa376a22be365ece7961a2c26c161a0fc354c2e0e13db212bc0ed59649318e428416051a9bb0a36e2ee0d0ba46b6202d95b0a239735e0cf89e90c9fd0f99483de7a309b5b42317c69354c59d8c187c487fa28657dcec32cb59cd3bd29d65f6d7e87ae6f02636accbf468dc7ca9410d68044625366259e03b32c25bef36cf465a77108cdd1ebf045a7b096c3a568f0b7f94b0d2903f6c301e35d988832a39d85817865808242768915d34bee874fc365ee0c75f3449ee9dac9a7c5122f11aee7eb927af892f6682c45907b034d06d381ba4ea10ec140927925594b0060f95fa568cd0e03be3a6dcac152c9496327c99d625cea3c1fd19f3b56b0f6f5f8d53f66e650079e21af4720e703c02f07a89f9f39dbab9a2e4040e108a747f69e03b8db71441f5c63b3e2f441be04254a9af631145457b245a72e4df6f810ec8cb3eba7b041a562024c82fbb98c084d80dbacdd279c00172e4c95b0d2b11f8401b7fbdc6bc216ae86595c0c089d29d0f3b300314b1bb106520cda88aba71331a1b0357c739b4b54cde737297d08b4e40213b8c119942362beb33932849f8d8f2ce3832bb87dde07c11006b108054eaa68dc0c8052a274c821502ce25730700d44d2c99341efdac687f869a4a8015a5e5bc0c14d1093b445d8d75b50529dc18169e1681e2c935766df8986a59570019070dce3a3a85d58641acc1debd6bddb4a30f08e49d8a7587ec7b5bc9a85c04714631029fcb33a7616dcc854101cc0fc8b3b1706badf1b2a3c20dadd80717dbc5e24d25015d2b3f67b5d1e8f08cb795cfd012684997f36051ea57fd69aaf8543a2c445da46b2fd2dcc7875f26ba83cdffa5ebcafa414e1c39fffc9f326914ad23f0e23d31f49efe520c78437d67e930cc9682dc18bedbd11a27e9323608711daac633b0210bde4bc92d3fa1ddc040934a33d549628d7b219f05ac2b6c7131786cff32018dac973debfa222470ff67d21eac55394c9b4bcefcd676fe8016d1d589cc3ce8228ae7534a78d7b5d836604fe01dcdd659a51d69587569979709b1e1fe98eee9742f05f59ebcda65532777e822ec0e494b5e434c3150d4949ed6a6db8d870270d8f30211f92dc9e04aa3f2f1d0cbc6e218931efe4f80ebfcb379ff21f38991227af402ea76c01227184374759f32319260e1a7c5a886642064987cabfe3821732cf30535536b9889aee79a7ccc2e7b5cfb311524ed03793e2a883e1e02a30f59b6ce1aedbabd0851fdf27c200dcfaafdf4aa845ee11c74f488ea09856b337e1a50590a2c77652b31ca6d4ebbee154510a98b40e1538685234397490e8738f7c417e4b14a84523ff58474fc84020b9b1d3b99a8620acc4874314e880614791874c2c19b7bb68210ac6e4ea185c9f2a3843ffff52d1ff2d61310ca60f2eab81ee2f77cb23a611ff9ef42fde2e009e5bff451b67bd3f0781abf031b436d4aca8b3889b5ef3e2a09ba4f9bc8e6988543de8a8b7d29702f2625bc1647ecd9cdc471d5986b087acf857d0f0d45f0fac605f40b26d59047a66d70800eb81ba57b98c6762c35d31815a672c50521bd963cade9642e67bcd424aeb335672053402af0e31b2bb0d70bd4e03e517febf8b9d95ff31a3f4c3f00abbaf264b05d2da2c169ee797583361935b7fc599ceca893567ff49b956f40128eb32563f1843bdc58abacd6c43d7c6af624e86a59b9a6479851a39e40ac7ac67f86d68861497da8b448d2decd8a40987a73c31496d43d239fbc29926c66933ffd9f1e700eefafca276a7095f8652074cbe110409cd7a7d798f99f4de1153dadcaa22487520bfafe7f886b63fbccdf10ef7c94bfbbce638be4182b0854041b2d43d37be91d3d32ba4ea10c98e372263e42621eceecd029c3f44ad64afce8738c53566d024a609e1bc3a32b5ac16e54ce432ae8e8285466bae12b1902164b485a1a48ff30f6c7810af0cd57bf0cde8697f2dae7a691114d5e87e0b48ac5d288174f89924778cdbfb5eee9d30fe5bad769d4487256d9e0cbaa2b32c4a7493a06b5538964334875773b00b777e4460f86e7401df5b60899b3725121756c6972380eacfe181aff3cc0016c205028e78cbd704da324d19b03d9b47779a180546a26250973d16d1c3cc4a41d1b91f76b2163eaecdab59a0dbe2adeb0bb545f59c8964f0a6d31f275ea35969e0da21b85cf12cebb35d14ec557ab1e29c53ea5847283f64b9c2fc9f251bd1ae83fd6498f42e91a0e351238fea45ecc84a71d77420de007d0e7525756f24e1490486a3fea79094c55a8c1ce902010a2d3206ce2e8271ab13afd9843ca7f8b7cc563187c2a0c317cd821f4996c4f962f1bf4332e856081cfb976ed30354e15c2a782c6e4c852c22092fe6c08cadfe1f81d0288ab368e73c73a42be532de4d195df562175429131c8c881b2e89e4c5d6473d0e4ba0b61f4648097429c73a6ed58e6166b6b491d87c77ea0f4e4ff80f485fe32557e67483f3cc700a20eb90e6c10c5f17a9f3ce14446bb1114e155934b011ce2872ebea1d426d6f14a8a891423e3856b75b60da57c989adc91f2de5f3ff9111fb8aeec11cfc1e2a5c3fd5dc212a8e50808dd69fa15ad885afab7f0546944e85d81f3d128fc1dff7b4694717349f1d0951e92d8998f0f9f6fa6f0d603f9eefac26012688471eb836ac54e726d7bcf097449070bc2848ec111bca49acb339a6c244fa29aedd036d04d7d5eee252274a18a3ac6525732cdc52fca668e830bfa06002fd0f3d40481c976cd5edfda24115b065912583d67d48505c38f14c03d610c535a84da944670a326553e982f7459070011a1bbb5e4a1659254a88c8ec7d2997ecd6de8c4dd99c62fc2043af5a8ee16dcb71a1d0251da6c5a03edab99df584929e3455c38720cfa2fec0d029acd382d704d34b994b171e04f27964fadac349041d7c2cd6ef3505bb4e8b8a3aa443ca47fa9b3e438cd8565c10d23c01700961f0acd8eea64216531116bd921a2ec599d2f3615a8aed6d5dc7c6c7887c4c27abe3094525b9e0ae349f162ab37a7ef780516e0d82c00034a2a482eca75a2724c77c462b6ee25a0be4f868a76d5d7d1cd20e4dc67cee9c50237cf8553b666f0a7ae429c9c66b30efc730cc30245bfed3b09d466a9a76212ca458041142554466e943e3214f7e9ea8f0c003b36b92a63414dc7a74721d4d777e004e33f17791f12d8eeee6f202ccf49fb37ef76134be4ef68191007ce3ab2e3258e14e01f54dd8c027a8ece2e1c98e24aa1f54eb7c9e11130c97729e9e83d091f932724dda16ee23e37ee2668580aef0501b6acb65f3a3f12b0d66a712ac200ece052a28d3c85103c0189d82ef1494a3873de0dd51aae6dd8a3ff641b1fbcc9c0cb949bba18fea3a790581d4d2903bae2156772f21a899c2cb32665966e394a152cc986298a881071d21c75a88a9ffb681c76e7f243b216a035f4c99b98b6a1331b979d6c2f222df405f3af73f5d4d9da2d023cef6ff69c7d4d6e65df8fce805194b72aeb1d815c791ddc6234b3051db291c6452e20d771486196ee135410d7b0104620d0fb07846cecb0073c4fce0e32ba7c3656fb4a568e52302e62b3c945e34852fade76c592496bcc09925b4db628fe095fc41f6d5b76d3f1fb5720805af41ecf54bffc73b7e6750d6464337c5ea0b9969d44186b426f5ef5b58127ac3f751c762034743c482d6f0ede5baa3af2b00d589fd252795726ddfd0630ed70c20d08417d788ab24be2d7f56b46b0f5b621a6acd5d8331da4598305803ec0793dd7c736d1731f2b3f6f8351b4d6d234f473a0abf5fa30bbfd25b8c754e45ae752add0ec51ece5e1cfd38fbf529b67770dfe13dc01abb4dd840822158541b172d7d2e9673513218fe102c2020dc2de49c0f35862d29ca91a84584db59d79012eee1e4860d7c8f7b3b1d5bae6f059383fec0391fc29b072f770c067d0dac5966b6fe7e539c9b85afc02fba7757667b519ae0deda6acd0155c82b41bd7d9f78e5e2bd2c91c084b0bbb4d82a0dac9ead9c448205630250e1bba3a1d860a162ab87094d2e645b9234e186e1f67c02aa31d92be3404126ea0b011c692962c46beddf67a246679148a31433c36716b8a61b0052333110dac5013cf61ce64a9e19e51d9fde8676b67564b73bf835a645a820a93246b8aefbe70400f550f037613a2bcd26c0520382a3bc63d1cca8f37105287e98dd47db71629562ad16e494659a724805e094ae1bb4ac583e76f6d1f92377e0c5f2a7247f327121287057909021483ddd504c0c081f22c1a76f4475c245d395c21d95e12f4d69b7ca4edf0f4bc23c45cce36dba5c0968b9e0d370c78e4adeb565c5ec33d4806bda3763d1333daa4868ffcb0109b9103408c611610ad0ba26b7dea54e2dd4a094fff7c3905050c950aeff4b1c0da84090bc2a2af7f29bb20c92a61c2369b4bb49a28a998d2eee05dc35708b4b30f6a8f7f92581b26986c6ac41312d35ff62c44e36d34cb95e77c4c7cdb0b93ca2e31b68d9e42896867159af3c2ec754d4cb3a8d6e64c79cfe5a24ce8d30898352817709e130eef06cdba2fdb70422806bfa5265e526456e59d75e7cc0f52f62c6bc32d1397a180829cb3861e4f555f2d6a45ea15c378a464845a2bd3e9c0518394e76e6a43d5cd370a473fa83354addaa4bb027c990770eededc2951befaa3d9d83c2f34f3c20a5eef49faa90e805f6425b106901eabd12ddfe09e1f7782fb0f34f795eeea5a66b1d64a96718e9c718a7c7ff47c07e9ef8cefa99cfa0f6016b0c0c42b7e8288cf15a69abab0797fad20e74743fad6f895dbb7d3d0e519ab53649bce22dec3a28a3bad069ad51fd0d96b09dcd78a03316c518607cf36a56d0a7983fb4431a12fb6e826ad904a20ee8bb9d891d8a00b4e57a79538f0524243aca8a80c00b252460d248572c240e0debf83dbec860dbddfcbb1788dc0777110c3d03d3036f083979d90f758c646c1e5e9318fabf08a59118e4ed36f20e54d8a15a2de40fed0066eb5ae8466c087bc1791bd9bdaef680c222a27836f3931a724e9c42d4710297573fe3b4c35eb2321a6c63b173f50260bcc2555a1f7bf88f6e7570a446600892bfe898cae286e3a02f0688fca2426a344135649340e4179cc6debd28489129da451f953b93b5d3d03f0660b9de164cac401d232675936694a76d207fa020509372d06253224e080e7778dff71cad656e549279c0e17caf915f448b20c0c0ffceb4e3fa3e7d88d810408311a3f92a3e2c4a46e064a0d3a1f0e277d17e46a653217cbb97bdcace1be8ccc4ce64eaaa14cd3d22cc0e38933bf8c850af95d9f3029272b210d70ca536345a750e9d4965793507f026a2a8f1b74ca27f6782194b99bde9a70709c773839518fea94de3fe83f5f8ac7c2b898de7be6b7e145df0a8a15684aa93dedaab16a0f70a8537e3d4c497fad0c338642e48bb8306bed1626a5c866bcf71ece2dcf8c78caad87167a72f89ba8110dc787121986233171cf6a5c3e236932ef3a41976ef3582edf57568e4bdecd711d70090a2fdfeabedccf3ad4f45a92e94219508a9f539543050219201c414df19a53c2df1697020193984401a908470f1743adee8dd61475593c35b09ff1f4734ff230d86856b2d18a324eca318f129217591caec96c99ba62e44838bb8157441998b2fcfc26cf816077efce68200f48dc7cd9f2d99d13732a3d2cd9089d703e3c5a8bdf9b118b32cd72bc94f2e331a95e4cb4652f4f94567ecc9496ae1445d2e1b609878f8bda35cd4b502f5f008c09520f11469b372e0f5029af8f9a9b2a895f8315a98407afc605f392fba4932b08a64a3159525e98f4a9e8d4ceed6a4508f7a8ba9168eeb961c39455ec02701a1b1058619195a9adf37419d3622140324098ef5c345e886f149b704bce0c035a314c050c2b29c08d09f1fdface718c7f90fd0fc3babfb4b8cf1e08aa909f9c7b8d03c356627f92da4cbef6a226142616f643428704ced6ccf9c5e3c773b86bb3e47a7bf7d91420dc9ee30ec9e825b465edce39f945b4621a1444c54cc82e19759e9a47e69785c9c1ecee9a8469854720a941e1f1b923f4b8daeab88142c6487e0bb0325ad713ca6bf851dbf72f553345ec15ac4a59a363581e0e4e372091d1a9e0762117801c24822df476587d9e56f363d18a8a688b6fc11fe45bb5f236905683498aab95b51735184fe2df922d01c0fb9f6760b0c0c93c51210348176f12bbe5f767e52f0b4812b5e56e9271ece9c6d093c7d3883373396805a53421a5797fab0d61246c4b41bc15c0d2bdb19fa96af992dbc29fe02ed241962217eb807a14ae97f1542bfb8ee50edd9c6d3dede13f626c44d65c9192ec1d655b27afb2a1a8d07bd458894d7f690d756c82cb3b69abfc50af7828a864049b20fb0799f43f4b13e6599f469a8ab002df3551854d312d095efb9620348b0072077e6dbe62bdd2c37037a7d77085bb1f7c22c7adf4bea0913bd17c9fc24cf98413bd31f5520029f9edccbc08c57fde2f983dedaefde182d4b0840cbca1b0c7cfcfbdb2ab0e6260bd77891a7e42577b68ddfbb0b0920c1330a780985423fb50255896b386bf42c352a70243fa07e8bd19badcea30ec058147f1f75e885eaa3fb0210a3f61bea36d58013631efc42571abc8851ab0e6d45f90b4de101b8ba2510268997a9f943852c4b0709fb3f249fe1ec43c35d86e2dd456f7d2abf8f8d0c263cedf8098cc5f12dc8312394ff0f375d30a9d85ad64d1263abb16b94e64b7e7e02dbbcc63c4aef380a75b047db5c27266f61a8cde8b5656af24114ba6e80651a5b98bb5e03cf5512219ed0d7b5b2451b0b6abadf8cba193e48dd3d50b2145971aff602a762de567e2f19656892a1859a8c7bb1c980e2f7088172f928074c28820b87c6a368e7dc766c5c97634f323867d4f471456ef873218321a1649f114327fd73a5044698d33930bc2268c2fd81423b0a7749937fa0fc4665ccf0a429107849969531592cf322d6fe35fe3a78120d630d674925680ee0fdd3942282a3093745a2efd79486e01b0390fbdcd28b2be55ba5fb901ba43a04d0d085a067e47723d6deada179a977ab81bf6eb0c1e6d7a7252af6f7e693f621920ec71a20202ad01425f647ce2e912151814064e31304a7098cdea4a68cab85ee0fe84fd5cb1571883379018a814be8283749b40b82913dc3694715a15b86fd147a10f26c418983ae684a15e150b13fa250584de46889574c5c192b371765c7a30abd72b51ede260e89fad338803892522cf79f5354283abcc4eb0460710260ff0e86e0b6ef3b1b9cfc3bac4d48f26d29ce34bff7c6c0edd6ff4f9d9f931947d752298563f6dedbea581b619f03a03eeccd1ffc42315d5c81212c798e7b2c7605a00eb166c47ce80ff49eb4a98261530bc0b6980dc7b57c1b89f6a412cf07290c0fbe5854038cf65d292cf9196693f43c8886145503411d177c40164cf0db26ff7218986bf322ce9bd396308f2fd39f73c6dff98282da845bb62a2c13dac088d815e214fb0ecd419645e8baa12dc1a151697e78bd46c054799478a672c57817ae706a0c89c81829af126683a580a80322e8eb69ca06c75bfeb23280e6f731d9bfdd641d1f91ab597a3d100382535a3fff82050f5cbd042b1cd76390ed37fadce7124a8de96612a502a50d817042b5e1143e6272eb16165c3d2f84d284b10e3201c220864800d6336e620ddf47f1a83aa76b93c55aaa0d62d211d1b2ffed7fa84b65221d048e1b07b5eb5c3ee2e8e779259f925a1922da12715533f2bd3eef7df9bcf8feb97bc5a3921e4e06d12c54695cff4c458c3203cc97b12100fb41b6cdfe695a68044f1cdd50a3377695b43671141d5c91324df45768b493540eebd4c8fe3e9c439b614e9d68ada568bd82b1f1fcd1e87ce2533012741f26e8682adb7c4fd20719f880e8274eca4f2625d6661b5926cbf689d7318c86cb22de40203bb20a53d9cf0133e0817610e997d6fe70ac2d17cf87baa10e278da8217d2e8d999ce1623587af3c494be0117fb032dc9dc06e68233cff3f0dd27a2b32af962025937847f0ad85a2d2a2bd1022817bc11e8c37022a84e9515c30ced19a0e4affb1068894eed1e4245c78588df8401463b8e8c48d076de0cedc159f371ac808f0549301d1be82367ea9d2367dbf387ef4d96b1dedbd18da6483a8ab50c7d2c7ae4e24fb6b1317f1916d7bbdb5b6f796294929a59401c804fa0438057196f3349933f1f2819658b4fee681965880df7af04d8440f26f42a0251637ef0a81e45488051402c92839054681532d69d5d2f2dc90a702f3ebb3782b2e49d0a4c1d56ab39c37f14b2c74fefbeffb9c67f1dd5aa63764f1bd891068d626eef63d6dd6be79c3514a29a50f7bc1ba16a9ac1dccd1a8d6c759eb69f4c4cf292aa3ff404b2cbed779a025163bffd91a3033f13921d0128b9c3f11029df89cd709819658e87c4e0874229c5f083473a1909c9988baf938bb79daccdd843cf75bcc7a46cd5c21cf0d55088a5486d562d626a2e038fb9bd7f9251639ef7a5708b4c4c3c2f5373516aed70981a62ba4d4ca2acec0a74d2bdac492a115f2f4e33c7e30e4e97005fc388f43a0251e15c0b7409cadd0593b64f0e3c7ae569bd11b95d107b2cdd9cdcfe07a9c904785d6831f671f7d9e201c679442d5b8214b96ab5a6d166737a10c33b8c2daf71f7d1d359801415139f29069d4aae6a88c3e08ab4f54462945f3146bc628c5ba54c6a3420c693e391741a0f45d29525ca6362aa3162bd23eca349729a5162bd3de92695b65da61326dac6aa32a2cad4af5c8c26816955d9d2c1c67df4ce619b0acaf36f77a8b053d69b5b68d70ad754a29e9c5b5d65a6748cbb45c20ce0dce4d1326d783e0cb19e802fd5b2ef0fbbe2dd0fdfb3e07ffbedff77ddff77d34ffbede0d6dae8474a1b65c80e1ab51e96027a47d20e9f74412bcb737b04b65386ee07ae5a64b03adffc219785690b3efe5ec0b6dad59eb6d7516f4bdd09dc918c27d102667d3c5a2ce5aa1100c9cd9f77376e517c618c2bd337b9f8394dc2fa9e408c42808d09f1f1f9f58ccf3e494dcde8d7d0461c6cdd3fcd7522384ac086893e7a51de34591270cf22543c50c92932558aa82a044cd468aaccaf36b0bf18e90b0f5755164bb60b9918dbaeb20b40a67976ecc2aab1af1939bacaa5159bcba1e567460dce5865f27fcc0e11d1d9252a6e16f61c1a1c25f8eba1b4e2720e96ecc4047c39549e9bdf49b378cb2e5f2e9bd342767fa51adf7e59144924e12279f6494bc222f35c1478412a214a5524a07af3ba5b7d6da18c415d75a6fed7bef0b570421b8ffb5cbf5a0cbbaaccbbaacb51d91dcd0f08987b329cf2436df1061996f1eac2d90087bbe817ddfdfdf91b819d3d1795007a66379aca5bb9942064d15304a05359f46a728569eb44d9eb6d22c255a9b29687337b1e64f3453a723d368509ebb9922ea7890c953de0089b09d9452587da499e35d0f308d844c1f164988f4463f8c2aa62dcae6cf588c3eed7b52f8320d7757a67633b68b3b22dc2894805279542b45214a651b819c9747ae89053563138bde8bb1e6c4c2348a3ecd180dc2b44926cf9f646a0a39218fcec79c53448f6e3336e73b181c7360d5559f462dcefd7b5f1907b6a3e1dc8235bdd1bf30ef41d847c358ec852895efc766cc81e07edae7d9a111fffbd695c2976fd83355f87bccf3633ecd033fdd99746ef0aab719640b5b3b28034a09ab0600e3ac5ac1f2efc5527acf391b89d21d0a524a29a5947af41863a40e23019d3f547ea4b49f48e9ffc51e9640668a7f600fc3f0a79be40f5591239ffed090867f22099147558e3a1e577439ea78d8b29492f6c1127e5a405518d010cc0f5f60e18702ad4cc310ccef1d59b9bf7f4265ff299529109fbe10a57287254c190ffdda3c7ed848c91f9568456fda4c249ef274e1f3df13fbe006f1b6a88b4ae6078099524a639f132c3f9c1ff366bdac1c1b8a1b2c86acfa54cab2c330e04d57049ee6e59a822c9fc2b2c33c90ad6df73e4b2d1365b24c94c932512626cff33c0f36671f9317e308ad58e9b7f0dfe77d31efffbf0f9446a4620cf81140e8400027725cc1e7c5213410d580b1f3c2cef5b6f527f6b029a379d4d06bf2b6954ec576f8482584165185cc81de278de42979e49091e7c56f9573402b4bab08c2fc4f08964f2953ad75de7c6a9003949356ebe56ca1c1729c1e6487ad30e787821966877580d62fe8a3d5867da005ab175c08d330162a4cb19081152648e0ba2050c2cf8ffc606d67754667606d7db0398309bd664b2670c12067206cc6113aab00045b71a6b07ebbf581283e0b7adff781b0d8f7ffdf07b6e82bfe842083a168b5ba6bd7ae5dbbd6dafa5c37fe4414b0d88bca309531f1eeeedd7dbfb00364875d20d7264076180772f55adb739c295cdf4da4710121c420cbf151b4602f63885961cca2314bde8b711c48dc6ece49f3be3efd03e40abb40761a65f65be009b22b029700b9c238e0ba710f6704c1d26c6500c13cbd9b0f27deefbb33005a6a8c31d21e1a638c93524ae79c524a197fca39e78ce055d7eeee29a594b4a5cb01b536a534ea9620020b997e85491fb0fcfa424fa635c676b5d61e02e5d4c911dfaed7bab7dbea2bc8551be4d65a6b071b88d96c2d6d3392522a658c33f651f9b4adbc4f459893f63e398a3065d3a74611a610ef6121db0a942bfdb10497e6a60a164010ae335368e09d1c754b6c10b26468c9cf7cc57cc0a33982a83636ace9310325ac19e581e4459e59458c1acaf6ad0e890b703ee86c38028b2e490e3f4af42f984cbf85e9ef2889c9f437381225d3efa15467072ceb14900c9918c400eca1e9dce2c754d036a875c67ab60a1098b1ded5d9f7bb17a432d56677c114b308aa16a88a8b316d123420cb076b8cca19b92289cc596bbf0b3385fd2a650ebb10fefc5906dbe8fde29cb30517664cda6e42dbbbc2d8fdc279fb87727790dc51da2208ed42d8c28c4d33d87fc66c37be48022df2431ae55611b584a6daaa186090d279dc6dd246bf7c929a2a16a0536295fb234ddae44d42f5cce96c61a68855b498ac7d60a64091fb63982968914cee1d9916d16c6073650102466cb9bf355310205b6b6f3b30c0c45ed2e79f9c78d2d5f034e5644789b954e4459e4588045024c7a32bc28302f29c31223a7444a0a6bcfa4127e4890ddd90ab2058f29c2ee6deead5ea51e1568e3a5c0e8fcb72649378f21f42874322479b2c1b511b1c0d484a1ce98238d9f16da67edceb6d6feb850f928e3358885c2c8135c851474423b6e496eb7fe04384f3f12ae3a6492e0b0e880e5c0e2e494bec9054e66607ecca5197647542d2165b7b621f7c0f10313680d8fac4aa4f8dd120269528e49c737e61c03b53d02877973699caf5eb912939b97aa02b536597040a923133daa54699866badd5f5443e902538cf9f178c0f348c82941c315369517555365f6aadb5d66b0208928fc5726ffdd166c6114f4062c8406365bd30c13a39ea86cce8b0010be1a8830e6d668aa8830e51872a57720765fa5016cb6245122a93e7bafef72bd8ee0e59cc6f8540402d6050a10513a2ac6e1efc0e59b45cdfc2e2e90fc6d32b7cad5086d6cf0fe7cbc0d3f87d701e7c1c02e1bff18938ffadfff0fc38e3a900e8fad6cb70f3e0afd0727debe38ce6a301fc3c7d03e3e9b005d379109623656ea776220492375cee569338133a39b25593380d6e007814df62f6fa48aeedcc0e252b382526b7353830d181e54283193918013021b4941dbff00ec65786bb9117de8b2d3dc563faa284f41b4a5136483412e9099509c4e2949cdcddbdb4b15f8230075274319860f178aa429f74f885773096f1058ab6018d6c528c9e8424a1aada419d34251195c8eaee9a17bd20b272379d214496106cbed4154be0dcfd832c72ca5319277a228a22da114d116df19c6ea80c1540099242c38f2c235a9af8c8aa4f3ea6ac56f23eadf4eaee26c25b41c4abbbd5c8b29e724892147a90453e794a6a31a168180e11a00e209334eeee4cb25b186888647f1fe92c00910c416273b73bc4448349077ee11d8cc15c0c0d8c24922a4d92c0f9d01569a67a1e478d998f7dcf3f56e3e59be1ee6aa048a9a9f23e5689da46c083082b5da640e181c30d30b2e2aedddddd29bbbbbbfb008c32f0144594cb22430d0d4e5092ac104ee28edc6f9c8254e9c18d26f063cc8b1d992623849850e130c22fbc83717577f727d9bd3415ddb6dddd5d2bf26a489ed7dddd3d84cd91cbf194232ac72ec7140c500695c80b61e2c3008ec825273a7c680f382a8bf7f2a8b92348deddddb5585159bc4d63891a2b563236e81e82ab4153ad1c4f6a6892a3cb0d1b617ea04d4a329c745c434b4d3868b1f160864809ca5403132d73c5909585e14de50662726068898d241c0ba66ec841452bd9dd1da57a3ce5eedaddede32877172f7a9ee7fd8ee7799e07656acae216ab30582740c96a9f4bc3922e3d2451221acc3883e34c616e436a6cc2285124a6c84a49171758d442684cb96cc8a4f0306607aa1c948acc7ac966249b4c9cc4d81346649e7a20e26888531226604cb46ab40dcc9114a426204536a8c90e83068aa1265c80809172fa0189105eb8d4ff706e53168f2e6eca621aa5298bf6f364b87b12f8857730de71811954882405412244d59123fcd54757222044a46a4c58b2a829822ad9d012a5018c16b91a8c9098783182238a1126391f3e8034c21c6a909eb0aa9ae0a8c1a9c88858244911270af5d92832253b0effea1b1b788110a6a2226e4461428b3041e2020bbff00ec6ddc207550f573d8e7664a14144d783148cf1d32a397346b1577777778daa298b57970c46f3ca5e0ddac7a8503b15819ee3647777ef81fd8c79901be1e444caca1235e2eeca1531504f8a2013832137c8907aa1a4de3a408316207043cc90ba3173214d479a68d89abfb2cf1d7777776163ca220a2e8aa62c9a303f58cf8cd500e430616b311da9b5d65a3da1d695a617643111f57046a756e5c58c5aab8f6d42dd460709221544698b8e11bb1e63765e70bc204812cecb8b2808c9a2b2b8937ba8bbbbcc8f18418091d61bdd5c9258451db9614b1219cc1a97bb9996e47a67151749a8b5d6f851de08420ca7b2bc8175e0a87312e5c56b03634fd7e9ddbfdfef356dd98f3553570aded727e07dbddc46beceb35e987daf7af66d0669b7cdb1875622703e4a1f1d932fa0b5b1e2c4c88255182a1556c4a9127e3e90b487ca9ff1f5f35b744ad939cc18e62b6fa65f5f880d9b51b85a5bab7dfa82a541c01863c08936467f8c0a88df305afc184788dfb01847a001659a74a580f30c6930d418f769db3006fd18210d860e7372186308f1a7eb0132cf4f4102d59ff19ac5a06fc3197716e352995c9a4fc31863b443b4ee501b325557a022a681ebd3aeae2949be1604514a12c038754c575e3ebc8078a1c8897107304612c00ff4ae370e4511a38c6529c34053f93d8eb5d6c63a5f52f0260b18e527bd79a9d7b6b6abd31863fc3888cd39a7847df66e0e46f1a2947ea05727a53bd8b6b4e0ced266a905d77cb560fa39d18267964fb0945fafb7957587e21d13f47ba231c618638c3f395376ce1489724b9a2012972472c9d40923306755257784c4e1ee44f88577300ee21244e5ddddfdea68cae215ee5e29e1d71025f88577306e777797373e1c381f694565f17eec7bfeed1054eeeeee3edd02aa298b2b5a5075b18e1012b92154586a4d4d5c8045082a32dd309811641342847677efa0a62c4a5d396147e53d15f2a92d1a04993a1714e92871211b09ac0c1395293fd65a6b26eb4ced8cedccedccd119dc1925ff711d39c0ad8b1256bc50f9e2845e3903c4992b9c233f933b33054cac6224cb888332462c2db902b2e668d69487dc3fdcb6dddded4911a1c9ddf5a88b974565f1d2ff6cdbf9f677de5a6b9b9a9a9a9a9afcf3aabbbb0f312bc5e7f3763089dc94c5249e6e1251f85544ad64f71741d9bbbbbbdddddd835e4c05f99282d490365a4c115ab374e54387636a87a0a1dbb6bbbbbd1a57b99b76776ff95cad56ab41a34e0bd4962c845f7807e32309d861391521a6098c5d769d0f306cd8219fb7bbbb9b899ab2b8333385cd6c7999b1fa988c23b283eeee6e7777e222bbcf08aaa1e92a86509a255758a0c64989b8866a4dd59aab3564d6a059d3e6048e164932186aa2648c0d2261746013c4a6c7c7235afd806184c88822a66aa0558cb061ba113267c066c7e607ebc29d884c326531c9e92641e1d7d3113e492df4b1eff9bfb6145bac902802cd922748521ffb9e3f2297fcb8a1ca8335128a8ec88c6e1caebbbb3bcbf380b05154166f757777ca2ee4bb2a4308fbeeee2ea6ac8b2dd685958b30af777777a489425298da4d044a6eb9a3b75aadd6c3ddddbf5d8e5255c264f75d6edcb3e6a836148ac051439235350494aed6dd5d1486cae27dfcc23b189f08f2f486e6d626c84651cbb0522446eaaae870bb11f23e7289036445910242876e434743b821a5a11c28832c1fac5aad964346dd0e57d93d2bdf74c9dedddddde3072a2265ace0b8a409ca418a145814c46ddbdddd3d765316a7b67828f4b0f23c21a718d36b19ddad8425c8930d310c9a13c2b439414c505177f78b2a3139f3f376497584c33bd8dddd7dd61d58867b13ef41899101aa342db100092caa21546e7777370e57a62c52dd2a7cf53a0107329fa3a3fb6728e94b1a2a22426a661df58407a3784583cc010728ddddadeeee0622f7d2dce249b8bbbbbb0c77dcc7bee7bfbbbb5b880070ef79bbfddcdd3f1b99ecee59777777fcb3da6892f4b9bbbb7bcd3665f176845b51539ab28e5128d65a6bddddddbbdbddddb9dcb6ed5efc92c4fbe284bbbb5b8816625fec284266aac8eac89421703c14e0daf052c7e4c63c8d891ab3731c5edcbcc021b7bbbb7b12438680d035b8d882cc96b7239c57d44509902c4641080171932285258503899a23469a50d5afbbfbc78edc4de21151dd7d5d909b4e45531691882471f2154e50eed6ca18efbdda56cebccfb44878abe907f641b3dcddc7dd1d0cd49445a92bf60430546ff31664a7eaeeee767777cf4daaa84975a552edec6d676f782026082d516c74a0c14a141f4f3ea2dcb6dddddddddd9d642a462921acdc3d558f1a2ab70d1fbc447ddeabbbdbcad19445dc55c2b9d7d347c5765316575899fa31617e2d1e60789ef7de7b34dcddca165a546523cafbccee5134518811a6bb4fa4914a51966032c405162d0176ac2ea4ac5c8f2d00467e382228490f57372814a733496f6d54162f7ee11d1c31ce9105cd910198914aa6bca06fb86aeba82d554170e043dee004cace93b7578aef3d1d5698ec2e63061c14d5c0d45426aa88922c3afac449e7eeee3ba8ec8e2abbe36a0799d73b88845f7807e386e163888f23f867654246da20e5081fdaf35f9757a8042ddbe94987664a000008041000b3170000200c06860372244ad3388e393e14800b4e6c4c6c5c32938743d2301c12c41084611804610086611800621880c2784e3bcc04189cbbeded30096d7e807a50e262eaf74e7fcd97e46448e01cce17796c12d62dddc7bd4a265a908fd0e15de909197f9b9620550dd7cdee4bcc21cf2b7f52ae0ccc5468160019202f220abfd1a8dbc8e9136025c943791f88307f4b8404aa9d2c23b1bf60fc23cf5139dae73e7a2502288ba7beba76ad4ff25284564647d74514b0542d6026902d45941b3c559231527ec4b685b7e58bd223d3eefcfb00f6f782355578b5670ed313d80623899f90236882004f6fc28f03117204694e726b19dbdc5375bca2f774dc302a6edb29ad40efeb73687b0d86167ae4f19e7ccee4b7e880d37d1563d0ffe23bd3411bb28e46becec4215e12c6b9c2c5122d94f6c5a392de2f1c24d2936bb5165e494f7c955bb49e6fbae3eef244eebdc190ea630196d97c1389c5fd8118504628da7834da0abe4c52e57fdf10dd43031d81e1440ba7d489bca215491cb24f5c38e8535c78ba51f4b6e4357c875cb92504f685a7aa4eb8740ec916d89250e806344f8c3bdd314d424b98a573483d36d380a0e585ab1730eb50eba47543b94f775c83c7e62c5e16a337d51125361cc8fb8c0aac5e1ad8292f360242013f7ae8d0d9f0bafd33979fe1fb0c91399679e573c5903b1652b9eab0d62499cdfd13785cb1b8d972db0a101792a921f95fda5e6410ce97dd8e81b5c27825720e8e9e1f201c94a620d1eba8924d2384a17a4e8ef937516e9245b3ec7828ee8175e2e20ab1ac58baeae1064734d6b7cd783b14e3fcbe2c8fac3d93ce03c6efe42328ca3227fc20a913d5735141993c7601dfaf721d6ef48c4a096ad913d8a5bf0178c6b807b5258e4a3a9e2e40d7cb5ddb4151c86d39a4d89f582776ab4c17e716f46a72d9bf60e67e895e80dc56cdcce8ed8a5c20d3ee98c6610b8b2cb3c705b2c20b547207957c47c06320ca5b7b94b16a270c1986a662a26a63ea6db870256d450a388f5ab6cdcfd6b8922d931950009e6ade2186ec01219bfe7e8ad5547f09012e4fc902f2c9abac0f47266ec4e5ed04c28645794847400a3f5793ae9f008c583d877eff0c48bb0bc33ec2e86b726c0845dc8e5ecbf1dd82db0886645f26749af485c4d3b4f6f5a4528d3fc44f0299dfa57393dc59be6c4082c291295497735a452839edbafafdd18df7fd0753970a4da1719196e5ee82c8e2eba391f60d8b9f974cab333f0a0dddb606a10a3d43ca7661160dcbfb3b94de91730d9ca3676ac5d758c5b7db8790fe502e6a27309c4cb2cc686e2b384f45917d81ca6899769328551ac94f3b65fad7604eff4e30d9094c086979bd02cd907ccf65136e347a984acb9752bc1f76fea3591a15aa5024a58cf7b74e89e2b3a9a250e2200510f917afe871f4a3f5e8ee6219d817d15d0fa4957e2ba71f2844a3f9fdf800b5932ad1ea8102497ea0a89b0ee15998cb2ad8543dde82a6ad366f77bb6fc71edd0228d16a150cfd56c7c6e8b20b8afba9498c87e41aa05ba3cbb3d1ac82499da03578a36878940e6f36a0f44005dcfc78257befdae0a1fb7f66a80d6e070f478cda8400415704b755c1abf4b29e138100b1b35467d73154eb7a789735578912bab30023b12d490cc4ca48eee6801a8f585c76e7337411ed71721bddeb1b2864ebd18fa861245bfd672238757fc739ae079242aa2d7f7ca1c729c80a888eae6b17d54b94970ea76f86e6c127c038153925d7f50f9a3f7771e17705a23f5a3adc32682b2c265a9aea26ffe21aba70728c060b8d9c2c4d7e53437a13f7f0826c7433620ed2e478b998b1cf4bb48df00ba7d85c07d3440d4a7205d2b428ebcb80b742254d5b09414ef1ce48dee71ba523c2009d9944af6a4204a426b30f78da09c8a566404e065702735e98da980b5645e6a68854021eb71f59e397b4f43cf499e812349ac58990f997341162aaf10df805ede87471c90a4848d2e96aaf01eb95a5eb9a0c31c50287db95a850072fe09f4a4deefd5708e898e97e763cbd0c36c72e49c01fc06e4700290d434efbe1c787a1db0e549bf88015c7b7f8845ba393781ea24a1264230e1de7c5a55f85a17ab1f45bf1345abcb246687b7b5f50c2659159f685aa52a609422f84ebe885388196b73035c0da8c72ecfef551665d22de5151546a2c053cff27de1c51d2f95854e29912336afba52e296a4a6529c1daed34c6bf62faf1ecc4d9f7b337de4ab9b7283bc9aa5124f1d5eaca6711f4ab12870ad33c59b6f309e7e72cb4a97639ed99589d1a7e5b03fc96c3ac01b400ddde021178e720b213b87a20436380c27f5fe788380a449377b93d533d0d69f7c603b3d5bfb7e1429f3a81380ce3b1e1f28bd1b0146f4f9c66ff12f8b7747e65ddbb08da6788783ac957e5da9c816cefcb7b553a3cf716985be289d9b45ddf41a63d05b37aca76d2c3427badeaf012d6366811cb108629a93d57a13d2e33980a6c5c1f18159ccb5c8282b5fb8b2cb3cc7a2e62bc16b1f9e0552344627c5244fc08f0bf376e694c5da344a4403491aad52e2505ec839242427b1e6f19b00da8834f09b4ea52283a2f5bf5315708a247815acd5be42312399a002cea5cb007777b69e1b98409d71d3dbd2038a3b40ea8e632002e6420980564d651e164257ec87c94857fe00644fc4f1938a2e06695ad25579623718244c83785985f7b1fd7934ec438b7054af47d4aa0a29946a3a471d2a66492411ad5643d2db69e8f11be824fa2a4cba830d94e8f03bc0b7dafc7539c48cbc68a73507300596076470bb92ada311fd0acdfbe66819a741734e786e97eebf381b2a59e8d7bc8d7f9dd8002a05fccbe944cd19239f02b804ca4e1203375ab816afc1a330831fd9739a8ff45a316b293d6d85b3df9e60431cc27858dbcacbcb38bc0c9fbbe10c312e776bf8868d0b9dd3c7d161a03a329598b5ac8715db3b059e57eb6cee5dd5e745af42f74dfebc7d55cab2344eaf29d49f969eb982a7a8e189e2bc777eb9784cc6d84fd5933289168f9d6f4204b3f165312f397039e5050034880da9bb1fcb729830651aec0a6734bb526ffda87fdcadbf86f218997545727d5c17d29b2ee0d1415c5973b56132fb1f20161d857395f95a2f9367d1fddae45113ec1c3112a100a716f7a59f650bb0280d7b95bb5c8fb0a38e21fd3a0cae5e1f0714cf21b483a98677ea7750aac69085505c8d1a5c49cccfc8b80872bc7c8705be6e46864b14aec0d342c94a2ce40e65f67642ea7797ad60b1c20db49472a853880b8ee4036d0b60165410cc35942a79853415977f26b22afc618eab7d2c816337b9f67f06319508178a12ea950b3167676ef63439857ba6247b70adebd918a274d09564172e992da792cf1eadc392318d62dfc1ea762d53bff122942e792a6f1334385041d489c1f90f271e1ee399ff41ca16b356618cd7520d3efcebc8b93bc6c3f35790c58d897504ad7a19e26eba5369954282e5e10b6e96904e3332c9e0f39f779af3e711a281db2bb101e1a7a29528a8e641d32f62931ce63ca901ee85af63dda8593ac1e6577053530019f9b522958a14021ba444c46abb14a3581c8e7d82a132584aa448f415834a2edb06829606db422952719a1fd59c922cd25b3294b64bcb2d6337d69dd4475d3c40b8bbc3925b41310c1f4e28a2ee349f3fe0460c68eeebbdad909a4f54f54e35909ac452f0716d0fb449f15b0671bf8e3f3f488bd1b4aadd4f572badaad63c7a794423fdd575629757244432b710ca94a5a50e3562c78c1fa85bdf5d8871eb469fc119d04490f53c678f139f41f02c138c1ec724ed71e3874eb22ee90474d2feeac37d9e91ab9fc9066a10bddbee12a91f7e9c19db15a4a3c106a8851f6084fba3f21a18703d5e9fd4f76f5a2ee41d98e3537a20aa0f4666393910a72ca2f60bd26a829ba2f8864f3af656da045ab4937f1948a811e2983a81d22fae65f53e25c013aa872fde3efad204ebdb826369958731ed6ea2a69e9dc190278b3e048aa88bad8d7c43dcb745314247674ab97f914ebe3c7f6a07b4d7c47873492a3f492260b9d918412f793042da1181d1fa8014742920239230448c9737b2c7d00f97bacef20da4c9cdaba5248c622b96131b338f8b29980b363572f6f436d0b6aa64bc6c0abb68bc40f6e5d376e5981fe46bc84aacd863aef5b78ae8ec6dd606f18d6f769b2c712d0006eb1d00fd7bcc09a3680ec8089e67070b7553d4f0ad2336f0b169253b6c9f857d9643534554b4e23a67cf30d612e6e13782231607aaf7c233f112ff08dac95b95a0855a41b4d6b07d9d1285c4d3cc5fbd6603ba724f2d4cacc98da8a8cf61491a6c562888a3075ae3aaf206c3afc5b72454c8b494c02179e5ca8cd027da6091cd34d00e4b233e1ce9cd2327a5e335b0e8e0afaffa34aa6944f741b5f5b9cc62edb7f483ea68b1aa39a269f32363f599c3c468ff9a5e1e3afff265b9c66de8432966693e74e4485c00ac7e274ed358e3854220a15937868d1ae49337b80f7228f00fceefe107c097d4ee504528228074c7013c55567081c9c51fb55c1f9f266639822831b88f99b0f77b38c7753912c600d87e4ad7f24c5106a879a1d289bb7e868aac4bf2d228e22907d9c5e9c7134b1efed40d7a58ff193fcad6e81ace99fd65742126816d8b4d0dc471029fe137864494a5ca286746729cee8bca137543e4a8a0baba054889ab394e1521b362e58e03b1730f3342859cab82b399199d3a51d5bfc9dc61cd64f93ed90859aad1d640021385bd1ccbb6bd09e74d9aac83506f6c915b22feb5e41231dbc4174f4584bbe4feff36780415504023667be83baa01514f124206aaa2c7e9ad251458e4e92890678749d67f227f838a1e04f6d6b0b3241fdb21b7f3d4dbf6121b74c4dc4d1dea3208a54fa4686b7bd30ca5e45b2a5cc9fa4738740f86d12fac80d0335fe4326747ada5c8932fca68581e024b8bd510a1f2168308d03ed1a4d1265538f7c55b06c96bb86a8d3227eedd02b3a3168cb08184d4585f3650485ecff101252e2e1da1c8109be258056165b0fb66d57c39b8d9093376ee83f3bfd6ba5c047441ba5e2bb14c0b96d1f5ef1a3fae06d7c25357105f9a9b1f04aa8da9f48e0a75adcb1236bbb09ad59e0b90dea2117c8095a474cf14dfd0496680dfa8ed75e30448b2945816f4344fa24df67d140817bbbf2b744cbc34d3c6d6538862ab10dfac5c5b3d7ececa492d178efa97eea94b65701cc569223d26d68ffb2b41586cea90adc8b0ce7eae5df050ecc11c261610942ecc032cb1e59b73513f2550836d754e597b62efdfc541465b9dd2aecb9abceee8eedb2be0a39373e6ebd97439cd04832dce8baddab301b8cc168c88d6cb6412e52e09b2680ce46b48011d4ad2ca896ad1b227cd447c8ff1440cb4eeb1312716de03319697a57ab32ec82d41c446f64e514e19139cde2edc13435e9b50f33ac35fbe9fd3c6b76fc246d0731c6b8bb7a9f76e978e200dbb55da5d92df2895f8a10e1a8f1b6496316292c853aed9673ceb56a17414954d0efe86c900b54c15f21009b132449209815da846cd6b61eb83b834f1e1b6efdcd10c4a7fc49807e64c391cebc8159f004679db98abdcd88d2152b7c5c89722593e06ccc39bbcd94798a65a5070fb15368b7642f974139b2ef5830c207503010fd12d1b945af02f2a4ad4761f80f545866ab160b2853aca6db8ae3744b6a8f6d6ad4ca468249db37d764845af6e2a68632d496ac712663c61be95f592d6cdf51d1681dd2cb90ee0ffa851cd3092393bb85f72156b8947077b5c873e6252c32b090b8b8258b97c9c71148df4dd02fca4ad958af4683ab0d2308acb0548cdc0bfcbf0586950841cb6cd2ac370100c92caf862a989121d0847541ac490316b7f87f601c443beef75c399856bdace24e17578fc986f12509f4d0300fe7c541c41dc840770d54561dd83f9b0424a54fe7c139e021be3ebac1be033073a9f0cff8da733402615b4abc90ff4afb6448b4988ec2178b0d7c51a8f05944e4bf4d268620ffee505db97df5c8fa6ae61952d863a61b6e6627dedceae6d617de2082fd4cc22f05e90f5779079f6aa96974a51750831deb60ac3e955d7253a0b14249f05bba6f356cb9bd9e15e786c49219923c78440004c79cf48c5eab1276c1a59a064761fb118b3a09fa2fda8f3e7d33c3cec9309a45a7a93d2dd45881e382c50d519db36ec80857d0125663b7167addb3597cd74bcd16bb2f4a3d3d626bcae047ed7ccf090ac17ff68a3e6103fed68e832de9da67e13aecac99439afb749d97d08912b25a79c862d07b40eec4fb13cd7310ecb4c0a96fc7fc2c744c1bc40349b8001ae8388c90590a4b60ea8370c6d4b4a28561100bb4a48dccb410120031df1eda0b3e0490ecf1a93dcc000ad360f6cb515c9ed824cd1f4e7417943f6c4c51367d78543d059874cc4699ee821ffdcfbe145c8ef71b8f8ede9278f806dbe28e37445fae12b281184b9692f3da54148937c49598b94e2047bb8d7c038e480548963ac41f584d797ea9493130acabaaa85118be5d3cd126e19196b43978e5649791a9bcc12f80d7d425a32a2b71694a1cbfb8cf5b89bc45e4360f518d544eb9b88f481b824d55544d5ae18ef792166b40d2d6f16941a26bafcc0aae24f2f4826869a6cc4a9df173873059852d878910c0eb096b5f5847b65e08915a49d032b9f7cb7ff65a34117f40b15c53a5acfb20f42bd1c6a3b929be9442b9b1f79515908dfdc4d63474661dcc078213bcc89f875ed5f8bdcb867082221d5edd14ee2c330aab1186b1d1e3cd2db83fa282751acbc5338a65bd4c59060c289e8c190e2d86d1b170f3803e9bfa70751d0175bb62e528723fcc6c2a642694c53e9774a8e64f285638f6f8557024959e6894f366855fb92a35c25e0e52a57f5036a0967c2bbfa90fa8d86aec965a0fb56ac9128769554477cd30348c4cfbb0d5009ceac358a17ef441b5384ed59c51264bc63f23b01b40d0e66604942a34544d3bf6515e9e182ff1421c528dc6713a10e8a654ef65e4e717636d8723d64265641d0e40f80a1c94484a7cd43daea567b9a7c22d4326b9ba92e0d82fcd0bbad52490d4ba25e337fab545c0037cc1f12007856c4dd1806c14c35abc484c8b47b1d0af8b9890b5ccbec1b8535e7a933485ff37b4a20c41c6c80c4eb4d78c34aad839113c90ac5fe50882b3405a2e50583b76595887915f103066ba682a10cb3710c67843d58eb4b4ec00c98b8b928c4759fbe70ac9892c473921abf01bf4a0031e57962a321ec79c0832b8f5a68449b3ba0a517fb28f22cf27d119e09922d04c3a6ca4d845a2989b6b0c0aff4cb251a0cdca2c3a0d9a4c888202fd2a7f7118c82e41821e0d31f030537e35a480155c033ca5cdfef3bf7ece1d8ba221de6c28a8b5f59d9c34167cf00c3d6a295f2a7d069610dde9e3fea085b61a59d5e4bac0c5733998eb4c5d9e20e55a91340ba67d5262983e201d24838c3e7bd8b6f69d90cd171c60ca63a51a958df3677bf26f3ed7fceb7d2ef4b95e9928cee7bd9d005c62b9cfac114a241569d2172c631b2d99fafc329409f625046a2133c8a60f8575f934040e3b6692b959c95167ab80cc790342e19f60165c15264d7c443b637cef5f81f0f0ba2aacd0e685ee168c666eb499db7f02f93096cfc9f2a933eee32c3bb6e76bb109923c50249fe31797c265763c3e2dbc192e5c076d6062c247de7fa8db623da1e1867b6e519d16c85559035c83ed2c0bc6d8b7d11349fb9ba2c1772bb74b2f3f58c1c59e247dbc9e6bcde652199784b2cff68da7b2a5b386aa4861c21c51df5dc247881566cc5000113d1228ec728f370d3e3f1541f6b8821ff74c00ecc3c638c16f9cb8895b6a3099387ef9f3d483e060566387a180e2c82f30d33c85a139e5297dbb712c32ba7a485161e62fe404bb268c2ccd481ac5b24f058cd211b4e7138082a4a6a43e5c3ba87f2992553832115068844ca04eb0b7d1ce60c16d8349e4f6fa2001e82ac921b6864782bc1ad666fb95815f903a4e1ee8a7329a6003338d72f495de4d484ad1008c5c6d8af2f286960d66975b3eb93aae116bfc585ac43be912b9bfef34001ea86f687155728399fd59ff3f26483e2c0ce5f1933feda241a4ca2a5e914219b37aa3ef4fd99eeb455cfe45c139ef80ae67a5b8f4f3b790c18793fa2efbf35a95e0589963aa508e236859045706e19576ff712775c515e149a918d151f9c27c914cc20c759aaf30dd4540c7f5ca32c0d3492ee61a74178d0681ad457933ac2aae9b791abd59ad0763038226668ad5e02da0d60f380b6b878ef7a971bbb6165cf82c426c7e9ac54f05788734b35b9f4641594f04cfb8ce5dbdc81f0b1d9a63b1f6644002ac7d4449c7c8718d1ee6ad7d78db3f34475ae8d953fcee1cce333fb0aa5c02621a8e113744d18e5df3c4f2bedadbd638043a6399d57bedd6a173c6d2924fcfb3000fb10bfda2c452f46c43dcdb4a98718eb42a9c2eb62c61749105f2ad6256f3a4f0b5973070e9706cc716e528c209bd8c75b00a08d36970492c9967246eb5ed1a5da4c508e0b8510d6efde0c9b4401860eaef68f937747ef5856a7a2b2fc9e3ea533745900cad2c4fa05d437a4b35373252b823e0ef8130846b028913da8b2af7492df48c611ac1b270d4a4b3a410c147c79729652418b7f8370b52db8b224e3174601a7ca0dd3faa787e937c3252f5a6466edd2a35e2c35ffabc3eff4e6a2df96a4d5ad90b24f238829eea5e1f22584c543b06e03cb36a812b7288034390c217f5b8f2d977f875610768faf4bf4b8fa1a410ae5cdb45609c59cb12572f2bbd3cdb2cbb15d8e45b857ef4e78efe07b32d89721740d8ecc65f22251ad2ec421a14fd544223fdcc5e0eed64803072bad3da0a761fe1c5ff6519f7b10479b5241fbe3bdf28e0c4efa746ef19ac4a0614091c6dcd284118234012e618449f9122a8c46d31e6e087026bb664443b158d2a82fede724f51d79afddf283fdb07aabba4a648f85fe577efd9cc34ed7481ef781818224694fc341d81a3d518631c577430ad80a0c264b97adf3e2aadb634e5eb79ff1f57945ff2918f989dd31149ac0baa1dacc1e30faf311e20d246b8f00b53f628d0c5fac7e0c1264b6045191479b4bc8af40f62a900c156a35e2946df7735e34cca7e30306170b5b04ec52a8a58c7bb8cc0739b27872e0366ca4acec7afe6b96ca008e4688786a1998f6a934eac6325162bc87aed93cc5972f64e67ebb5a7416c73d671e6bf06b1729188910a7d30ba1540d5fa562753837a3436f3cf7d4d15fd32c930c19cbe1e1e78c00f5371c03454d491ba49f3c91414eb461595e404fa79e590b442ca6ea2ae3ec14fd97a731507a4f8acc80f70ee6bf5dd21d7ea58783b0c18482ad0aceac89fb2e085773239b3b476564c94bb536c4fffafede0c857d091c99a52b3c631d96f380ba51ce052e5484d038ad34f3ca44a58652f55b28da7aed4927084df7ef9d1fcac7a1b74801cb5957bba1ce17be31b91c66e587b7df5002de5a903816a7de2d0b17340e4ec84ce5ed6350d8a6a49d90eb7e1bccad4e5917ef8e5c7f919e0d88104e4f66c15b1556618b5a353d58bd3a54bf64ad8b38593ea61479b16a6b171c1a38a8a0acb66cfcd283c89e97c744d903888b0c8a4e892af6d3f9df6000566f41149c559bdb1417f3d4b4979469e99ac1254adb0d0a54509b2ac09a383cd7ffa98e0704d68944b5802b0916d565b33633b6e528eb91430208ac44295edb8e1edc33e030030fa4ff58844806cdb6090ba0edbaeeac66a9110829fa5144d1cc3dd808c4523cd13cf11449dd6035dae41e7b7b1584b1670ca74cacd13b9b97dc803b070e53f05f356544c36a6704f34a6b018ba795af13be0b5e62e732bd08e86819e190f8c8fa84772200021775655740b28ad8c9ab7187e7e95eb679a3ac7734d7fe93de257f366f883712ddda6ce6b689651414b0bcb7bd12777987ef8d50c246598fbc629ec3f2f4876fb7dce5e80ff901a98fff7bb39e4ad2902045bfd409639ba072f2779309d65615a123cc95549f24b3938f96587fe326250be72d4c862d62dab016596b52ea56578ed34632cc47092d7993369d26ee1d660b38c444b69f9bcc114b080dee9241a760056d49cd14f206684e4fb8466efd84f1102e3606653b49013ac672f714a2677c772f50dda8402a647018a8d0c31dc93887ecc2bf587c7443641bc43bd16e6cc5e02672a134af59debb424c44c022c36de7cf575b5ea6062a2ca6424b441d88ce7edf9c6172d58625e2e57815184f72317cc5c38513c861a4990891481b37737b59f77324e5abd436c2eb329a4baad3d2012e9183b79220e0643503098bbbd66b3b105aed82892eae685c0402f483d54d2c30a3f6a3c9a76d3549dab3545b8b143cf714d00fe7e33ca45cf651592f20272c8b9fbe74252d1e06f8c461bb240b0885bf0c979c6258e324ede6a0e588e8ece7a337b7576b068f74b283494c601e492ad026ceb3c6a9a20cda73d2c6f54ce60d990341a44f8a40d7c395b8a75aa4e61e3e1513ef35a2e7777eff00d3e7740ae716b00568315a2980ac1cfc3d2580eb74816e658999513970862bacc54264d004cd663466b04db126bf4f6b35b67718644258ce52f784d46b8f5f5177a4be2382bde1202412e4a3b74aa1f8459b340f3873a2549c0ad3fcf2313c71164c41797eab8697ac088f2862e8d87216ed54d4b8ad12397183af00a8aac5c468da084422a4920e60578eb5d5b6c3fed2ba934de2655b302e89c72a039fdf98b2f3952ed63de33bf50e4a92e52af49127eddc476c6749bb05e6a015056a3114e6c9ceff5e1f5d00021182b08b64fd8621a01c70b4fb7144c2a41ad7b864f62ae51c42916c70c4fa6a0c6974e9bf373591d3c679e428786e19d12e5dcb330bef8e0a7a4be1a5f61855648c1c09b18bf99f9faefcb1f855d55b736fa81f81678dc470bbf851f863fe1df2b364ce6ff5716cda495a41e701d136d9de08dcc37c069bcc783bc93e263c0b45eb0fab9e402271f5c60e9160c221372fed360ad83952b2057ee35c073bd93461c1c606be8f504a95aafdf21f60b314bd41969c3bdfdee90f5dbd8d9eabfe70a7cbdafc174c958a539916cdf7ff83272ee670708f176f44949ec9a89b69684c22ed2e5300e572aaa57951796775a82a8bb50e3774041ddc72791eaadaca513994f0b3feb6e3a09878719a625089956d351e113b382612dfece404728bf1fc3d032936138d95bbdc8caa3db95b4c04525657c90473e828ad9c82ae412b7087607f3b0e418b0466ec83ad9ccef083300cbbd230b6b23478a27fde60fcfd79919acae8139415d7b405e0d0d65532c6e3bc51822d25ae39e504b9de26aec2e06447f1fdd06152fd917f0bba98707ad72fece73420d852f0545fe2d633fa660c9e742cb3ab2968499078e729604866ef9f2d9883eec2968f3f6d27d0b27a84c50982d216c14b401799268ae169a8337086b91895bd183d2ad690991d6780ef32debaab402d20ae034631714fabb1540ac4b030e89846cb2143ac2ffcb00d046507835cc80f39c1512e6b1a787d728966c0394db1af49d279b2ac20f81619c41209a542ac17fd8a9b5c2d5df5c095d26e1c2ed71668009392b3d9bc172952ff067edcacb82b3e21ac99ced6bcfd5ad53be8531b8f6170a86f5a9e3949faa05e4790381bc1adec4037db1ea51187ecad9581739f8abe2771b027c35bb5bcf330b34f0036aca66cec75c2f343454463c5ac38d816fc13b85acef6810b63483740dbe651f47930afa9b9ea35853f9cb8c59422ef327004726dc4c30429829921a0a2834e30084e0d6f708e2fce12d42ba301299c0f026f5076d7dca6a3b93582c03cea653a1e8e664bf3c1c4c57c6183dc57f0888ae2c07ff14f49aada5636bbbc152cadfc6dd056150f9b00531efdf9f240f40526ec5c095193e0652c95f7c8872ebaa342381b30ea8afeb1d47f1573a489952559e187990f3d380c86bddba72401ba7f5e40f6954053f316ffb16ab63be3b7a70ec0754aaf4451065655189e20633feba1db037cad232325b5ced32ff6576b12050070af42e5f4995e103951018cafc20ff0cee8ea925322ed004737a4e1caea4fa1cc93327d249b06486be117d5436af09a4e591285748d9b2e28c2586cdedbc172c8f87e6de737e441a403a2cc524746156d85b1d488a5860ab3c0c2e1680a871d9edae1b38ac4f1781989bc71ddfb3e3613c930a3bf56366684246ee1449315d202bd60253f820b34b19a794cd2041a704f65661afaf8cea5516b732059ac1f3b7c003165df4cd599c4c47a4ba8ab8011b27cf2d02f864fb1179adeb8d50c4381112dc138739149f241d1895a7af1d801fc7ed3d5e58481c63760a4c81afd48f0929a6e71d703069e6bdd30ea703c86c15a8248b3a3b486377ec2530cfdc8d7b65b6665640c88e486f03ba7f2dc3d8863e8108c84c27b0ac02abe1141419529bcc22e4ccc5d5a25fd8b0592e562ebb2e243bd5a11d1224442a4f099ac7a88cae8aa1e2d6496ea25fa4bcda80dcfc6a19e71144a6ab5002e24179cd921bec9d2d84fb020afe4d02fb4bb7405985d6d0a1aff90d471a7e17a367ff0fe1d91a8b449a5bbb3412672ca84602ffb505cfc3b5c16442cf10d81dc3b2a9ba3952c0a7468e308902b6a2c5159c2ec6d0703c846283be519b0145c7ac5b14beb97e7685bc5ce658efc964e4448f89a6fc846fb5b892c685ae4bae52c38a0364d556dd9d67c7ebee0a29245ae294243cd55beff4521c5ac4142f2483d6a69bcce5d95a2b50d19a12b3111162c05eb8e818ae73292a5bcf624d6d01dc24db56d893150112ca3fb987433719b0f220b224806bc75588e1d5d91d20ccf958ab06e4143ca69b5743708e352929913beaa115c1e686f73217a5a8dd2b3e8e63634268811af1c2f8d4340720c9152c4b90b337e30add11035092ba8091c8729c405dfbda8a433802414fe4701569c0d92f6e9ca6dfe2bdeded5effd3a030de03457dde97c9e2dcb0e15896245d994a4d1bb224fb3564db88f9b40c6200ac45ccbff68c92000f2cfb49f5a7013fb9c956886d75fa825f57660659ece3e79f1b3646b04ce65565e5f75054a1627a35be36f441f88c1611ef7fd66f23c0f30c0d544cd5b78a7782241d7ba7e5c460152db18dec7ab3ce94fa5b2d2a52d782901556a5a8102470cc94a3b9cb37ade480a458e2f22932805c07e3b8abb0f8ce3c04af60c7ba1974522e3ccb2159ec690deee35dc2f6973ce1d5890e705df2ee2b7158dbe3de41a0b734a03233cbd14504c61d9357de5354efed5444d03f3462c2b7a5f75d7c647f4cf3adfdec8599b9e2221bbe96bfe64688c42f61389e7eb7316af7d44bfb42478adcc0641cac1f828a5086312d1b43ff56dd1481ae5f8ee661790080477d4b1f1a6219aa9fa6bc6fa389d3307cb57c5874c224d21767ef1d6df9a1622a1b4600d026466788de688f11f54749e3e7ace45acb5b826df6dca657dbd6f40e8fe748d95b0c4e543eecb006064c7c7f8660565ef6da962cc92b9bfb05f4340f3a538273dfa02f89a45abff09f3968253e45558a3c2fbc522df96856bddc9c5150d27be331258ec21dd7df96b943e57605817606303ba1b835d79309f907602fc6a8a29ddf69fff392899f2c18cc7b00e319aab830e575201a2c7a0fd6b355b8f53db34247a26097c45161bd655b6539c0700bf849e438f257d5b18215a0146822eedac3602d6b56b58a6c775f6af926bd21b60099939dff4ce574aabcfeacc4211c33b90cf74672896e836ba23a70c8bbda29533b6693938eb4a9b4ca4668282c8b240783883bdf00b4401b6a496d9958c7af817f3b802a4e6aded5f8b75a0194b71f86188aadabbcdb96517032b2c94fdb80a037146d4b5213c27dcf6a0cd729d8cce45bc22e15d3153d2254a9f89823f15ee01d9eb3a5eed55c0bc03e782c56f7fa5368f112c8397a61682d71b63c409b4acce2892581834dbe62401bf7339076cca5652a9adec5cb9c7222de6cee1ee670f03b85982a782cf59369a447d714a519c7c6d9b0097ed61796b4b106e90cfc1e973204fc77501c16886e0502564423b1d386a634f97944726f948258018c7efcf7f352b9a62865583505deee1ff91426920632b027f9a781ac88ed06be4b61d02ccfd811e08d432733b6e2f673c4da7fb500cf88c55ca87c06a78acd8a0feb6745c660a1b8878a3904a530d0e9e52f25ef1239a48b7ce4576ae45e8f7aec1c5d1bdc1219ad174da9cd8da347dc5228fc1a5e0e073716d8163522f0f26191b7c619609d7c42482b9eed93d5d142fafb8540dbae69c858a3d368b48ca51229f66d20885a2d07eda98776d63f4b6882c47c819198af59d810102ea504e5f8e9c03200d07c20459ea99729511a7556faf50274a3efb8ed237460bfe85a37211d000105be679d0ad0340e222dac91ad7a79b1e4c674132f482bc996c706931e21fd99d858eb2a05bc4cf08651278c89140aaadfb1c61762c09bab46a0f0d0d1014268c8935dce358630a42910867ecac5fc11ac557b309aef4c38359d4eff9733009bfc803e76c7c191dc38def4be1e8e1a38a080c2ab3e9c900f8cf5286236f47d3bc703b368649b08f808c7371a75706ee6ec8f46490e8ad67e390dd7400dd2e0c8dd78624ff8b5ed227a1e99c50ef4da1c103a75935fa0a66dfae8fcca998ec8e13d79fc47c4e6fffe68630050c0c3c4909169c643a6ce7181e89aef048d250f3903d2f5c59d8defc434edae0e109da50d3b22b915135702ed0c353506fff78404e00011253af30d5d43110cb09cf1f951df7180879df62e04a24f149f4979d0a353776151109c04da762a4477350337b5fe1cf00e58c2d327a83220ee54758c7a9159ad08a873b8331d79517d6397daca247b1895977940f745f8420165c3379c66d32da0532fd94bbefca52f1f51a29b111161b519ddaa768a6e6fb727d09135d64ef506ef7568175090c35ab0cb3b635abe56351eda18a99caa0d57b836931e3192efe842a779d3d5bcc951ef827935d18cde2fb425c127410ec97e5bd98b196d55e062264ac69c08ae50bc06dfc687549c1f85801dca8ca692cc6b24d2b67556f47f4382d7d8160f278586c531f056c31e829735a055c2fe1e241695d37093c91e07f88c75297d51c4e571a9acd79bf48ff1e69df519fea32c6d2e5eca4725eee1bb74a27259bb22fe84bc9928d73535afda00f0ea260f943bab5290be7db716d49e729f9c03f63b213aee1f267e467ad050a83ac3ca1c267d605d2ce6002f36bd079b2ce52094e1242b54f8d1b562fc7e0641259c76613b89bb8febaecd9db31e798dcbf9d1ae225db0675cdfbe88091ccf42057ac38a30ac0aec7c7e1619d749739b902ccb0f2a2ace456f3ce51f43104e2b6d9c53ba3a3afab8854a7c23cfeb4881137c4f03a2b3f11311bb5c1b8266ad90e9ab36f1540371072b4082634472c0c3fd76df28b2a9cd3baec9bf6494de48689c3d15f4008f7641a012ce8f51ce3ba142a93d11a89a4fb6c38047ed3f9d2fc8392c041482dce85931b1d6ed5c83989ca695ced4a186facffd26c350a5d0c57d6f5ea28a6561d4c4afcde4cf6b742c2b267cac57fc80cb4bc6e6e628be67017945cdeda41317e6ab1ed50060512624d035c4345f944da0931c5cb47e5c1399ccd44085a0ad8772fda6933847675b70c0d47e4a1dce764205276f7bc6480628cbac43824fea685bae4a59c52ff560c195d312ebb61c9bbc5fd8f3ed0e34c3d6f32ccb4c74fb7d339db9fd6f0d327f113d0b881a472c52973beff95dd37511a6cd41a9e82ae16781f0aa9f947026758678292e331fc93d3b2f072f8a33eb5ae45a96ed5c95954063bf81d2411d6fee31755863b5eb140c3a153dd86f2ce8d6a727a16a39e9b62c01436542354734fd390b2d4fd6e50ce1e9d3e6350d0a7e97af64a7bc10925ca8ffa86e68f7d0dbf407a3e10c5c3e2b89ad23acce88a4cc23b259df168cb852323de2a77610e4bbc9d26a4e1463c5a022c071f239bdc2d0cb2252a4a99c3fcb085c829c374b53dc040f2154d9b92364189c245391b6b8bf3cadf854dafe4f8f0360ef7411d026c462783065b1577cbde603a0a194feb4c3c82688ca95a46b6a2ff9687d5ca220ac385d40be8b7b00239764a5ca1d7dc54ab5886a5f08c43ca2b4191d4e9c0d02790f8de1682e80d225c84bc1a516a6998f06923d3d33b320e03ac7bb7b80d9ae505995a28144c8ae74aaacd5d2068dbd0689700459bb9eac474a00c124420a2d80d8486ee4496416173916122bb79119ac4922e07406877ef96283f17c69ce777956db292ee4ab3f380c8fec182961df7bc133cb5dd520b6360d3a91644073b5411f9bd43cbebe4b04f213c882eeef6e44dde151997b10d650eb43f7b02abc813121ce171427bd345345e187dad2baef8c646c9bbcbaf25757832aea9a926e05f8d59565844b4bb59198739a05e50b4aa7442f19164efce9667083969e885dd1931fc03d419ae61124fc64f68949a6eacae1d46a1ac2f5a28d250929890206d184bc992fdfc6a15a909f8113783639801e9f9ba2fe524bba4998f28dbd569e8881fa06e5b6c480a334b1aef61afeb84aae77baafe06c246827a00fb8efaf26ffb677ad27afe1359cbb7f4943550535811b784f094eceabbb418c5f3b74443658d0d9bbb0c002cc17f0029b6d082fdc21a7b855ee7ea06960a57c6a7898381391ce96a059bbafd39693b76d7047f5edacbe3210f303f063e402737103af3ed72b1fabac279c44a2347de40bf4e6ce41c4d0875fc710a807f71f232e87331e9029d9ef444edd56aa0ea93d84bf7dbc70f184445e60f6da830886ab8650e033022bc2d99385cff187a579b8ef5c936090ab521e966e94d85d3381a2795b5d6a6e5dad644407d380006a7227351f093aa02ac795c93157fb0264322fcade1f0b252e9f8389ced158a252363b54f3d55f680152e78c5088e78fd6476c9c527f423f76dfd0c221a93862c1f96c5880f6ec5f77760d044f06eb11074e2b003297fbbee1427c08ab0b03d645f541d7167bb0db210d34202772506c0555c2878bf9527337ca3e6e3af2090ec5a797de947def23c247eb07b570b0d8e44a91568990cb800d7353ed43ea66794e8e266e1fd5eff51c3eac1f26090ce07a4a3c7781141a58a2c573a110297404a30b54950ed6271cac6272a938934e738067acf0a48e8bc63b3009111304904c3a4003667564f5240b11a7bee5ad3832c8750d15c9106a141d5eacad98a3cc845f2363ded55988f706afd67b3edd31d0554d2a2521e8621de8b3743f23d3655ca3ab1d76693372a9abac9d7a025d505ea6ab1dcea8a40697eb17dbd6e10a2f261023c0fa3e73e2768f48eaf9a0c74dd1e7033a9e770aeccb4726f4fa30a88c6e7785b10af9454a1d72c1279b2c2daab099f84fdf4194e7bfcbbe83eacbb8724037cd6329c0014fab0e60b80f74051e061155211d41a2c2bc74cfa46eb528d0bcca3fad0a32905a42c0489d0ec8dfd2f51cce23b09e43a72f50cec7790c0bd0dd1c8061cba11b948b98e40a2efcdf3d17395439d9b9ca0a407b56d166549fd5376c1824d988b7757b7a4de109eeb90d128bc91b7c34de7bfe8f53b967584686cb2cca529c8a8fe45b22bad5c51c291caa8f96bcd81e32b0da141b5095792516e249b9b5cf7dd2978deeae8a079c5cd375746973e279d7a780f960c1d4ee1d328f5499174e0558e857adb8b400471102767fac00445b870ef56560250fc7483ee4b2b6347ebff84b516c18cb15c660577751d141f9d412b2f12203c38a6c2e84ea4312fe8efd9e8393a60f5e9ab09dec0a9770d53a02cc37df0929a6ca010dee2fca1cff42b693e85d8106cf85dd61fd7f19b0c7e84eb3122f3e4a4d18d8b484d788ccbf64bf3fcd702c0aeac250807623b99407f1923847af30d3562c43832654c73efaea4921dc465fe1d152fea0591926759b88da9f22b57d573099dbd013376f3bc286ada71917569b82168e3f79b5f4a256bd0f437fe93ce451572b6df469294915b1300865f64765cc7b4952b2100df38b2ea83b70db43130db9f48a3e1f96e0d76486894f35735c8498d8f3326ea022cbffa64a02f4ff02da434e755920105a4a076a112722e48449c1b2fec1816ee22f2e89068084380700d0c339a5758753c0118775e05a2dc2e1c9986a554a41a973904aae3ac44de152c19450f20e88ccc383faf55eda1b691da4892464efbdb794524a99640a7607d206e506dc279b3d30894264072d30a32340b0f8495ac0dae888c3ea2741002c3efd9220004664c5477ca21e72c6fcf844567cd4a720510589acf8a01f41220a760e907c9932450d78b8811858c3ba4806305a72c8614b1916bc8135ac3f7cf978990c16e391077d10e58cc275db085fbfe598ac13044145932758c3ab7d282483e082e17d158a8080ca15bc1f4a30240ad3245f4e480ee3cbfa2a1421e50b8203184d306c214b10741827b97e48823c2f08824e932bd8834a11219031623651c93eb9674d4af214a27e214548302b7b7106882252c864b03845d6c10b298092042400493f78910dadd069f2145908648c90053980d1520bb20ee304d61f9220d79e85d99287629e42a4d3c06c8344fd424090830e3418122593c128283392c9600d86de1e044220b3e50eacbf3d08ac6e7046055714b97307d63a5859824b9c9cd15e4a5ffea56f29add3a54775c82d77d1ea4e5cff95f1ebcbd7eadfda92523e4fdb701c724b5bf5f44c14d83a6f034987e2fafb67d2b611b308eea26162c82f8999e0fbb2c6f6dcd3f7b9df401ff6197746cb675ced79e0e450844c9fa7de97b967619c68abeee5e79e21e1947ddc75eff7d897d9738f05676168c83d0b73bf4eba2404d72a399852973804ae5570da65470f47bfbfd71ff5fdfbabdff5a387a3bebf7f05c3d861cfaebd90fbd89bfdedddc3617f7b7b82fded04edb7e8c55a302232449663a108ead8b99359e8262a5096e47e168a7283dbdfe8f1d89e935dad95523ae79452ba671a1be8520f47ccf6e5fdb61e8e06b7a71e8e2d7f0f0f6bc316c01ff763443994f9fe0deb47ffeaab2ef72ccc519e94c6991fb6e84b8434351c4649c814b3e810ae3b76c7d7588f6457f3a9d71fe186b8031e649f043b436229db9f4097c9360a1b450e59fdd931b1896c3db76c90edf75bfbdd8f954a3d6b6293c31bce9eb87e5f8509e5f303ad9759b2efe09a9504776098b27b09feb285a58e15d7a917dc8d3e65c8c937923077b13a0f8aecdf39a0080b992658aa90414d1a1760edad3a774da9b20287ae8c5d60aa647f1a2464c71e24d374b96760907294475c6324d8bf1300d7aa3bd8bf4bfdc0e59ec940ca0857d4c0e5055670485202d61dd71f0ec93c77ccabad8664de5ebe8f0759ce6ada6faf59cce2acbb3a347976bf75cf45ef2853162b7998cb273718d6970dc658094334fbe7b5b6fa71d79c6018732cf687bfbee15226dfd53664969406f92a3a2a2bf0531920b57c5474707c0fc2b58a0adc750cae5d892825c0c4e1ba2b92311acaa90abc31f19441c8463d5218c346c2640ffd198cf91fd7053a30832a14f6c40d9ca8fcf83d3ba66c668977b887a39964b2ec20905843c964798845a62f674c94d1d185b82514674c60c9f345f01a1316bef2ec31ac2c9d72d869ba4c51580148ac84ec0de43f5463fef1a009a47fa8f2b040cc831cbf1bc83f8238a26742110ca4169620d3273281098454210e86456dc1d0ff470543f9fda127c11e1ea109dcfd93cf454f87907f7ffcf892477cc923befb7810c54ef07c7a049838864b9e8f5930c8dd34420a86d14ea67f45d3e88f779a463f5d40110cb2cb9a86ccededf021396a36e05a05d52931557ccec42039cc7df20a5cabda04e123b2e4a72f4cd9a38afe8c2fe7d3afbe05a5bb0eb32f5732cd3df36226378ba53843c0a9d0c45914e30d2c0ac0c0929254dddb3f1a8c09c9fda17fd432f3b2947be6252983907b160338190f91325790de903d15c401def83fd3064e8506c70fe57bcf90ce21e3cb9fb4f6e0f821df853c6ee15ab506732a2f4db9ffaa54378809f9063b035cbb7c29a79c524ee9ddc7b5770ddd9830d65a6bd938f98c7e4a001b28dab2d65a8b5b9885f151bd8124e9c819eb84cb11d9d65a6badf588987fd5268f67a89c517ac375134b4c8c34e1458c84aab5623570ac88b31992a652190a44146aadb55a927c4653a10ad0a2d452c16bcaa1f2f3aa36effa9f7f2827994cc6a49952000545adb5d65689922d6e6116c6485a645b05c949924c2683e13617304c4e906e0862692639452fb5d6185e1a972e4e7ebedff53fff1c945a6badd5d6b1758cea1cd5815207a98e94eaa22fb0c3228a586bc53b2ca1a2e402399452aac58ccfe8346ce024404b54028a30b2af36d9565c6150c2065088d9d0d48026090d9527cd03a828c46c29098d767192e25089a314074b9ca5eaf241d3344dd36aad504b5308163c3d35c964b2177a36439a2717342aa98043134045149411863a6a3d13123b4c6ae09844a1e2882f6bda284109d1c4a408265fb629e20eb32c513ad983b5d66e6f98b2b51aedf04f1740605a922487d082df2793f7a2bc443545958972a2cfc24b383a36ae71f2a2f9d00224447ac8c193304f37c824a05e809aa19bcca148d8cce0821a1c81f4660837616ea8720310de26a2808820d69c60062de4c8e42491e3a4aef1b2a6694d99354e54a36fcabc717af3f406eacd9ceab2d66af488b5d682b6d65a6bb5d65a6badb5d65adbc685d22b5e6174c5d115507e1e49d29c77fdcfbfadb5424139ddc16badb52a61f2190d668c12332d2569def5b2d65a3a6320c1052b366052e3c40d685083a021c93b91c9644f6692dc4c443425c92cbcc5b9d3e4838ae6a5d17ae91cf98c868224a526804e955a374dd3344db3d65a6bc1aa176470393a377807c76f0270acce050970fdd3de8a089e42ded3013336b33549e46ae70f4ac19e1df4417f39c1b97a82fd238e56b8a516bd1433615aab5370c843c16532996cf6f0f020d289a5566be9f55aaa237b66967696dc0e096e878478244e8f8a32b717a5bc70ebffc74543b3af851af83feec24db43543d63cc91a185e508557d634edc37be9b8e2bd76d35cd0344d7b9c266bdf6a93353054e1f51674fd8f977bb6c6881cfeab0ece32c3c1d24c0a32584beade7b71d0848333f8051767c1acd91a1ef2967bb6262987f8a9bc5e9888d7abd5c243b45acec3eb85b14849a725a5a493bd951dece1e14fdfe5c4585e2f29c0c418638c317a03f9c790228b1442e427966652b02087b7df6bee991455722b680ea7a283bb960c64d9dfc7591ed4817e7735397cd94073cf9e686a02f49cff410ee3fb175521797ea89dd0687205a7d4ef8f7efd88f27c1b0ac9f55a10082818058972a8cdef83fc4176e94ca8e8d02073f4b110b3bf7b3a3a6be00ffa7d40866e8ef51d1cdd6f1b0786a005f6e6617dfadad7da731cd5a27b3ca6f6dd0b790eeeedc78f931e8ffbf763e8606ca0f8da472dc81d8a4535ba1aaebd46ca6bf99db1286f9ff7f72bca1cc769efd5e7ea7cca7145b1d3348d7b2e7232739d53dcffe8e131adcaf2b02a7b9fd3b6ff7a3610470d79fe3626097dadfbfbc5ac6d1bf8838522145828ca1d0af32d18d6ef40ed85a476694894bbaf5f38a51fe926408419a5fe483701ea479890ecbe1851ea6bd88c491e1af8e3db7bb983fb0a86de73600ff7f33db0870379dcbabdfd71afe571edd5c0b03ef71cf785ddf768f5ebd1401cf4b9e73a2f3127cfffb17d58bfeb3af77070dccbd7c058bc533fee8564ccf9f6a75e1d952596a4ca8d6a0a7f8ec3b1b5d5fc0f6aaddd119c529f9ac9f533ea251ae755ebe4fa19fd27737f6d27d7fa1caded33ce947d21a71fe30b494e662e52c1fd42de9c6c93b94969bd81a7e07e7a5da97ee5f879fe204b5fe2b00fd87fc83dabc151f6a4dc33326cb22bf78c0cd28ea12250392cca2293e50ec19318e4fe887dc05288dcb33349e4b8947b6683267900b9676688c89d6343c229c72e90fb736c5039e49890540e879e38d342410e87a8a0807b2287432e3c8102084d049143166810446e99acefe47e2fc2c562f62794f2e7e766522065cac53b6186a64c2eeae141b9640f83b8881e0acfa328880b33d983f9512e39fa1772fec3a9047b7270b327cae421cf547e96dfe7212e27899c0a74f95d18f8173d7e0efbdc6f1ff7da1772ffc39ff3ae7bef5ec8398b53f2ef97fa26935f9956cd634baad59eb1ce94bf90e4b8dbd5bf3fede4d861ef545f4405232a82a9ee07e49563310719f2ece014d5735ee73b4e7ecd6ea9c8059aa89ebb57d571ca94f766728ed4d4be3f477c4df374085d79bd1df7b5e7b8d4d7759bae2273b24b7d0d2773b44b7dddfb27b3ea336a166c0ee299034062ee9eb31ea682bb6f0f4fc1dd0bb999dc3dd754c6383d462b23d5fd2b73dcf6de81de4b99b7efbc97db0fd52cf3f6dd0744fe50e56181ec2fc36842d3d86efc8edaaa82609e426251e7db0e9ee7795ef7dc7b9ee779df3f5e14c7711cb7bdf61cc771dcf70f1747d3344dbb6f5fd3344dfbfed1da586badfd1818066badc5514dbbbbb73ba51f03c380e7cbd7dac60dffc64ab0fdcba9aa94554a696bb55fbfae81ec5fdab3a6a84c7f766da3be7defebe729e0fa87ebaf6fc82d77f509b24a7e37d0d73590ea63e027dd738f9fe0edf1135c5fa57ad57bf153397152b5a37eb5ef1fef4bfbf7860c1273bd7fc1a72faf94d25e29e58df58b11fd75ebd7af7f74e99f757ff9f55b79ef0af90259d2b82fe4f7fe0ff956ca0732d4e558094335c74a902b380a17c8379a504443e2a02fc1fb42f2ad9c608d560bd7beea54cf450f4733451fdc7d2e4ee9fd44315548f49cb51e8b319923187241705661ca86e4d03f66a386dcc941be50fb6adfbf2e748f5d23224332902035c70f62330b451ef6d7dfc0588cca43deb3232e7dfd3a8c5f5f7e413ccfa6e15f3dacdf34fcdbc3b18098fc8ed6bfd0dd9fa83fb25094fb633226f3fc217f66f9e08f7e07fb46c9c0151c66823dd5b3262c399cd963c48f01cca46b1bf13d776d03485cf2a4d7a5546d034894e5580939caa21c2ecefe961f16c5971fbba5fcdafbbbef1be8b5e37efdb1e5aeee8f1e0eede397b51576d7f6da7361d1d7da3e2e8c4162deb6e951ca544ce61e1c326baf81df40fd31df6f037fdcd73ec6ee7d0dec16c601c7dcde6b110c89f2941e2e20bef6423246e4afc91c935962242cdf81728d1fc15804215b319c3f9e01ffb0ff410e2d188fdcd5f948036d03c82b4fe92fea283e7e374841a3234030fa495ac0e24f56a8427e41d1c3883e141c3614793e513f96338a601d07c90528708192934c06a301d3bd03eedafe7b5efbf1efb5d6da0ed3dfb66b353c7b4a29e594d25e6b2b7e0b658eb4d6ae5c9a77f450b0ef6ee7bdd6ca56a8f6de5baf55ad7a544dabb5d6d9de8edb514a3ae79c744e5b2b8e1965958231a529b84b6bff2ea5ad18f1ada5757658d3a6142bfbc6c698526b69677d8831cee8d19f1f0b314f5f0db9bb0f799e9c4750d28974a27774ceea4372cae8d2630da491d5b4a9b1522b1fb94a697fdbacb576ca793bcea96945d7da2d524a39a7af4898d35a4ab959ef5449a5a473ce49e79c935249252533a7e7a8eaac2b21776177c9fbb27e8cb5fa7c29174aee191da61cb66674aecce818e59ed16193c3d68cce186b6bb5b5520aa74b0c5a4cc1f24e2ec24109111e88443162430fb0fe7601d51c0c32fdf035036a2af50cda82aee1577c067dea59885879a052a99ec35f70e84aee4aeeee9abb928cafef4a3ec6cb4ca5a934af4c250d88ae8127dd92a97fc9f45b03ede7323e3cb3cc312eca843ba5fb44eea2df420ab2c64c9233a203e14aaee44a1e7a9a31d9cbb8994cff850cef1c8b12a683b7e9643af1a070264d28f1e8858c175f8f8c99d4428c3d679babf83f1e047e7c1aae14e37f53c5f3c37379e2f33cf8c9768510ff7e3ff155f1678c710bee7c5fabf72acd2b9b9292d20c8a00e8a0470f77517777195734a52903bc718ebc944a864a46ca63eb3d8b07cd25a6d9034e1497524d29eea2df49d80de6be8307c0cb0fa792d254f2a0003cfd79c56bfc3cfde965367910009efe2c33b378900726d39f63325d9a4c019873ce39e7c492e9cfef47ceebf3a12bb9f839e70b0de44983c3e944c2f8e94137003fac1633e9c594329366925f712cac163254f6c69904821e7c61cc1e7cf55ca90a8eff1fba12ed516a1b718c07f53f8d9f420c9c63b4720cd6f711d1ea4af782217eb0850e3e7425007cd873fc05585cc9af38160fbece00f83af858eea2eff34d517dcf0724de87f1855d0e5d49c9e6b0cb0cc5ec0234c15df4595f4f8b145880e15d81b88c0a9c502c21b168a32e189066728eb01c9c83591ca4191c363338b2d91b36f8cb667fdbbe7f36f04794d5383988ca9d0338b9e9e4ae222a77a3c0b5fff4a8298b4f665e394a24b27f3823c7a8452789c44bd9e4e4b235a7ba528199f8b4aa82db5d617ceae2537c8a4fdea432424537d93fb2894ff129b289509dbb2c787b4e19180ebf99bda1436ca10a3ddab38ec3be7cee5e8cbb59981ff33630088c9f3fbde9fdf633d5f574decfe6f017f6c89cf691e7535fd823a7789e83f11b8c0f6fde607c88f3f6303e6ce50dc6879f37181fbaf2c6d3ba776e3f3f9937cf9b7b2fe704a78dfbdcbb83e1fd548fcc7de12b6f77bcaf1b08bf30fe98e599dfb2cfc28ffb3cffe362fbe1863730667f7bfcf7fd6edf0563139ca2a4e77bbeeb7a5a7cf722c6b362bc8b37c2e28db888a1e281c1f3b805863ca03fc6f8398cefd66d8ffd77dc7714ee0ebbb9ea2317ce1ff5bd9e2338f5f14e0ebddfbe7a37e67773f43614bc77304c811ef88259e15ec87b6e9e99f32ff2edfd37f08420316ba0bdb32a96b2994cbf6b2d654a2315426d651105d73f3a547072a54c8c4a6a1b4c6dc38f94c6c83913a9857040668eee02d35050b0443332295e9c8d932b6564541250516e8e72cfdaccc938f7ac8df61cf7d80a1c3654d6fa5718f2a0f9dab7a0bdd0eb188184d693a6690de64b7dd35df4ebecdbec3db77da9afc77ee7ab7d8794b5c7aced359fcf1a18d2c8da574d84ac3d77836a724e39e3d19c737a37e2ac72ca94fc19fd5deef239539dea9eb5ceecb099453df1c5975fc729e7b78c71ced9b90b0a4a0621f78c8a2417b33c4629639431ce1438a59bd3d331e774f1a18b3cbd3c7f3eed1a3d24cb468b9ff33bd3a0c1e263f1c316f204637d274fd9dee5413fe4f3f0bc6001fe60289cf1fbb71703593c8b6fcf847082b1f821cfb3e801c6e2cb67f1f3795e48ce8ee759bc90f7f016e88282214f0ebb89c573f16301863f5986cf00906b198b1fce0f22f314eebb48f7dc47822e163f7401ceaeebbaf92c9e078cbd00a7701f5d4ce9be45f754dbc02430601ca884e3388ee3388ee3b8dfde8806e237b2812db982acd1812f0d0a0a4afe2bca550e3555521fbe6e7823b8abeb5fef86fc178efa121cc15d5d3ff5f369048a60b603877c86bfea03425780f2c97e676eb0ccdc28cda6588a52882da232060d3dfede637bbb81eef7be6bafd5d7c0ee19a15f5bfd3a9d4ea283580a3c3ffe2cf3028cc68f306bce5f29c1a12acf67b5a49452d536e45b6aed4a86c3cfd6fedf1f4b29a5b4bedc48fc7805193ac15e82a54b77e950cce904b326a5f4887a6fac92b47c7e2afdfa1d6e1ba38df5ccad33d6faf62df8c33e48efbdf7de7befd534ed955b867fe1a80fc54670f89952faf520c1b1463753284d6134044ea6917bc6c64bee5f1935c11e70dd6c261038822bd0be2a42e99b80f35f21c1611fcb5d51c9bdf776592233a285594bb89cc0082e2d5680e483142c38e440c30ccec4a0844909196d75023545d4604e106e8810e30606253528594196950c1a9224552829924545c37c92044c9226b218d165490e4b94f4e0eefdf73a0d1148f8f139a5bb7fc7f2995afca75c85c1524ab98649110d513718e1b2030c51c4007332821517356f7290a4c9d21259908ce982c409a699952e4766e88244a9cb912e2b23565cdc294c1d16188961064b6693559695115028f0648311a4264eca3c994c646801c5ddbdbb1c39caf13b1f5eaf225eaf564b4babf542ee184431c39d2844983055693105a7f251f6f142112ea0a20667a0f8c022820839898851477371448bc2e94893135ebaf7a658b02a629483d10f2a3b1a9b0d8aa6699ac61559c2e8c813f07a9aa6bd89d3c588c8bdf75efa42d1102d46be7819a28d134f2e3c694ebc507365b0bad285c89c2e449cac5a8079f04102c90f75ec849101ca921404a1699aa6d1a30dc09901942e759e5c419a4aa0d8508f6845927429c264e5838bdc5d8c88b9c29b6551723fde7b6f172343e42bfbc13fe0ce737585d5c5880e5d8cc89043fc480e420c4e94194266055c98920cd16aad945aadd76b05c4eb3591e47e82624c92284d34412209587fbb585991d3ed54a72b2b5be4a983fde35be944c83248ba8c47e54e904c60e8e32eb9a55a5ea293dcba6f8aeab56f9b55fb59c150037bb4a7d28b06deed3daf294bd964e649c2911dd52045298b97314e733cc8c8833ccbee8f3cccca9ccf03ccdc6f31fb775d88c872f79ac74332b9ab0353180a07d221841c0fe761022f1ed4bdfc0e0c3d73a0640ab72e43a072eda277822a77036d31a9440349ef82b128bdb84b76a43425c77868823cf304655303c94f83e592646a27b9655c251e90563ce027fcf433af094d9e58a3242a8a2f47303f42848d92ca6869bc3d797605d42a05ab286d70d85aa5004a96568c150a301538d492564941ddc909d5b282e39a563be02f2b1d563ae02ff88b4c66b96564966be6aab94eb74c138de69a71979751010ea35399ec40e6aa0aee48a797928959edb0d2617685d32a29faac77eb5298858cf1c3517057074a1d24393eee628c2e5f5ca1dd9c73d2ca7384b5f66a2f6eb06d1bd7cdeac849759ee77929551d8231c62b162c90b0ba162d5ab460b948d9c0d3bd78f1e2054f6b4e89d1c1800103468c9e550f1d743ede8d187dde078697a3032f16efc8591d29990503b7b4dcb3a82c5d37e33a6fe126eaced31d991dd51a0def208225cc442d35f981c80f445ef065d63096cc2133fc30eb9e7477e87491199945b90a907b668598fc22f7cc8a2c5366a63447c7cc9237406001924c4a1053c8a9a27180832d3844a1810b9426da2972537e3d1687672a6331d3a75fcf0ecff13d63ead293206b498411c02c8d204297114104dc3524133220b384cc0a1a2460feadae5104a5e98c17188468c1106260fedf358a4e90039d2bc61c21b2821c60feaeaef180254954393ad3c41056c0a89072be9c9fe7234e2b90bc08ae7f26f5cc79b577fbc9fded92cf7d3edd6a56bbe4df0fb74bb64b3efd66bbe4c7af3bcaf76965568c1394524a39a31395ef7cc1c6188514b0c218e316d13bf71bb8491081764ceae2900692527a37bc2d0f6920ffa9e50634903fcd5d83002dd49ad10168036cdfff41dbd0561863ac43470fab8550ad3a14a0a345cfede9f124328805ae543a549fc323ca2c7e8411dce5ad0f4778bd60c578186da383974fdb86cfcbafd1003d24ccdc828b8f60d440fe3e5f34f2c5233104ff18fe30be78e42eff1eee72f1804d2fc0b0e59405da19582c2a70189b9aca38758d125cb043d30f5600b14508987f7cea1a0790bd59a28a951bb09081f90b29c0835abc906cf1a59e7e2c5ed808dcb97ef85345595353530f77b9c13149fa709787dd24038e322fee5a72d72b36c518b32889e20a1350c86ec041470191458cac07264d58dacd0c55846842254d1b1cda4b152564513d242133529f7e9e24d3eeeb6e17058263e61851aebf63e60a4ea15996428f8f4d714c4c139b628c35f692d0912ae4d8007582a82d718a6280410643d4c89a92947c3489c326871d9c886205029a907943c58626de6c913cf85630c4b1e96b4dd66898131ab2fff675aeb9fef6c50ea1bef6c906aa7fbfd940f5a34cd6a03da3fad0e022d7f77ccce9eeef9fa397b8c4220e0ebbc947063ca8a99d72f8ca22bf469193364d7c5801530bdaa081f9c7245da39b82083954d84047ca9bae5184c31338d8723445cb1548c0fc630d5d430339446122064e7a500206307ff9c5247f21241651e9c19d78255111cc82f6fd9360e836be8d34face9cfcd41da5a0d3696236784e0a3ac9a4cf28b8a61486d45ab9e7ed66cf981e00e5393f1a20ae78705157a63de40cfa1de86a7265da434e3a2918bac891863f3a7aca415603c5c7dd75bbda155979e2eee298d9c118d18ee9c347435918b8fe98bc13bb387dc6741ea494b24e2f7876141101491e56413a45fc29856be794a864545136680f70ed3e964e0db32aa59dbbacadd5d6792ba5b789b852a1a547265aadb952001a89eda7c0aa3566dbe16e3929955b744c6bf17d3359fb7a1683a7ac7d3deb1275d960fb73ba6b4e3c2bc61669ce39275e99e0ae3ca7f476d8bfb77a73564a29ad94525aeba495561de69cd3f65c65805e4935ad3690043d7635ce4a29a595524da3b4da3a69a553cd5c4dc05d1ba771dbd43a7c69adb74bad7275811c9d65fd7b6bad36e26b6b85c2be9de35a9b51a8e4ec376535ea18b2190000001000e315002020100a0644229150142541bcca3e14000c7c8c3e664c321147638118c7611483400c832184106210300618a32082c66c00fe42c7cc9bb80e75a3df5054df62271c071722d5263ec553e45da32f15fca1df00427ccdee556e9100958a6a2e58cf84e137f009d2bee5d99409f6eb959d824194486507e3d9831b95999b2f9c48847873d683023b292811404ae9f091a962595d988eac2d94265751a55aa272dac393eb148a2a7667e0962019d3691b3c38280def34540e676cc8b5be526731735345bfda653608d47fe764b5298e9bdfa13a370d31c074a4c83e3196e5d7def72eefe67c86f2f8fbf112af620e8df212ff18f8568b8321bb95541e2a3878868fb0ca6534be5a0a466e5367770f5f126d39573bf6555dc7f9aef7f852951113c56c856387bf75167f0b1a9c07b98f439c22d2e7306b298be7aea7f4c18bd4d302d44a974c30b44970c7c247bd6b96c639cb9491e19c9fac4ebe0759732ac125bbbe9c20fa94e1b6e1d49d1f702a84a32e02708c4b70c88d4a2926461256dbe5989a0843d418c69876e2968ceb2b52d19747e4dfbdda9c33092963c36c52b50dfe30397669cc068b621ab6bc6140710dd8c04ea8a11e1bef024ce138ef48df3f38ea612f93600935e8eac2f82a82136a203eb60c608445aa1a76daea43b525f7c0a86c4002f91276ff3e2a950d41af82e4ce94528be9b9cea73ee9a545b4540727011dfe9fa730c2db1c21209aeeeef9d6635eada8d41d1ba37131b99243ee6b8a49f1c934364d43b8163cb2af36b6fa1d6609d0048c77a80e6c82be944cc7b8aaa5ff526dd49413cbf1ab2de6ba44070a967d4505cb55450973f87709dcf9c458840dbeb0040ee4d5cef4685b2071c9090ac44bdb42d1baa720bb5ad3a7ba69cfb94856730b58ee02187e29930cc345cd934d689ed343d85f8fa8ee782f8c89805b51580282f4ad8e741429e6952cc13287efa115c2d440099a7e099d08133762089a8e0228e352bee48d2f05341b23c8c4782e050b48a673a2d447b789fb72461bea7a29bc52f94158d4279fa6012abc36677e11e18ba54f1a21fef35260da4f6b9385601476e883ea1c5a91b8f4d05e6fdd60f6901c9d6e663e83e09dbb0600da54a9cf8665943bc9d77e5d5124c977622f0a782a0b0637b546c45ec031b1e829da83b0fee5a7f119a722b1b8786e6f72def286d9e1358c3f5b7a4efbd685ead230a0399862db42ebc9ea273fe9d942d6c14a3f2d478f8ea6bc013cb9a96045b4b520fe000a856c7fad4f0d9e541cca4b723f95066224b7fc954bb1d1d971d5270057b58b05dfdc03e5bc1583eb2c90472595d4920a0d6dde02f34dbf8029d57ee60ab61a3a289c9a34a4f523a80063a000ab21464d1313df19cfa5bc610dfc1a05e34251c413508820e8b9d769d2eb82e61b3d57b509b90458e3459c3a37ce6b113d2593b7e484899e74a2458813155a49cc2f897003284edd24e5ee2dc7b895a029f0f9d5ad544e5e169c4596d7274ed7a9dbfa6c07a25c62d49b62e93b5c46b197f1eb7d40e18512f8074871b25e1ec0e47808880303d1421865c8d5a5a8884dea5c1eecbfbc1ff0417c2d0844fdf09742db2f683d311b8f95bba22960601fb8c58335710c007d4e5244b9deff159683a712812d8c18fd99dd5214ed708d537e44665bdd03350bbd9a9a81b5508f219912fac91d7f6ac40cec8a680bfc11a1c78f3c2d8b256c3a560cd998d33a5aa9987dbc0385da7d655690a1ee55cdf7640af016d4ef227699819ac65b7a57b6c80fc8674630ec09f6959702b42bbeab2d610956eebacceb31876257adfea469b259017396ce7036047004fd18d4a4301858c6ba9f72f5dd5b4cdcbbba6600882e8222439d0326cdad81730b317dbfd9108ec8656326bf105a0f93c06a22ba81380bd3766f186e33855938c65116a44c544d02f04c1e37d82216691ba725dafb51ba3075491dd6dc8702f8539b7adb2ba63f4a99a260daa34c0efa11f40138f5d285b655a68bbe8bde38dbe455dc08581e2ba2c30c4ce67595e93c472bd78d35026338efe562cc32a13ace85ab85f8b64d208e4d75666a580199eba62fb015a0d5e237dad19dfc5c6cb45b3abba5da2b851de9ec93da9ed4ed4bb58f34fba4c2f69ef4f648fbcdf415c7d9fc0a447fde62caeeffe952216b1e31517a4cc97b3dfa2e0f37342328d7a6ec486d9fd476a5b5438a7dd2d92dcdeed2ed4ac79e7476498fec7d69ed92a6ccf62b3cfa759eda38e486ddd009f17cdee7e57d7eeff2f0869ff7787bc7bbf7bcdec3e73d5ede477cdfbbbcbc29f0fdcde319ae1a5e1fec0e2b4122d33f47c5070e1d81485c3b303029e0663ba976a9be2b08ea41f257a0a4ab190a991351dcd557e1ce6f9ba6b39e208458ebbf7a7ad51e3ffbc32fc278baa6640327d931ac53821edd5d5a049d5692a8f7b0c3fc9ef7bb418451a132758bddee137079e8f35471fb5ef707c6ecb7017a54c2c61576b4318e3b0607373431b822585ad68efa743b42cef53a5584c63239acc31dba831ddc61ac435df46cc655f2bac29101ddf01df99f098f0db87bb5cf05bbdcd43c042d7bffb73434cddea799a57a7b1e9f0e1c555a663d213c1ed4dc177f7969a7ea00ca1088d48127e6ffb29fd5107f202012131fbceb55e71f486d7db5419abdb1efebe6a8ea68f510a8071da858cd1a717818f4d917e77ae98e6a1808f861e566b691c2b1c14fdef1f76b738cea1c0cd0e21ffcce912e2df05d1cb0c6cc0fe5d59e3283e5c0a84b62777b52e522b87bccb712f01f01cc4da1e8368735f37359061f16b5bdbaf90bcbe2114cd3d0ce7346dae4cddc67d7388ee4d1ee3855a50fbd1ccc456caab40371566c5fe739d55574f21b084d29d0e03f7235e9fd2887cf480337c8feff53ea8893bc27d4a01f7613a1d2d2e9f722a54fe8a109b9014c9b37686356dcab68c63fbac15e75ba550db5b277068fc588087251a4ccf58e2bdaabf1791c74ae62ad1aae166a2512896652e2a3729670b1989ff480eaaac7e60970b3f6f2c77faaf589260c6980832e4676d09bd9091241ce177d0e429270f1af95f0282d714b09cf30697e8f5e80b98fdd7d63dd96cef641dcd7ed1ab31899e5ea21f6b9ddd2a898eba0b930dcfac73798eb43da821c8c7369d3e54dbd35332255b4e723170b210428991cacfd8918a0b8f38afe9e071ecab167ff9528e194f71454b4c885db09e9a684604e0e72ff211e18424c92685a51eb4cecdcbd7d26892630db9020c3361627e0895cd3ccc6bf2c4780fa0d23c112adf3e24f6c1ddc280d866447d37bc2b8961e3a6b1691a96df9b75be8dddad6728623d367d862c9ea1da99b454d686b4b81ea136555969c51df1a509159e49e41ab230c0888a164523a69114cd8a4c70e779b0afdcab42a62604783079308e7e5f36b7be1a8763f1007de1567ffbac3f7be843054fa42a47b4d811be960c67933fca09df07d312ef852cdcf2fe75d2b715e9eae845dfddb5f74ef73b7d7888292ea159fd307b3ca02c10d104ae93828588cd662b01e4a1041289b39c1129b1c7b7fe91cbb34d5737374818d73e212a16b779295a2386c1d9a98ce4bd72c7b70732684e30925d8b91596ac311c113821af82b226b492595360a531faa72fc69250083913105a6062ca633a0bc13cf4c3a9031b7da57dc04934422dda53a9e1b962d48a7b0773b363bb09288b439782da459bff20cbfe1358b3d8e06134a3df62d7533e01f8329c1e1975bc12b56029e4e0750f069016489ed6c42745dd3cb55eed43ad55581894a1439c04d66653e020cac42569e8bf49eec603ebc7d91b1a73b807433cc02a36ebe6ba187616a5551ecbc12df8bc386d57101c846e63a733c8101af74b6056697ea8285381e18eaa4b34033b664e95239fd14265b973c048aee7414230773070ea3a45dde1b3fb6f4e9c37efe47c35278ecc4004f67b0b1ac27aae6e48c5c9b60444038215b804484845689d29c00e7e9b10de49c5778652a2d731e16d978e725e98f5ab2c1373a1a378ac56e0627d0950e7a13598e32c5dbbec37e7a10148740b112e2804686f7874ac441c1deac3e9d7749d1fc855da0c378bace8a160a95477a3ff8cf5e0df608e2a77a4e7c9238528bb393091ce8d06be87ba3e9836fec869525c08a7725a0dec17e6aaad8c6260635dadff3f8c62b01f404d70d0107368ae174a593320a1e8bc3428f38b20eb6db1cd543448c124253a76b19276f43e1977533c3bffe11bbf573a0bc0642b9c995c7846e4c3b55aca6f6d74ecdbc893fb65a95d69527a327372e2405770357282aec21409d9dfb2535fa8f9893a8a085aba08db369c00bddaea73668fae5e73af95af6fd36fa7520f443860857ed01fbca1c7f8f22aa5300681bb1618b6164fb33fc93b48b534c422d3195cf029f6fa6ece16eaa2789019504a3de80b94c47269281e6e439300ba0ee8c4a221c830dd89083948ec3cff7e90f762d9f7f5049111147795c1c59e793534d76eb69e30eea358fc0675e12c962077fac0dc9515822e77415315de62cf821422b979fad1a096ea46f5ef9caadff70b8fc2633bf2d66aa61f7db5aa60e5a3472504cb45c91cb6ed32084a744492abe049aba5fc179d8e7df64f5588ed61fd8e4be195f8803c11acd4ec36c2a044fb1abb11b6e1443827a487ce1a61eef17f912f2af812c81f90da8c6ea0e3a092a1802ce48e3d3a644b00dd417ebb81bbb0ccc7969e149c5b2a03c57b163e8773057113a7a8449dae06dce48af05a51158a19059c3d93697318f876b026372830442aca7f05820588298d48a0ba61b29174babe6ffffeea1eca18e7ec4c021c03bcad0a474139b7f3d48182a01ff98611a38e5f209f32a35e70514cc7e7c2fd2f2f8226eeae27e475b27cada2e82bd668419a0a98316c8e5c6b2b0018b21f9a84caba46d5c6fb6c4fed0b371356d5c9237912c46ac6734b978740fcf8547b9b73a4c80be2e18b88d828579dd3f88a166b647614221493d9555a2f382e289b9055321010396e73203dde758395ac6287c110cbc64a43c062547bc9d2b01865fd242ee4821a044561d9527c801d296374aa5bcdc6cf2f525e768dd15b726f7218ad7a61264abf9311597cfb5c98e525289f601577c9bedcb8997ec5f7bee01748bfa8eeed26ebb682ace5bd09c447227a872e9c0fa6832c02b5a7d6009ed8b6738a1599a88e5adcff86ce0ce18ee9543a423ca8bc7a0614fbc5a048e29695301f9230ab177db2332cac64b05492d2ec15a9a000c16e9c188f574659f2907c0086347b9d222d70e12c3881c298e0e37219f4e28086372c176e5df78c740461807093271f38f3135c3a182cb454aae1cfd8d3610604df55b0a7ab3024a3d7ec27f071175ddf14c3cf73d318efd9c686e780bece93cd7423c78d7888e9fdee2417c6208db48c847c45b979a3316bf1691c4d8c2c4e65d83bf1241fe2125a03cc515b1448c6cb9d0c778131b28474c76082fcd921d6218451814c519864a29bae347cd52251c6497b93b5350e7a6a78a1a8cf6ebdb15827605dfc098d465c5075f1626ee76e1996b2703e21e857899f4f8ff0944975e4850886dc7a58119123f3a1cfdac665f074a4e31a4268086357ee3afc76e1a443ea6320963d4575f68b6d8631ed44e88fa3e0b23aae2f7d564bc76d12f1bf18e3e0c5688d449381501e5b208be7d0653a1e6420daf9539833e9bb7eafbbe5c1cd1a3d567044f6a8588c3e5c25c6ba46c2fef19e77a4fd35a93878cee6e66925bc3ea33cfe3a4210305418b35440aa09f767cd521174eb723f891763da047ea9fbb3d2d2ad8c3a5b12cb5f52b3257f09657f7335450f44d6a73f4282ab4f1f3938fc6720fb7133bb5d22783196176ee494217a5c59dcc3902d2e3353212ba33708fbd40e93039d2e8e6027e92cb4a4560e681a8e81b67a447aeaa7d0fa8c54feb5f7661142ad8ea9e219a7ce0c5298512f0939d0504043e39e363a0d5c1a4ef6af8ac4c8752fc5cfa6f3b57c9bc49793c6abe1f81ff0fdfe3d7abdee78060918dae0a6a53e698ec8b4a8a18cd6b446c8debba1365d17d6f1a8091b3436f9414587e08b94ed1a5ec4385a63e48d63e8ad062f551ee2565642c7a5a0aeae5e7381c22c5d443d91cdcaba525c1e27c50f9d5bf7c66024e50bc2198246ee25c0725c545b2d531dfa22d68592ed1d6e1ed5d57ff704197a6a7694e0d2b8b86726c09348681a6a85cf3cf3018e06e79bf78d27a08ad9f0a0a570550ec16e784bdb51e0c808296384741d83659cd071690124514f4c6a56c1565f683a9663d46963d1c5433a32e3584507fdc625f9b9736c96ad87652b68bddabe45547ee245a66ddbe27fd74c6ae5e7cb95989220402e835ead8efe755754c49ad10536d67933486238f800c6c500b0d688ff76468695dd6a1556c7d0145a07d327ab10096cf94327b64bffbc49b1a775c09d0bd74a65c5be42c2400685b8f9056277a33d02e0774a3bf06632a5a0f34c55a13b9dff1876ed7d23c5fbc028af4863eae503ce64b9d98c5a6b7d013d93f1b18592200b62e4b61b342f2a3f81c374032768b694b15f4d0b4d046e54c624f14702b400c1f4bbac5e2a4e8669dae885ca3b475e8e36519dd2d09fd496532f1b9a717eea5bda47c28657e0e7a392a15408baa5aa1a5b698179bd557252741d602799833a6699b964c44bc1c584d78753142836408d08ae67b632bb67ae282c1056334ff12ccad41cd8d489d3f48446c543e552e46766c1661f2b654609a59b936628b981b00f602df1753be5f0e4c3424d476494509333fe7f510ebf7095f9a19d58ef62fe244482dff95b41455e450f08af170f245c5a6e3450f7c8dba958de12ad5b4758e288d3d828d381dcc3529d500e3255562370cc86e07f99064ac94c66b4596de88e9f73f392a3c49e300213d93b33996cbdeace17bf7c6197f3101f1f6aae7dcd0fff0974b96fe020e085966d75d14bae1499e185a4f51090e390bdaabf6fc2940269dd74e7e25a4a093dc44b0cb28374662133b23c4bf87a5b28829291be6e37d2550b94084c9479223deb190edf97ef97b909fd2eb0f95c756254979e00f97331198ecd9df665d6d60a75a2109bc8a7efcc461f9fcd1e0b4578aac7d6be6769d81b6e28363d23f0fa923006ec2ea9bda8979217f5fff41b6cae0e34b98af46a0abca0133b98a5d494c4da14c6138ffccffc1e5f6ba3ab34705118a98d56f4217cda078f26899e206d1eddcd75e8533c2c127b7afa1d6e461c1c491b8ed35d15c99b4dd954208123770521d66bdb1ecfb42d0327b150663881cd5c3b9478d2dc441f093ef4f53d34127d509c1daa416b15fb3c7d88725d3bd5994556de2e977afe7c989590ccf40cea5ad98579ebfacafb58e6fe304eedb90282047546960ba44ad755854c91326f30ee28782f783218942aabd8d68883be1280fd0aa5845fa0028b89c0467d86255b5438422a9d6643db43854ddf63a7784c3e63e7efdcd7fe63587a8fc9a6ccdcdf86fe6d5b612561ab2c8c279228457f06e05b771bc3c9aa251c1209c07504c500df567082d05fcaddff65080e1445ca4911156e7a1f38d8c510d355813203c596ac7ec80c20e6cecb4d371b7261db1068d4058cd733e8e5830f6d263e393177529a6deb3030ab5f174842fa4a24d8b29d515f76f76a8c356db9ec73533dd8e716f6ded3e43d25b70d1bd36ab1495fa2a169a4a4f85857ab88f844808e3054b5c3cdb2e4761564703c126184f78fc1ef9bd2a07da5358c31634d8b0472f226ff9d40c9204655ba310a47650414ec78012d653e2233aa7d82104107971811940fc70d50dff601296da58188d197f3acf5f9b6aa2017bb4155077a515b3b07b146d76178ff0a2158a9f9f46b3aa542e03fe4a14811a54f64f5946fd490227a59f693710ab0c0ab033ed8ddc442d3750c980ddd6500dc05e93de89c85c28fce54c0e6610b64ccddf229fa53536765c8103201196d89ae494348668464cbabe83993e6bbba1254e33d6c071c7220444e0745c13a28aa86bd7787da41590df606aa5a2781e07243047a4742b967a28cb73f0fb782718d3558acf7b91fb5b81f1531926a50e0c9b4f8ab2aaad11c5a5b1d56711b843c9f58cff8b5196fae446f55e430da1c78e027bb5907dc3c5be4309884985d192be3ca6c8cfba63089f5f24071f643c3f5c51530a043693f2a749af0a7abef2a23571374583411bbd54e94d1869682bd1e51891220cdee9ecaf40f6dd4dccd781b02a40ab041323707b594b0f7ae8a794ce0e48d8acd63826ce34b0e16025a314ccf6be072007a294943b5ef281a0b9c7e3bf71565c0a5ecbdbbc05323cce48e5789e56795fb41213cecf9165ce0eab871f618b46e3c87f9ebc3df7c049cdb18a89f744bae2835eb3ba4a381a9fd1ebe48586baf5a6082385afa130944dda0949edb84297abca59884e9ed260ca038002c7d464aafd60633f44641218387fa7d30a0d09e780d70a42af028b0fe18e2be708c1a2c998a42a54625d2f01f7853f04ae9286193f86a4091fe687e0942e2064869d58e9c83bf7692537341331bab922899eed6d60cc1c0bb30523b4c57fdf21f87f7058d645f55082ff74836d17a8a3a6eedc1bac50e61856e76c792c46972602edc3b1cc7b6db6d4809c068788b657b18f2e6f20abc43dfd33a8911b8683d3436bd35395c5e82761b5bf41cc1111c71b49e8b28c4d1c10d765e522a088c2ab539d7f480a9d92ca4f5e8ba434928bc8cb28b7d74d9a76c9c6681dafec746b22e28f364ab55cdaae040c6024ea95155d75d19af42635280b41e3ddd7c529e4c804778a1cb68a90c8152b832714e09805c320c3129a4f55c57bc4b27a873c0cf1ef3ca79055455ac3177a768cf2280b49ebedab3eb320c2fdc683de501541db39b55a9bb8f8ad6831ad899a2daf11914c74b1ed8ebbcbb63283ae218746a6d5321dfe4e9ae0f4035b9ea711b7dfd53a7533d8a9d0fb4f206b271cab01e3a8da7f6350a1334eeab066ac635cde4238f1390296dadbb265a4f4701b8c86e22f9401f28eeb6d12a3abbc884c01c9cb57465f78212ad07dd2a2a58f4e9cc9b0066859486d5f94011d6df15fa37f14eb41e40ca5d8207af814512893ae0895bf1d73cb5c79914bbfdd1e1ab198854cd20b5e73b93a0e24747c215459887bf047f927001f4b91950389a8d1e14637d1725953ead87535509e09100b41e5a248c4b140a287fee81c2dc9f41ac82ce59ef9effee294ba5e3cdc9f2f5b067c31b9070605ed483192cfad4815ec9f0f67d17af59600296b8c453ddaec40b3178615f15e1f16d468ab79b37c89ac538aac90956e8a7b4d6583a5884cf653f4af5c11d35cd9376966cac448e6cb5ff05c7bd156efd1c7a2da1b3b8ce8a8cbf47f46e1c4ba87c44222abc589e59fd9af296035484a0a216f5bc0061bc2898f7d07a9c4e5728441f2c4692f08674015d11aadc961b9cccc5854d20639c0fd9a124714531bc823b892d893241ea528f104556d50d153d635f12777e683d65a38bf5e1d600854825832821ae31ae55dbeb5bd8229e648a3eaee8ee3ce45dcb12a4bad7c3f60d57ef8fdd94470785fe9001b590805d60a01fab02aa35f8160c56c13945a0bfd9f5f20247a1a55c525f938eb998b4f81204aa31402a6ba1bb2a004816682139d3ff019ca8ac2319340761ea85bbf5b8c67bd33b37fd035f18b27c31ecc9e7c8445971441a204b6170894b5a5ee2b6816c2988ceca6d47aafe8aa4cebc406328b208f1e3a9642d4acb4e2d002f9883c773f347e28ca722a071db42058a029d6e6a8524fbb2c48c8e0c7b2554a49f49f4945bfdc4406f38218ec89c5e694c5d9d6ceb41813f36d53e2472ac8399dcb99d8f164644c4c7210f8190a05a40c918454f7cd67c51450e6629aca39e5503a41685debf1c956a8f07134459a3a62ef060a6f4eda3646956359ec7cf41e7e2fc5321a9596fd7992e8fb72c5c051770fb554525d572e9f3d27f67c9103490a4ffc7409ae720e5d46ccafe6b34867506e3c0eda076827a6b39a86c21f64fddab21e1ce6271cc5327eb67954c6f617e2a3172fcde18ada22721e45eb6afbb1a95bdf67ae6710b5a5be53df6d6531e7bdc92943f523655d156d155903db7218342042b608c16e325538e5b4534e22f57c82c66f880bcd37c2c5c334981f41be128ef696838146fd3ada2a8d5b08687ff51d17ac61f21a3ff4d991613dbf945080b5211ff0752c4c7019926e15c51ecfd0a219b91880aee10abb221e1c25201866716b0d0c9b1b7b3f114a990922e03046feb762aa8ba43d39555272e586cce660d1b255e40af60fb32371ff52937944da7f3d808af74be128c55102218c6463de71351f600ab87b53714278691a0189d948a684c32f16ccbbf8cf310487fad422a4b621bf0a8e11590f842d85aaf4afaa419f606ee33f17ff6e21c1d9c963f2ad32604a2fa9369cbac897a17b6a615c864f851b2340e114940aff28a1b48df5bf18bc8c570ac6710874832916e928d271b1b90928f4c441a9a6928d34c0bc50ef1c735795e43e4d9ffc09b78010cac8455bc17e163a8fb215f67f93452604d9c12b87103298af65612a23d5a1b6ac52f8a5e084621f1291785952fb1d95843fb877ab0b3cb5f9d08c6cb9dd64ac52ffb46946f0312ad6f80ed94c45fe40395462becbf1f32189179086c6eb1d014da46adf2606a551bb57c0d870e0752c0a995b8e3164556fc6c3a688740a3494448fc76a50435a6c7ee5caffa58d158b94d9d033e26ead00f02ecc5e6be17c2170c6bef54a226d35998b6f809162f33ff655c6e3b366b548bec8765ca790a1c3a4aa4f81612c09948ba2b63ecffc7d4b17a4a865f5e2b63c8d70dc757c9b9ee3702381608958a81cc0e4e4cc32c9eee876d12e8a8aa5cddd752744b1cbd96d657a222891a14c401c254a66f6a72bc88028ed9acce3f369ec171a6ba4ecd2eae11529bce192136dc228b6172ccd829711a526d0943c0a38b93149f67ba7acca6b1012d3bb04149bcbee31e719664ecc83ec634823bfc237336e409e80945c9596b4d6074f3ee48cc6b6e3f61f7d47dc35fce658faa4c6eba62861481eaacf08ed68c05e18798c36f6e4aed3930b834749d4331ce18b799a447f123eeba90f175b0d6e5384b60e8a118ecfd6b550f14d02658634e9e83d578a9b7304528f7ea8c9bdfa32e33a73c2c6d79a560da078d453a8e1c5490a4920fe537cfd0b339fd5b979a86521ca5fb4f772c97437699c554b00e114b543fde856991be64176619d46092db10ee8378c94e10d906a9442c2b8d3082408189eeba71033d9720ac44fdda38f2d9a62c4b15476f538fc2acc06c8875fff3a1857ba44b8475d849c12348c6d220ee2a0523dcaaaf42ddcc316fb41cb683d751dfee28825e142e2398c3181f12da05e87f8652d5b3d8a3d85dad4baa5e1cff61056c0efabf1bba0128f9ca79821fd0ae8f6b9f056c929079a05538e39fb8195691d4b6891726103cfa4e81690bceb0046c3ad8cded88f428f3e352b765093b62b3641e6ebbcbf006cf39a6b4cf8decd17850520fec177c8d562f60f80e95c629d3838ca299a141144440ec0aeda47229efb5928cf3f0f9ea9a99c2f0d7a5e7bbc348309a644d00df31f28c9b0a69734313f51e9f59d39978a5befafa17626c7bcf1fde6a27f45f82b8d9a6d40d5ae58f7293d1d42fefd3aac0cf8f37584d920936f88400be852ba25271a1f9db78c2405601766fda5f150fa254fae680f0ab29101b4f892ec3a4b5a4ea339fdf63514693f51a0d83cd60021dff87c3d09406fd864c8732e04f03551257e2127b42190b873a9f2f1f490c96c65b8846933d4b24768078b20724fc01f62d5d888e29d09f3c7a0a1447184c9e57420eb05311c305230d36125a4bc93e1e07e98286a9e97ab74671bac2fe3a8a4709021a6e1d82207ae65f13e7543884bc0446bc1602dc7243aa2d5a20a6a86cd77493de89c0b0d36164b5615f792cdc59eda4b766de325d0986400d67c2d43373f7d36e90f14b461ccd2414f5a0d5ff1ff81bb64e7122dfe0ad7b39813d66c27609e0837f5385ddafd346a804630ce953365463ef07624a309b2704e6a461be22f047d81f512d32440a7bcfd1f27c013e992dcb2f457fb746de412fbd32ca8a35ba971dcc4afc5f57da60fac931e889615285efb6efaded541d92ae7721ce0b71c043d20696442b82bb4b31515d53839510b54c7878179fdb071de211792ed3cb44176c5269acb145c8f6bde8b439781e242312b876b8900c73b0f569055208653a61ba7bbdeeccb5fe932e8fa629a26a4bebfd8665b1f4a748d1d3c484c29f227d40ffd7f4a067eaca1a3a2fd18aaafa1990b921026ef253a498e6625fe5f83b7f3b4a7612a0168d580571ed3217bca1fb5046931006c8fd14c45fab4e87840a9aa74ea1aa3b521109a3c5dd7d0069fa3b52b94c344d2a8a2b7ceca1e8212758ead0409b66974de892227621f348a5588b1b1ec7e83dd23a92ab370183c544b93e35f3cb10501c228e6d2a402357b682c6dccdbe220577e5650a8ddb6788f444e2ab72e6e7e066fca7f67ae4c5099ba76ea83bb286dbb7aea0a95016a59ad0d22816fe009973635321ea6a8e0e5e1b5703d65ab00fe8ed5e9499d3a92cbf9a4c66c0d70cdf251fcfbea6aac1d073cc70fd0ce704fc46f7c2b6eab6d432051030fbbfb1f7bf9e6984188c0177d864ae01ebb28789ab59c3a6747f1567e03bdb37fa37f3af62b42df3cb4d8c56fb2540cf94fcd56e7e28b8be0c0929b3c42cf2e7ccfd8a2da178841e82a1e0417a0ac76f9f573a7eb91bf119f0216fb8ddb0479d1631893b1f09aaa5533c477a751e7abded7862eb04e03d4f16c63aedf2952e3494f24fbd40e49781c278e04228ce723319fde3d7165eeba45354700e122c42d83c0534492f1fa7f506a1fc4de0404aec3ad87c6ccc1f8c8afc7a83cef15691b11e55728a687b1bc45dedb4c3f2c219d158662d822d30372a60b9954a2ea66169906ddd4393cd7791692aee9694adec486589fb41bb0316ccb8005e7e9ff1e818219d2a25332eb60c18106fb125a2490d248d64a3f416d59c72b1f93f0a257dc3d4852fcc74bdef6df2919c6a09639e1d6252c7feac3c5b1a969b950db2075c2c5377b0231642dea1ae57853f0c8a0863164632f01a729a1bfd6b7158709324a20c8392451d1828e635e9605ccd5e1478def5c803be80a782c04a06704a9419f018c0368e24d55ba47671ca84249f5b7c299290174a2c121f38a9f8d6f600789d60d54a54a7d88f381de9c9189e80bb0f0e8476b886bfb71fee32c7c8c9baf339b62a8210eed4f4aad4d2febe6f6a4014de3bfc4a33facc4cb22c23160800b024dab628165e9121c34c43849f289e06ba04e9b668bb266984de9caec4fd2d21b018322298e331572dff52fc2530065e3cec618b592018c497a6553f6f4720012b47c6767a825d5d7acd9dae612c933af0b6710ad3a75e6d094d0d9a4a79a6d66da1df253a12bac6d2eb2a016147951c515f93bbb189b202c12aa4a03236e48fb36619fd32e9b626394e1b5b854dbebb0123f0de2a5e23db7232ca134dee5f098035c8bd22d8face40617c13a1831aa3fc051c8c9325b47a9615e9d90c9a668f7843bb9705df379638ce5ee1c0b4ef7493f605f5e0e75baa4449261af9e28199c34ae08dd9095a4c2050520d7ab548674f622dc289d9c401105047f93c834e3daf2a9f138baf8a742ebfeb9bd6b0a6351dd30a32491f640372d2e94778ffd0e6f371024673aadc89bfda8a4d4af7eae95dc9bbcb978a72add8848cc4ce69d12216a9d6a278d766553e26205f30bbc279bfeb6db47dac701e63950c405c3414ed7d4ea99252fd76019a9382d592a6f395ebf34b7aac1e3f8b0637b8f8fb6408f98391381d5c63a7cd7b62b4ebb4a60590165399763d3c18406c37118d542c4f43dac84d914a836c9cfd54252e9446fe7a45e07e7be5a6d282283832a3e28ac17d4bf40b91957080cd29625c9f97285e092e4b12c45b7e03cb16855a5295e3f53118e0420aa70c3491e9dd84c502165b5cb74575889ae41ecd5e22a7ff7f64035bef81c6fbef65af2b225ad26cfff023b44734b8b555d305bb52928095a96fbfb36e773b88aba60a12ad97a033a354cfa539ca5f72789527a61855ef6ae16f39b3e115af5a4f42cac1ad92ab1c607da835ae92d59f7cd2738bac072b7dc6c623643ec22193aed36ac3e31adff28f03b8b2055a000fd6d52037f381eb6b598a7a10271c72f139c0fac8843f5991de400f93a05bb0d754e2d765768a7b275fbcc28da46f9fa21df7b5afc5d1da2a09e40b6280ab2947fb3369d49b7c6067c5ed2779dfd1fe8fea40d10564cf7a2f4a4a18dd762ad98e68b0850ddc2c9ff1bec1b535486c22e4f3993883e43e2408f6c014fe7fd08d5812827016b273c969845a815464f2435c040f2fb152ca907f8c73db55e6de6f14690957e4bc72a944b44e76a78e130884bcb3d4713217d518fbf8ead894544af1bc60523926cbae4dc6e5aeaff860717a3012cd343f145331b271ce9f9f6aee6860a2c00f2f13dcbd4b6fb61d740ff74f3ad68ad0b9989c699015755c3031d2cd865aabe812da9cf60a30d914095d566e8beee9f4ced8ca2b4d2518368919a4ce22be30c31087ed19e2ac6044b3fdf96d06a9a275807141b7d70a2c62bb29d257f9ebdd49a8430abd9b637721fcd20f7bcbbc5e234268a99b54331f2dcc6977950b7d66c62c92cf9e55e2ce052930448262bb16c5b3dfda15a198d3b19aaf19721e489acfc960d2ce58c693ddc8965e66ef7b801c5bf8222fbf3a29d325be866c937ca0f32d642ecf3fa4fa98c06248ce98a8695a55f89c8ac9537461ef74d60f722f446cde45f7909628af70d47e8a6a0c7b5e9b088573fc8fa7264934f75ccfffa6fd27094d0c2992d06ccbb7962304ca4204517c209b9e0121565e68c2336e51b318cf69a9225597457196c4a554b2320a774b24760697459e2c24febb0fa28c37ab0dd3ac65cdd7323151a3c6465dc35d130085651d8378fb1bf9868079ce2ab6f20da621a0a2d43ec8fbc54a0613358ab6e4e73383e120444288677c90c7cf655ddcab2dc29e36783cc32b8131e1a08fdc53a6c7f8cb323da8f8d2601b516bc218c681f3d3f5d5a3d195611d9943e8b6ca605d8b43d6dc387aadfd9cc2822d774d2c866bf3ea673dbacdc0d9b9d1f8bf8ea1ce69a70f4bf28ba110a5ef4aa7401a159c24b74349da136183236ce5becd515c9cd55095102c6bbb6af3b69f7e639d7eec9de0871a5b6c1b08d2e17e9be5a0c19e57978990cdd91ac12044a0fb7e75d912a48f2a309d8dd3ece5ea83ad9ed2cc5c56999c8f4dfec34d91292de3825cc65269b97f1066dd2a53902612e532f98a9d11bbd50a1b1ed13946cab887f7c54dfa5e8adf2eee5005336a9321c42709c68667d2c6c284929bf2f0dae7c2341cc058e488575d1c0d87a455ecdf1e7631c4efa47970ae9ba8136b31fe729b6bc3ca8460b6ca23120d5e7a9e4ca00eff1a5c3d50d93eb7c3975c194955a347bb84b70413c5cf852ea598966ade81e5febf3518cca27d5c33aaa4d243c4a19ea4eb3c0d957ba7367c04a6772695fa3f693fe489a3a752f7dae978857e4c64a1ef3eee96b586663246106a93048a209c61a76ae7cbdef2379a85bc730ccbfbf6a739ec4b204a41c66cb3b96dffb04cb2f3f735dfdad900da63f28ca20ee18771272e50f3a4f808c223a793b1d77a5ae2daa6485d7f0072af67327f42ae8044454bf70a4f3156e15eae82a0621df60d1184aefc46ea7c6d1334b94479b93833fdf0b7ae0fe82ac46685452a8028ca53910f1e98683068db164d4d8dc28bc46b9b4b6ed1ebf37b5400ef47059e04bc80e09691e7b44ac857e47709cca0cded996d920765c4fd735c17105d115e7a1d52fc8e820eb4b82e6bc23d7124c67868e805d5c002addb07675c03a39fb7d4e989f93d6789ed2322a22aea95c8ddfa1ced13e9e70c1652fa145d814b13b5c2b523b359f40aaf0f1520015df887f025d74c1b2de68e390d49276bb6c81657ad0862063730b05dd7041f794fc71ab70eb894fbe2fe77ea624bf5a8510a9b7d0c3dd6a3653a07490055a7ae2bc5a37127542fc314d837aaefb5de56cd2bf4cc4806d2e0095d2eb01e2f46d6d20e3cad42029316b6c16130f2c99899df1d282d63df7596bd04d44319c2bf62c23a793ebcaac6fdce7d481d83817984be2715f7930ba9b27f48636d062cc868ec1fb3c9864f06168c504b3b49da74f248b700138492bf01e579033bf8eb24eff6595a5f1c1f0961409013ef425792da8788f0c02112770352d70387eeab7f35fcc4926c40355faf952d2b25e050ea4a8a5d82f932e8292b4aed244a048a042c83831a5f408466d2420e7b00e848a125389061d447a0c4b86a7582414486ad819f83d51c84881f03990782891458020f29b257c44a03f55b66942baf74c631d80ddc3dfa10285ceda9b095b0e3ee533a84d30a8160c8a71b4e6ccb7a449cc05159051a0a71f207b587e029b58015f31816d29181b58e84d40cb649bcd28778fcffcd84a3ce9c8f6755aa69431d7a4eda6e11a962da696d6d44667afb4d177504f37d7cfe538875c1e37ec2d501b54d930f4ac3e9bcb53035a9d9395995df04eac513fada1849d7f673dbf9c0789937f7d335c965a121ea68a0fd4acbab9e4acdc8fd7ff5dedc4cab0c51a90ea15dea32d25037a4349a0c042ab72d7e4a26c7791ade5fdf27c86968c212d1ad2729376642a407205e613ec32eef409d15f50fc77c564d64b5426e65cc74f2d60d08eb9c892ddaf4181893c250e422dc7afd996c59312e87d92c1602df110b292d880b2f0be69ee9f2be5dd258ea25284f1ac32ca2c78b4a98a58de42ba6d7cecabbe3ff0fba1d7294558ce75b1fe4dcac98a13cb4fdbca461884378fbd03090c94b409c46aadeeb485dd2541528cb920292f0da43810c2987e82927b1595871592330846b54cf20f043ff9bf73a4d694f351a8945547bb68102db5df2be729b46628733cb6c242f9eac671d6f524cecdeaa7315f1e17e79383bcd1d4d6f036247729e3d7e175e1afd94779832119587f825ab621f574f24ead8ae8a5f248d348a5a5749d39cc3eddd502d6705796841983aba419cfeba9bbca991440464440259bbd413dc6cbff10db90e44f29c48ae6d64a8cd6c7ec9b0caf534cbf9d50088e4f61edc8ab18c013022b0429fb771d8775a49b3d0450e1c7ee7896e10eb9a9253fc5edb31eda2fe3d8d069a7e2b88abcf45f14e4198c3135e745a78b687370abb4bca94ed17ce0b37bce7116b7e0659f2de16d0f51d30c97911b8bbfe2bcce45fc78ef4c4cc48369773cbb9adb7a79b15acdc7c5adcdd28b416981f3cf6cc677d6e9f3c0692c1c7ceab86c8aaf443bbcb56f438aa0a7d9b2e6dd498a3be2b07f7cd050bb246b5acaa03c2ae85d55c1e1f37345b9e16b4afb20a696240670f3b81f3853a17886e4adcc86912a90cf3b17b6d9a71826b15502645851f488e6d56ba750308bb07002d9ec35554e9fbe5bef4ecd2c5442ff336aa782a329b5c962279322310f8dfc6dbe0b41a1ee16535af4347d6940def2f4f7ba9bb3cd5fe3080354ed3a9d368184b1b84ab86f8edd6f6a17ac39e20a84520154e33413ec460e5528106927a7e6f5f920d09039515601bf2df24bf98f5d95d9ba7911430750ff32b4f8b3dce6e63bd0cf9c66b9e3df251f0a6cc842a96f4550bd558349a505721b2a5bca54ce284c22dc5d4b81db1c88e3cdc44eab583ac25f32f58d929b94e47c1e8e225994878498a7a417d25f4bc6349627254a0892dde21e5533b016b4921add78fef4e7807562356b841d5b58b32c5362ea3e9dc0e8f47faadf8580d38a866a954bed45be233cad9bd845407f026974e9bfa7a5bb05f054d33800ed29762fe0966be6b13750b15b344e39a45c1945934da42033814e1adeb2ed19e1cce828d66c932ee034ac0265b8d34aca32f9332d9516dabe3a2b5d3e15eb1e9e4262791d99786922ab8680386bdaec0ef62ae376856b501baa52c4c70a4f51df056d4b6043eca77deabbcd0bae3ec1e557035a0ed55554611f7760bed0417f927071fced304173bb436b5d3ce61789ba951ac3150adb632e0493a39203d7d0d1c32a2ac9b395c0db5f5f36f8fafe0faf6daa1e4f969eda1bb610a83d304c9b8ad33cbc6094b8bd4d6ce1fd5f9bf0c6d3bd5be95b8419efc5c9d327f7f0d0b6f25ef41153c6e1ba8a5eb632cf84e7f8aae5341715654d3af81eae480c42682baff0180bb5511137b526061ed694fd4bf8c7f107510b7cb5d5d996f21e4614762dd0ee0321e62b6b7f6c7eccaa6744c02afaf6f7917a4019ede24252cbba8e057d641da9f1b0d5d2ea90ce826e4c98755154c137439140afe2b3b71ace8b18468fae401bb09381e4bfe69093c49565a523b98362a1af9c307e77d5061a8c73d8a3b3c67b8c4379672dd35a39b3f29369d826a2b3030c042d8a682c75c7d33635a242b164eaa49568cd813310cf47a57c2fa8b6875adf30174fa7212b9ea5ad9bb0d837266e2d0578a6f3b178caf4813700d9166b7a7b0e03ecdb2dd1b0f29189ce4121e2682a76f1caf9a56c496ff10bb3b4b45598a5adc66d6aaf962cf91f15e8e90410a6613718445f9c98c88fda311c38a34e8e24cd1dddddfa38b72155a8aa06f44c2444f3f715f42ce784b48ec288205be3913af4ece5bc9cd88a439ed470a245f48ecb191a31770b834ad6825d971d4ce7a3fbbc87a6130c91057e12c305864bac6777f8f5a210cb19ac66caa36d07e8f137090b814420fef8effa9d8a267505fa84c4df7f740152fcaee0f74156a97a70803624416e0601a71e3ff42f3933801f26bde4508b056394b8d145972625adcdfd32f925eb07812ee53d4836e44634f6321071e5a9600de3b5969f873b07f2dd0e36965f5e4ed195789534f2237f4129878d7b0a1e570329e20bf5659033d6fafe5cc4e43e7f296600cd220567633bb8104dafd2dc242100fd6cb277aa21fa7e1b17fdf0c21e7ce300e090ed7514a5bdda75d489a0b52f8b1c7b4b319f13f024ea6ab6b374a16994f54c0def0a2842ff02cdc3b12ae8cf24652465436730b68bf8f19ad86ab8df4b307a15423f15a39818af832ee0adeaf4aca35907ac1770440b36fd0cb59d460d3911473caf24c25e32a5f9e9786244a234d90e14ac45086b9c21d9fbc916918a2f0b0d60fa8a40e204fe3c9ae50ec1a29d1e040730e21460b21017a31b4c98ba7694bb51897d8e0d9e4440ff246328eea284991ef31d4b9c69efd157f0e28b1d02a49dd72d6027ebdc745fdc6147c66cc8a1acc585aca94a10f718c51185d4783f8aded52b58c2469993ed461fd3b6d2db80424a8d64f68395245b3c2d0a6ccf85c8163e747ea823e35fe9b3ab38a8b1bcb8a616386715bbdbc306606858dddddbd755b9ea47e0e99ba5a3f36c7355d2e165b572bece21292afe8c6f41aa670ec18d78cfca04ed2b203e1542ca02a09727fb51b900b7c5d03f41fc35e3a43007da0f2627a52f4d23035e10e055a843c716ad2ebe1b75eb156c836ec8629339150aad320c6940864be2c2db505a3513983104ab7b537c678fa9a8acfd795d2f44c1899c25b11479effc5eeb4ccbb5e92fb9b83769c6c3c7b7750195e825186d2f43a23f9b5db8b74b2b30d6387a9252696780ba3f7788050045b39fffa9e72336e70966bf5338ebb97290b908d7098e29b32e9958a1dbf40797ae94a1b7f1a705181de921f0e2e4f2fed6297466a66a9bc5ced1938a4ee25c9b0264fcb3542c1632c04fd0720f01ed73aaf8145143f6bb4e615d2dbfc8a08bc8cc7bb7964648a45358a797cc59049a612516a321a70fb61659585ee2d90fbf9dba93da824c37affed964806777e12a9d8fadb1cc6876628081b5f972b5559d831738f713f7ada51e86d7bf3916dc9033750f31d4c6ce5118f6a5908bbdc10c7fef4f67445b2c97756bf793c0078d89dc698554a70afd0510e2ab5019a3b4205253ae8322a35de0ddc32c60dde5c1663b3f14de4694627745bb5a4857736d881fa5db9c58dc7dd2cc29d05d17ffeebb968fde639df6ae9465041357843858a76ce6c26fce7e15259c65fcb86c4b90eeb06fdeeecc98f2e7700ccacadf47217030008bbe0e57e4dd3e8b7f7acd66bcaace611b82b3d78f1ec3d2975e2d7c149c93ed2705f6b2f231080edfe8755f06e148db790ebfe82e504555f4e90af55677fd7226fecfaaf96471996367958f722cb65508666d72ae9b41eac95b502f1815957d11e7e3d6023e5e02e28026ffbd7ec507bfb75692d96333f5fd5f4f6b1d6c65c0eabd324a682919acc669562e26d1fae8e9d58597b0e29b425df7180e85362737991fec6cea64ec1def2e97b9b667b8d152d2fe457581a8d798abf9beb9da2e5d628cef0886afa9ed9e943aeea395654cd5062424c4b336488ca9f6ebcca645b7fa1479aae523ee252156efe07ece2e6b4e636e804a42f1e2646973349caf2adca9299c93cf87e14a43c99f00650f38d6e325dda5d1a5dfc7b7d11cd1ceded78080e8d6d5ec0b36d59a7252c9ce7f2886988a603ff4071f46a4cf8f2bc0c4d3102f1fbc0a20d27f935a5690b403c574eb8d9e298f1043255a0294ac0fce71592ed0e5bfda3d09f0823b6c1b470ab2b25e499e539c4224c0224e653d10b230be1c465002c94f88a730310be0d06924e86af7d53035034be4d432c1a1ec8af4d4a9c4696ded98b263df0fcf140c334aa6b6db955613266cabac04608f18fe21893f92a5b2b587021841ded5df3342ed38939d55a310213d5ec9f0b7e3588a6b7327c0b12672d22817a7d929474f67e2d451eef2aab1429651be0a6c02d98169651d187ad3c8630f8e9233d962a6c225bd8bd191aa4726b5090e83115d3b4026ad361a9a22b33641fd487a2876a67c61f44a16eac81d578a661c53b23c02de806050c10347d579a61648dfe9a73b6f3588e8ba87fddc40ecc472de85e66b4e982f620b646131f16fb13a308d34d98e5c2a45c41c0f9ebdcb15ba099e73458532616f6f9f559891925e671ce011523ce0eb26ce5d5a0c76708f6df30a765c203a3bff683c3ead84a14333467309aca330a677f60e89e9466943a58ba6a8c55feb68ebd4ef4cf610e99332f1173135630685d09d01597e3d6723bc9f595e4de3a3b44494addf4d266242eb5b7242d010ba3c11a6394f731595f7221bb11a168446514b64a76b9acb7136d851cf760465ee6f97325145877db05820c6388939b1ec0b651a63617db06dc259e3cb430bafc2c91df21f3e7fecab29d78a8ee5acdd931575082784c217e2ea01481b063856de5dbac052ed20a9ef68589a0604c80a38161464ac5f9e2bb6650bb36e46f643375c6573286b538206edc1992a15e1a4bd59ca73852830cae947656426891ce8e2dfaea90cb3b84fe7615e2d03f925204d594607bba2931e54437484947027935850b1c6a92faabc4036a82c8afb038891013a40c337628ef12acd4795a4dffb55979eb49c405f5bd861b6768889d6fe033c935e017bdfc9b0b7c840d9a2ea451096f27497bab5c7e9d6fc2f37f2c68fb9919d9d6e9a92a17e4d45544c2c221195cbb2bf5d6665666aa90eb4761c4cbc608ea587a41049e7290f7db67873bc7c100e57cea881582491781727c57d44bbf23279a4d529b7ebbfa6aa902d6cef28e1c8c3007459eeece6944f3ab21b3b90d6998d8dab879cb513c0d58f324faba3fdbf94cb5443f58ba6ddfd5aecbab634210e6f0392304a4e16885541114fa86cce0d30a6850238977e496ce59fc4b05c3e3b5fe335ea5ad72b5ec57a59afb1c0b392d841332235ece51c69f824863d99836a013bc15d52b642eb987ef5273fae6ffe8d74ad1f81d83f76b8b2e5c3f20a69983a002d96ac97cdd9ac033079e0af59c5a2a40b05abe4048def77caee86a5157404a1233546e4890604a28328c85131d34f1f183f22e04c1e2ec3a907aa1af526d34a8eb41787627fb225224e0bd1d6a2e9d3bc0ff7143903362ca61774ae739a1fbc2f087accb9fd47eb6734e86909c1c7f483d4753620305e8e8486435f51e163a95921039ba0dcaea74b4ef1e74dc64b4fd2d4bb8fd8a5327737d86a4720c8c3d1afceadb02e58d408dbb33cd0629a0a876ae2ae4e2778c7bd27a555bfc48e7e5ad16b8c06e239340865c01e73443e4d5613e70da842762d7bb1b328fa6e75a76288b3ce955dede62e9e77e9e728fa1751d7a76ad41b87c14b8a796d3f5fabbea0367aeaa2e23a4003a02125e655cb26c01c68f15d391ccf84746cf155eb4d8054575465c07baacf011fb78d36f00dd34131489962f23fa560e5d1addf3fac0dc50e65ad0e9fb06ab4783401eb42224201e2e5b363e804ee0d78a76ba3a3922ee95e2a047390e48cc76ac28f383cb5e396071d3a8482d6ffb15cb76d8a796d6955e583272af68b75965d53f39e89cea3f8e36724cbf2e5934fa0015791df495ea721efa063f20d56dcb9d903e06a4c76e74d0b81f88fedbcf70d4c924263328f1371a0acfefdcb3f1b7b5bc7fecd6d886b7d6fa47c7cc3b35080178f52de466b29967a848523c9716ee22bb3687ec21992271d39a28b495ec4a75bdd17b9a2c1de3168b19a81664a3c7e6c893db373a6646dc32ee97194774d5dea248c8801a98e9700e5ad6423f8abcda636ce35afad8172bbad9941ed852df61afcd1a8d8d7055be00158d46963a8e3f3bccf43412528afc1d5836cdea7d9894943ae80aea65a098d8ea98666744972ebbb01ce04a0042660bd600910d271e2b427b6332fc8340c4408191ee6ecdee6ef29c6f72543b3a7b036926120804d331dc157427e66a4342fe3b90e0a12ece8262f1576194d4465cd10b58027c27325a97669a08b937a2112a65ac486a6628161c238b6d260c4218d68b2d127f8464ed401a90ffe7b672ff6d77bda74fd8c8570d69b4505c4a125240d71c8f48a8c865be67f4938a79e0c4b674933bcc48cfd4b4760d3a7de1bc074c5df4a16094dd54b26b5a362ab11345353014af4525d307ae041e853923612a8adeafc45de0c68658d685a8bebdd6da1ec28e471cb10dcbaf7a6c376faa12b1a5c47245cc040fb528cd8d64deef79096205bc25bd355dd41a3ea320370a714bf7b7065750e64d059135c0b0ff03179936a50b3744ba99134ee85e33eb5f76b8502d714b04094a2f3f6195dcb586a0a385eeae48bf91b4ef4286833812eff4a6b10d6556536f2cdd4c53d59a2d160d945c940cfe1903a80d0d63a12650fe7ea156534512af69958d24abc17d1144d514b72bc3a91f7c29beb5a86bc51619fda1abaf1f6cc9bac1eb604155cafb7af1001a81481fc2716803e1014a94ab4f050aae6da9bc7f0611836d6c6eb89955fff8baccc5d6e5192d56d824c9e9da523021766c3dae05cf6a87afcd3256cb6f6c5257873d1658494042d827989a74e23e88ed60953ee45e7f2ff96a41f50b485a3a3c446742139270ac03cba47bd1097e208d16b19ed4519f0518b9825fb7e9c2ac392e7a8bed38457aee80bb7886f88782e384a1d5732df4e8b72b5b017bee1c970fdc04e33bddaabaffb3bda73acc5dda3ccaa032ee68989b6f62332830e014df92e12b46e6876b9a7d12ae55f7a253d90a3d20dfcf4e438f291ef25d9d8952b4bd28049519ec23366aa293b4d0caf89973626cc9156e7ee48a2d52808484733bd5eda3d43544b3965420a2e1445748548b88ec11cab4277b4d711263da676b1e2b700c8d5dccaedb2b583f843433899985e781654b5d0f2c532f51c8801e616d72c82757c5089a12a9042372b5c64d3c1d981b07a8780359bbe0f96e51af28c814e54bc4c7345c1c97a89f6dd25e2909b0f7bf396580e69577015324108ce482901a4af86a7382d919008bf8d5c4886ed6ca88680336e00977889b2fd80fac7448863faf9bc431dfc7397bd5ccf0020af331645c4050424a074ebbcd882aad8fab7ff3271dfcfd1539572a6a5fc5e3fca760bd70628d3d2574cfb39579da6684bba974b6bb407f20d5cdd8fa235f84b614cc70d234f34a33e26eac4082bc3cea7d8d4ac8269290bd25d9726f296592294e063906f305a1932219cb1dea70460732b9a9c81dea90c4579ea33efd1a39d571da71688753af24e9b14cee69dbb918c2262e7820bbe93c6c1284bbaec615468e9408253b88710176bbbbba57cb40906599931c9ac8c1cb980cde9f546015c34c2083df2e4a91310cc3304e867bca1d3241ca7315320999f87084ad8ec33dc618a3c718330b05479425c9fe59fff6c9d5420e792ec933ab15bb688d33694440031a982162053b530726552293a27c73874c84bc5810cac4445830ea37e2e9e9e9776cc2f7f83e2ee8fbba8e2bd275452e2d99eb40c6b560331d22f0040c412c29aa82648b1c72b021871d9ef59fe5f0012a4c391cc121860cfe77e6bb5d4d9365666aad9b0c049821080b26bc00d102144b965cb164cc6949134ba8683d2b8c1e6e48e290844b82421c94704492860abc60065374581ac2923471f0215cf203d7028724bc818bf0062c382317c422644382942d1b4c52ee5ce215ae97bde3f4e7ffb08089c42c39bec4442e6fc597f32f6cf380b86343c2f5f4ddb18361638ef4dd3f88e315e8fbf4c730ecf46d82207d145f89d39f011448984f3f08fdde48b021813ef6185e613ec5403b5d510852714b1c08f4ffb0b0d39b109b0b076989f33371741c59987212d9f6efe388741d47249ce1288b44f5c70e2202bc745731856dc7e117ae18765d94ce29a53b8679ae97b2c7f5727a3b0e1e7f0cc3ae8bd23965ae3a2e4741d24f0128fb3cc1dfbbea6e56ddcdca5b5406ec767775ef54f202eea29ae9ee4a8916d38b35539454c31819d4a03e4325072331507879d2c50d4561283192d118728e9820091494f9a10a1ea60d45648a5ed045bd8a948ac47461a6f52c2d77a8c48a0cfe77b51047c0a5882cb02417fc768d29610206324818d972874a7ec8e0fd222520e880121a8a844a70c83377a8a428045e843580c943610892a4ce5cf041ecb3afef2d0ff36a996bfa0662fa68c2dee457966511833cd8c76dd23ad3745dca055df77da9167c5f2b8b304998308917210d49a612d91e3b245a0a3f400406b69d688c1019a22c82c8980940ee902809a22b5d27b9e0c22917d3d2b03902767773619b057e2ded314fc360ab55475addb09b4d7b0d6bee616c0acc056b6e8b798dc1564aa1e0d6075b09c32b8a44a316d66218d6dd8d611886610da6c160b83d70476e77f7d69793c2e29a1eec2d13042f56e808a541b2e9311931afe7cb2613bedc9807fbfae057b1f63ad42a839afdde91fd9518f8602b37d66a533a49d031ba4b3927655dd7c6a36550112d5d1557627095634eaa0457c62ba574fbe949f6e69ab4d344b3ee29bbbbe6ee56214ba941f95a5db06bcea03f33d565de4829535dde6fb012585f7a5879fe4581a782e6c9ba9152ca13f25481cfa41886cd97d50acd39db5ff82075c58d3b727767a911e6c69772b666ca0473ca9cd40a6efc54979c9aa5b7cc1c398e13735a0766521d5d6bfa59e7eabbf87990b611ffbaefd4b968da6a47eed7691d434e5a4bdeb0e815b39bdea4c505afbc9fe9bfc8fa2282b79272cc288913b70c6faaa35b6bb6da3e25e682ad568ecc1164545ffe0760e4eb807093a586c1cf3cac6bb867878d5f5b4bc48eecd3564773e4481cfa2a1fee45858eb8aaca55f170c15854e438a260a10faee2d00932c76d69c25de7a98e2e7d107c486acc055b890216701cf695889ce03f8e03ec69e5a882081683fd25538b7d668ef14adba0ef9e27005c653016a98e62d1dce29ee043a30419cd37f23b4f1c07d83377e4e8603dad25c79967dda1356258a696ea123f5e2ea9c7a496524a8a35a5f4468dd6a7ba5c4a23a5988c1785814edbe81d3925b0ace861657aa3c5ea12bf6f6c2a05373e8d31c63ae79c2da094ceeeef9058abb46314a3084e6374778f116c6511bc9e2f471dd789dff46d83c9f1ad96d8d36072bc45717c1def76015642be587d936bf4b072af22a635629a4f8b3d3b6eb79627d6654e966f3f02f17e6f0c6365ec4763b8592d20f2638c11ec72b7a6cf19bbbb654c93260dbe966a5f57f7cb66c1ed9262c18def616c950f76774f39fbe7a4294a29a5292fee840139098d54d2053f67e127eeccff313241014148593e0bd8981dfab553f16bd67196e300eb4d4b7a112956112684814938c4251cb221445214ca30942241cc734a4f0b73e54c188396308624e1109930862774956526214ac2e50e5ff8d27dd77b57ddcd9cb3ca8964ce3993e42954449e4246e439efc460b042517cb879210c1bbe200606173278ff4936c7efaf77e56ff2eef4eadbcfbc483dfb26efbee6f95bd4af36fbd64e9cfde5813703fd0c9138641e0f669272c8f5d9444993e9e9957a308531e89ea569cac79e6690e20bf43323b842a6af232768dfc779b62151287a107fe2066392cb2d883d51b65f9f4aece359fa78c630959867fef634daa7bf7df4c0a88d805dd806fbf8f6c7d3bfb00f45a1361fd7690342df4e14b671c6bfbe6d3ee6f35c18f5da0644cb736210c5235ad9f9cad806400ee4f934538f661b81d03c371c856207f4e9e6e39a7888fd788a5bbc36cc33dffe85a1de71506a31cfcce20891fa0f8b411f6983fdf6f469a41bb639727d648e0ffac86c9f7ef6264ff391f97afa3c169b7e6e40b41caf377d463433f603d36bc5f77169be4f4504ecb8044dd7a191138d04e393607caa22425c09828b2ae848082d61a88a5c2113c416cac106390824d6943f719493c9111b350cc619040eb9860fd95f933a1eb72b61ae80b9e9be39e79cfe4d77a4bff47955465c39e7a40f52ac010e32d2952d5fb2fc55084498c6545200ca1ae0803ac07b2f082785f8fe2a23aebfdb08a5301f9c58031ce4f8f188f577eb1e10d959399f6e3fe83be6f1b791f8877ca00c94e3c6c3e6c70365e901711ef91ecf7ce9ab193d8c1fb4001125836d254cf6ff204816d9bf70652ed84a1650caf18d2427c7827b71662ef82fe57f672982b33833d75fba771d175df77d63be4f8cc66dc19aacd842626d61657f96b6ea1cbd8417435698e922a98a17ecf8df748e57824186212e31bc10c44e0960500504559088f142ab224315214e06b7335823b2ba8e1fbf7aeb4ecd5bf2de2aaba46d32994c2693c964caead7eee84a3a9572a600d4a366d16432994c2693c964a28f45d3d78951c0324f05e9a70094e77716311b300cabd242c6c478d119b92083f76da8d9509655b112841523b6c84c56a664569c5c2b339cac0469475fa872874661b2325ef840a44a0d31e894b22ccb321b188e8a38a2021f15b1bca839418c2d55c60fcffafa956bb2c556c40710843e10715b78d132a29267e40e8d96d84aa085165a6821c1f824189f168e86e568582a70f08646eb9a530e72fd99335c3176d189e35c1557f396572e05373e585f064d01c11129676af822258ba22f538a9722b5d61b3c7728658a2ba84cb1c104b556ec48ad950a95249c972b36c81d4a0142ca940cdec781def096a41f98db7ecd9eb1cb62767777c7315d5782aefb3e2abe8f53818c28989e4317ae4b1747c372342ca75c97245a43286508096ae525ffba1e285bba8d10d3e488a76bd1038b6ddc137766509bb475d4a7d76332040ee42b4f9eeba9e7fa36d3e28cf24429fe71bdbcf09541d86e1c743e9538e694deed817f06e2773b0de84f119cc9f3a50567dc7ef44739b710e80ff9d325be787aab717b4f193d04ddddcd4171bbbb8b889a08863470c78e2951a2e4f01d9f53327823bbe6387caec0ea3de4d3cf93676a8e83cb8d0f85523a5bca298dcc288fe08ef023babb65532417edeeeeee239cc6d8b1bd35c7582e0b9785cbc265e1b26491b59522162e8b129705ab596da42fe2d1186f0246124d7a61156b0733e9855d8dd18bf6984967237d118fa6ec2ffcbd81880d5463d6420825698913e27608652984a2d443181e96b8216e875090c21e96422858be3062ba94abcb59e1ac7056382b9c15ee08ce0a3702ce0a678543e290e08ce08ce08e686f40b78c5205dc12b7df65cb88e294b8fdcd449428210f4547577e7f941d23b7c4f5ef233826ae96baeaee9649b4c6214d7a572ad971487061774b2e64558d3ba2cec072576e63d9a24546914f96c88c9173527a5d18bd2e0cab35bb34acd62c33996c663259ab69db699e4ed2e3c96adab69d4ea8ed244fa83b9422c34d8e931eb92d60be60f12b7ee444671c072772e60b3063b2d8a205293691930e45a11e23e40b9f062324ba4b3927eda24b3927a5d7a4d78561b566a66932498f26ac6699c964ad66b29ab66da7d37642a1ee4da5384eb55ac5882143468dc66952b5f988f95bad34192386263d199b8f986fbce5fe31eae97acb5f725876762818aa54830a05703ef0404307a6f030e4c214a3f0090c9e7041458a9c522217e59a72874f949e8091389d310c4aeb593b9460872932f89f50734fa2f4d8e1681bb258c21d98e078e881dba1ebbecfe90e3a704ce464e268588e8625c5c813233577e80407274a8a38a121c8c90c45b8269c9312ac4fe3311e1fa3dbc667a88f1b90da24633f9320caf6af8dc78c979e8c9fde0903625f6210687af1ede603fbd5576ff5298fc7da473de7d9cce8ed877c2a6d667cf79d4d34dee4d984609fc6dba7816d66e01e40997b9b10babfdffdfd3bff7a55adf53b8ffb1a8f7b191eb87dfd181e4feabbfe0a8356c5792c8fa7fe05bd1c9ecd2df68a6d5288325bcab26651294b59bf37077c1247d901404848da83e297dac9bb9240caf1fb1d5063bef478487c7d03892f6777eb53d2d4c36a95c3b595d0a5a122baf36fa42fc05f333d7dabbdd5b00d09fedb43c03fcb26983dc5b20693331f9975e4254dee35b0ad1ad6a53bbde69cf3b211fb53141840819f73cece87b25c9b7de3c5c436ed5899d618dddd298e1fa56702cb676067f764091de92fc67777afe7cb13bb09acdcb8f6bb57dd8b6d998308f8e90041f57c4f7e17afd83a5a65e5821ffdea43e9940bb8ae8cd7348195793455940b5e0d885b9dce49737cb2494ffc48e78546db3c30276f0f428c31c6b8e90c5dedb5133ee851734cd236b4d7913959f3b44e0a2273745d5297f401089562b0fba718fcb20a945228f33d67c7480275db505db92b6f752aa30b765dd775d98fb20b8b137cbce575870b462227bc7063c640a01c67965ecd71f544e694de8db77c53ed70c14f4a0a919a64faa0c7237fea78ebe3998ffd8fd611b71ef3b1f8818eb7da7174eb8839f2d05a478c2a2637e786d45a470a315f1fb1ea314bcff4d2f4597d1dcfae9c65207b9f2b9bea6758c5f2a7498b524a8d6edd36c053e6f1a36dd0296c2780956bb5947e8c96524a37950e17acd9f3663679529a7890e0c78ff437cfe6c8cd7e7b4b371e1b9632662dbb7ebe8e742981f85006ec006d512a51fd70ebc72b72204d9a3453648a63c29729e6f922188bfa81bd3d79d9df388e6cbb568ea322b194e5db8490bdfc0c4bd0aebedb864ae5ad3c307b0eccbe1265f9ab472292a976b3f1b83ef3b65f695f3f76db0057ef0fa4023969bef1b87ea532bdcab3b9fef49d7bf3917dea3ee7813621d0bf4fff629beb6d4238bde94f6f0a5224ee08751b00ccde1ff51e8f86afb7228f6925b1f80bfbd92a11dcae2b2b4b1ac39ddfd9a909ac1c31cf856fbce55ef194172b6317cb89e83a150c5d07ceac61e0a499eae581126cf4804a20ccf245066788840be1115f204194bb7624d6a2828a683c5454eebc62ee98c5115172874664c907c81d1af142b641456aad547ef350c9bfb08a27063d950b773ec555f5e4061132e41a5dcf1d1a2194ef511adb4effcafe8ad227065733c330393bcfd879fee55d1f35d7681b418cdc0bd47a6ff7ad310d7eb3b7e4e46897def556931660111282cb105fe42f7738441339cb981184c9436867f3870c21ca3caf4a1c9f7875d3f2c0ed0e23e59730447a2b242aa70b1e3d0a16cbd1e4389a1cd511295d4a29a5a391d2d1f874348e4675244a3064946048d964293bab75dcd602ded848c07c70476e659b4b2f7b9347ed066ff2e8471a7f79fe14780e0000d62b379018e8870532d83ca3793ca86721cbb618a6c5d73a8b75bc4cf460fbcb62702e0141bd8641075bc94a29e50cb9a16e5e3ed87363bbc13cd8a332785d3802fdb0a0029d6732e8efe33c35cf42be3c2733bda01805e7d2f5357229fb08358f44e95a31c5577322d873a3c76b9ecaeb6bde5568499cf9f5da3be33402eafad8619beee97bd2cd3f066b663c0718bc1900d806d7e0117ec8bffebac8440fae8f78a933c8c1fb8373e9629ef8358fba68fd1b0cd6d89f81c18b6d300a27c5777933e337b8feb4f990a7dfe4552b00f015ff06db601a333038b3cbf8273ccb681b0f8abb534daedc799db1ebcad1a339d6f824133bb8fe724fbe8e8c353a2d9923263d58e315559f0542cd9ff2403a1f755493e3188933ff8e99919332868c075b8e7b6eb8d71267e5b594382d712a8679e86b2f51b34cf4e04a61f0cbf422b92bf5dc007b6e681f4ad3343ccbc40ee2632a74dd7fab7534658e2a71664d0d9ef8a84ccf8dac3dd87323a3dea43d38afeb756d437b1e140623b601d1b2f62ac81c33e69c37f297af6d96891e68b3cc5cca19866115c3b34c752214e824319252d6f2f51389882858963216242c455892728f6fb6dca8d18fc1f695e2eedeeed976f4284602e58e51656655e54679faf921dc79c5eaa8458001cca8b5076a02b8fe2d716cae5fd170b1546259b7a70d4084e2f8eab0deed79d16b1582fb978a20a594524a293968e1eb58375d6774c1aebb79566b83fbc5306c8f980170737bdad09871838cc09a7d01b63d9c82a8c19b25cdb1714c6f4d6ce3b833cd437edd78ab6a542e78e5bb978198afa717861267fc6f693e0d6a4f809c94524ae7eae84a29a58684bd443a56ad35373a76ec3a19426cad31ee0dbbba3d554d23e66e9051e709902b293786b63de8d801f8df2b2b176c6559e3bb9123af79d11415db23e67b1bb33d62465d1903e70ea5a822f706b6bbb1940f315fbcd55224e5985747eed5e613394ab125c767ddc8f1477ace056b9e370e41e914a07c638c94ba8ced274a371f4057f41b6c0c938e72872438ca3772875280915138ddd6b1db0da588927d3ca380e5146a504ae94529a594528a6917a59d156b6f90d1a48f6d85224d57c61ddb2cc751bf1ff35621b8d9c53ec8885ecfaab55905c83c58ab2917ec3a6fe938763f176c8f982f9b700a22b9c3a4a55c80dc611212f6dec2b0aa92c1fd6e442f0331c79f506a875324c9314c42cabd39a63783361477dec27e662d0719c920a3e8ab1aee751c146904764abffd7dd978838cbcb37d838cfa8dfc09757318cd2d04f9d74b77da330adbac99aade5afdf456462e58796cbbaa4c14495c27378a2c32a8658fc24b8e7fb1bbcb1946e14528459093b1edd845a774298a32cb1dcb0c250c61a68b2b9e0c91e08a113e705992e3b6aabb6d727c5517511071e9810b240ad042e8090a8c4831855096289a6cd105170fe42b0d105d37a6eb5820468c982340342cf1220395263c48c152c4126a0b33a82db20da345105c8acc00850a1210318313135ea86419620c957cf5804318212866e842070f3881a4872758d2644b965cf10418275a10668827c674b922842206288e7cdf97eff3a2b2627e0792c8911ba94852929caaa4289cd0b2e50a298a38f1e589284227aec83377e84415377c1f97efeb3a28bace89ef73a2eb9c68620b91266808910c59010d5f783083948219a6105b88966822dc72255fb9c32d5598f83e2ddfa744d729f17d4a74dd08baeefbb27c1f5250ad352854ca59eeb0892155124b78b1c4900a498b90aa0855124c88800925b007bb2c7eff5dc17efdcc6befc7484866adbf7d1516727a897fe29f3efbed6d84ee15e7572cdf5a97f13164788c3f5f48fc393fbe0a0ba9a9c141627cc43f5f8c978163e01f1ddc12c7b15f61ecebe937cfe601f5ed0ba96ff18f91ea7fecd7576121a63f616fb2f8e7048983bd7f9661c73f910826f28ff8c7f1cf8f9190ec23fea1c0f6a8ef7eefb412f7bd13b1b711babd93fade89bdc3793f4642eca7f04fdcb1affdf556c856301292e1d86485ded9a28dd08d471207c3dea68809af402b5e817e635abe608228dbf6f61314fa3bfd24c41dfa16284e4ccaf7a988e83a3955445c214225c86470864a64112a112654e2cc1cb2ed95763b46f3b3bf40ae375b4dcb0e76f2e377dd4cf433af7a3d5dbedcfffa4a59d2241d0c59317b93510f022077b35a35be2ccaf8652d6b4f1e76e58d66d4f57e7b6bda786c98dcbc994fdecceedd78ebe4c9ebbdb74e9ee7cdbbc1b493179ddc93d779f3a84a877b33cd2e96ef76dabca6afb967b1e8337dd895c9dfbd996386639cb9e56d12c3a5356190954de077d9d295e3b84fdfcad55315063fa39e6b2455f6944b3df7947bfa395a0c0cd64c15c3feecf9b2b556e5cdcc79edad946751f64f9efdcd568b41d34bf05e8da6a2ec62db300c93529e50b637cd62b0f559974b76668a75ec9885956f69a83e70c1de02829459c51c6cd9761c9a167ddcebd251ef1b0fd4c6711cc7a5521ceeedf4d4070af572e381fa54ca87e6537c1de7381edca71e859806dfd6a3f0c9f3ed370cae9ad334afbd85c1d46f5cca036fe4d4c90381f25b94173f6e23c8e7b6bfafe339e5532676b0bd8e3c6d9a9723fdb1aa87a20e045cddbb4815c4056311a8fd38c5a9eaad2937e59a102a29b75e975feed82c3397326d32c9cca51d86d048bbddbbbdb5d08efe912847a421f105a248d4f40361115f4222b0844a70099350e2c62433cbcca5b9349f47fd0c850960a62098a4af238190c8344020268d459e3c8d5390d6dace9b5fe3496f4524636f13429bb5a8e46df754ad848a46000000006315000020100a0704e270482498a792b47c14000c7a863c865616c962a12406711005310c8410429031c000430840c821876a4700a6a92a2475697b91446d409535993146d72399a161732cac2c7d4199355abfaf3750e413bde1bd3ff5040b1781c2863f0bb9f404037e6a01f1bf9329ef4318b9057fdef5d6592fef472da03f1d9c0b58ae1773bdc5304a3203545646ee11bb15774d8634a13220201842872818ba1a58db3d5c95f6c7c17308f9c89b4c55a488b34ca58fadc178c8722c9f954c5cb95f8851b30ff3d5670bccff87793b9ae9a949c68910e44325aaa3003e1558f4c177d06bd8dd71f71237a86dfdb409a7d23e1e0e8787a982537d61fc4c67d9557cefdb88c470e3ebe35088b477ebd5a017f0a6db69524df7649269ff4ae70aa991ea49fe51bde9bf5404a0d75323765b2738fece35e0882d9b4e46b9023e1b97dd26dd81cd46f65ce256211ee280d05653501d01abcb1960ed04b0f4326552293860c9a448da33578d5e9f04a0812ba5e2ae1c42e1f084595b42c12916c9c085d9c1dc5505c0e03a2c4cd26cd7b147f269ae016cc0e1353c8518b10d20616f8c73733a7249228b40a5dcd5f05d360bea4633d0399023c73fdcff152021c65a54e446b7b8d23519f68a8c02526bda5bba6a29d5b4f6dcd4586430f37ca73cfe70c7898e1cb1a024b5522122a3f3632ce2c89299e4736067a5d3bdb69675acaaf78ad6ad0b7a746cb290eb76b9f6d3667481646a546c078fe11238dff7ca40badeb3a507ee2507a4d769a9756cd31897a0142322bd7eb1c897d06420e1d8f5ba363c24a79d8de39fdf0a56a7c9417d0c57328b704517cac658f330cfdeeb7cfaae5a46dd7a9716380741d6879bffcf957b1e51ccd9edc7032f908f9e0a838c46b838347dbfa8945a0cf11b31d9921151c98d3df48995c153da26e97b18a96d0b00e648de3e224f2a728919308709f3c62fe46882fe9e522af64a820f6927168606b41d6c4d8caa7203af23cb88f62c36d3f85c4025bb602b59492df4d5c9df9beb6980545a82b512382d0551da8479f64b0764eb164ead8c353cbed9b3bd4e0e1363a3ec1bb05841b96f4bfbb52f6ebecf6c31962807bd71b20564a0898bba9aeee899556916ac82c818e2e428ab3cfbe28ffff9c677a0ef43816d38262eebcf6af4ce88a4ff9bd5448e87c8e335bad22e9ca07d72c6e8c97039a50ed34f405f0e104a8e57462cec3c111e5f209018cd5c1648621f81bcd8e20695f077361a5c4bb04c5b5b576a709199505833de0d1ec920484daa7eb8f7d199d96e2c02e39f248286a3f60a639a1ea011163c64208d0d4563a6bbd01738483e138797477a849aced6142324fb30f9212eda7c07a62c269b99ef279f74674cd2f9099a5890a59fc3af87052043280281f2e6723b33fd346e4fe142ff4c90b8586fb1c848cf50b4e9969a19fa8e23316fd54d5160c5dff6caa5bb0534812b6836a8dc5476b36de1c024e9b519cf806211f5dcf57e739e0ce18ded4aff3545905dc59bda11ca9110ca94e4162e3f106d96904b5e78e9d88b0fa2c304690ee6f98aa8508e78af064bf1d944b46719df405b6b97f0de1f3528d2153ac94c0b612c4d2bd1787d7df335f3b856d408bbce727dbffb02084092493261e2742f76233b44df6cb881b1aa3714411e44e4760cbf0cf43525aa1356bb1e1d3a69981aef664110ef76990262930dd2d22bc218080c06f724287172064a6aedccdc144e12913c25147f9599ad16102ae505f9ca1544cefa74e8420abe411aefb73854dff1d22e4015949120be83302cc748cb05a63b28e28f80f688424232775edfdbac9c498899cc0d3d47051307fbbc52093e17d3712ea2ee77809a2063b723dee36a13842ca2c76e901358a619244780f08661d719290dbe927157d6f5cb56a7c6534119d7faa71f1539e58424f257b83474b91329ba15df62af72ba4e61764478cc4724ca438a22affe01d5f3d51523eb4a1a3d6b943684eb7f0dd04bc0d9299c97db9b9ead528b42789eca06259debadda1b656dda2217943ab864659674cd1a917cc219205b4e1f5762984ca5b096623fdea57ca0e89a159864b7bf4e3004ce891af6833ddaa7604f77a431d2dd61c4c82f5dd23828b66f1046f130b213a6a29641f7e6ff1ef687ba0196d3f829c61ac8b1f6ee714c079590e7dd17f8066e79f6532b0ce3fa4c6dced6facdb15ef14612c302e2dbfe931deb3493b793e851c9b7929f3c794e68657aca03e1495fe968032961f44f665c0fae2cf8c04d055db51ebfc711bd972c964966a420c2f4e3007a6efc17047f9c3e70ed6c413395aebe3ee45aa67b803d2d18a519eef317284c6e155e7a13c19b011d746d44f9abbc8c5ddd97b95fc7ccdc6034302fe9db3a31f5282c7851b68e347bf82de454798c9c89ed4a066e10c4112c3f1515acf810aae500e80a228200e2e54f1f17a25f92abfb081090c89a80eb50283136bfd08f490d92357c1ac32d434f69279a8c8a97eb8db1a2e94c910d0c6ef2031a4ca81a5ebe89dc6d7032b99b6202086855ace1402f3d605dc08112005a5357206080e5a067ae9abe4ad846e2597ef6ce4b970289827521cabf7585a147c0a25a06aec294be4d86f5f6ef35f96ec97eb90236a4d02356041af1593c72d24454e8622b9c1764248dc10d7f81a249aacf2bcdc920d2b2c5fb11e30d72f216c6067422dd2c547e6f82b32c2337ef3e022b25adf5ae50625721e1b6ed4c11b921b2f4018baf326297d3404ce35b06288dfc10c616c8c47d180fc844bab970a27a59dccf0f289bce6abc78c3c8744e83394db60179c8a345e803222587372fde09baf9ffec630407cdbe3f0985005a0ebfc2bb53c8c12cc1974431ad2f9cb5d5b6285ec533975e93a3c38d4aa2b43e91bde6aba3b0dbcbe249615eea46f5574f478efec2be655ad1d4e913542c859c26ea2a81327b0ef77ad82de0cc6a9fa590a5b971a22f287db21c470260b50bb8ac81da79b9c040e9e88d389a48aa490c8d8058e5096639c5c27c722229a95b34a00eb9348b97f2cfe24d0194b69950095abcb7869aa55439237eef703414322ec8ebed46b24543c12a0ab362414512944a93820b44df97795c434dcfdc4633420ec10807a8ddccf7cbbaa43cabc851b92d6b43f2f88ebeb8cb23a5fc0132cb897a3d0e68c0201f9a032f07a7141529351153fb88c30fec1005fa1ce8c46f3eaae6023fadecc1ada3a3bed3b7b9a5818280477de2ee80df69d559760a37ffa1a556a609d32b8a1be51d8e07be3f0a0beffd3386292041d6e0740c3050642dd049dcc2b808f4ba2da595f02e1f1a864d63179a5c0afca0fb47959a2e5ccaf9d5ff2595d7578f33d2d4505fb98fde68743f0b2f3e3b879d218013befa01b38fc350cfd57b7ae1c4a82d95a097b4bbd6eef7ed4caf2a31f132d32caf43b6c33a4b2827a1a5ba74fa37a0042d58fc2c96e95f773f2ab80ab99b792bbafc3cfabb393bb4f55788919977adde306d6ea15d8ddb5e5f9b2ec4c01b390d07ac64dc972ce9554e99c85b1039dd55e1349d24315f0d39a178e64ec874544cbb3550be6e9827b444c60411745efbc3e59efc850b74a47ffc601d1e4eb9871e262a98f11404e1c7cf3aa63ea3e523b78d4ddada02b27904eb0603efe611f3bbfb52201cc3e978c0d0296a96fcbc17cc9f6ab8e953e2de44a581c1b7de258327e479713af4e6a28c3d351ddf165fd496d2d433cf8e42acf6c768cdaac0974d9b7c6e773e120a52ad3b353069359034c9f13ad4ac5718049145f704ac2ff5d5a6a42fc876315951a78eb689247a468a165a8ca2e82b57a84979c2fa8be7883a48d5a9fbea280a901c579847014a579c8494069d728cd0e2c77876e489575b70c35e34f4e0542f8328092a11e1d95784293a836b0f2507f8d2a90323448f3ef4863d59f0fa2f8800bfba4428bd48378321c3e5dae7a3a570fa7a69d5928dd93cb5567b68c5e5d47ad876291ef6a106ff47c67b8642f79fc2393d2d76846a793d1697a3d84166130815f700a6c4a9240194d746ecc6c05e1ba6602c12267c21a9142d3aed06de7c125281e19a495f4382bcb7033ee914732335acd568d886c39380f360bd0cdfade01c37aee838cab8423bbcfe7115ba262e37c425a6b7e09424e58ecaf7ed32d84478e77ce93f634e15df3ba4f22c7d4ceee80ef2368011e2454c071d0f6355af124102d83e9cce5cb02441442bcfafec9b5b89cc59ce30b8d0c54d11f8416d0296d379e5f54ae8e5f12daa74eb7530abd54fb0f81e05b0dc74aec2de61b032da7cb4db05ebbae35b3158e2439b09c68bb155deb8c3494c96c1f22ae5e29d8a798369b02421c7281224cd56ea18ec5746d5234bafe2d7eeacb87c1459ab8a15f546d32a0bd8592e20eedc7a1c6951de0675f8c285c9d1aad07b64467bb951ad57b0aad251a4e2ba4e78ecb0350ed051467273ec17332c7e034a2b62116ffd2474c5a1d1eaa2982df59ca0540707dd4c38f50195f75c4502af16acb33a2025970e77333ef8260b1b6e1cc8bd67bc16e27e666c47d9141edc623760fa6b0978f3ed083cc990454c0b7e3a08005f2c099ef75956eea5b991b317202b44b06bf6d0c07436e449090866f71819994968403d769bb2388e15187a8eeda1d09e9d4af887a95e8a6d8f8f1cfe4946e924d554dd67faa4be1185abdc0a579efb2aa93515dab3dab13ee3a3bf25b9edd5856ac66d5389ea675e89000bd61e6396b4d860395d3626f893370a16f136c0d4db3f770c1f00d064839afc860e91e3c42e5cd275527954256790c3302447601a1e879a6935bdfd5fc106a718bf63f12f4d4c334264ebb70868199622b95d0a9095af986f495dd5f411730055f1a41f5949673d1f547676e2459aae282d75d499edb44e5d96678600ac118b11aa5d82fed118949b3af06ca9339f09dbfbada5442410cd056256152086f473a82c31a7dd49ba06584c3c7af8d1a564a67436df0347ed26f42d7372b12dd432c6034a6d06b5b4e15e741d6372be05d75a9787b82f32124ce81d8dc4b090dc836090b698a171b99e7d1145f6098ad72ee2194700eaba89cac1405cb83cb71d5e975349a077cb42790a16bdd6fca6e16852bdc03b17bec84a618f4436e8f9c8426efa4d6c75289f81fb81d27ae1949614f2844c8855314e5754a3460820e14c1325dbc47f19e8dd0c4103ff4c6d9d1f425557262c273b882aefc55935416533440564a7bf056548c46668ebffc24b537ddfc4ff564327b06c3d100b43d296a50801ca04dc9dad31dfd14d2955865902c530f17f3a33a40345429b979a4dfc2edb3913fdeb0b29311f9ede34f848b07249ef7ac210e58186625a0902a1688dc0a42cb700ef73299ff84cae0e567cbf58cb24a2d4a0b610c8a38889fac13e545db41077c55c4e322758f50751333196b18796e984cda305e16baca79e42211b2a7c40b429933aa2889c4e4417287229845aef8a5a44a68075f329be3322ce7ab4fd492d34c35ca1de005adec462d04eb20b306544f1bd532b4b4c5b6aa4b78ea1fa6dd226aa9625eab35777e9ea527fb46f3e4d142157c46db65df8e2ff87de8e94452546889d9cc659e021569e40b8daf3659e5b6070d49b72f36ad6a81751d9d83656a42044317c9970fcca54fe27f1149869c345105f0af492f63e5eb31ce545cb8a87ebd3466cb64909ddf0b2fc50e224dca3cde964c5de78006de3591b9deeacf484fe16458737297235d16d8e7cea2254590cd3291a8447c90ad10360c8daef55e052a4841818c8255d5b6679ae77d4d1511dc2d0a21d50bf29f0b10172377125f9e9c2dbdc6032c49bc3810b57842b1015650f529e480c9c17abd60afa169feb205720dd601c12b248c60fc2194201544a93c314d0841f212f54f42b37dff51e87dc387810c85fb0f62ce4d0dbf91df6d44cf9a92317ef375ee63f3960a5cdee3dc7c40e2a8bd14b0a825c41e227c34d6497767d9c943ee080f58fcba41ec2f6ee71220d86c7304ea8a5416a8fd50fc08241f01bf57cabb9cc313e093f9a426e04b8c5ebb21d7ca59c1e67700c61e3da989d9af2e7c1dfd61402e09ce8e4cc4b853c5fef7af5397192610a31bee76b1749d289d31b15802c3e1bc0074fa1c004459f1b274d899000f82031c200c8359788067fda4db9028490181fb519252341581a893e11b0efec22539c268302c6c0fe346aaa2658d66c60d27ccec55810719911e84ea4f13c998c2c4d786072b76b06e985d80d257045e9a090a901d3aba994b9846747e86cf48f351ac4da7cc503b3a91c6d2da776bfc151f5f95b8ecbc13a8c99296d6e3cbba92ce5dfae2af480a5bf703794e3e35546c1ac406c9396487870dc0de299d6af88f583cd86fa9fbf4e59bfc64f62645fa4444e08366669fbf2044876ea50d62ac4f841fbf8953ecadd3cf8e85938ac91182f91591829d8eeb76a64513926153a85764a49e04f7c0dafcbf351407d9f10e8bcd6dd36a625dab6800e83b45f79a4774cea6a81e47c645c60a89006dc46553cb567db46cd716b2d880994a3043dbbdc7467271cc909ca1a632eb1f56b53ed0c82c5711288053b2b86f993ce9dff961c50f64b4f1a870bb5524893691ca40cde022a7cdc47388f2786f26280e0875d77d6d2c9a4bca0729cbbed9744794c8094a5be5958071f600661c407cb9d59eb78c28a05f1bb4ab548a65e31ebaa38856c1ee0b690764788f6db929bf1bac76de3071375827d49136943ee968caa7433b02fb47075910dcc215ba8fb3f5e65ae9133e84cc9a537e9aea8f0e23c1a5c1c4276d405cda705a2ff7d3ea2d99167deedd2e84ec9bfe185eadc6fa9cdeddb514a9598f53f83cde95cd0d4ce4b3fa7701d11f3fccf214414a866df5c4a0f4ca3ede8a7829e1088788f2cc3567fd107e5c976b32b9b574c2003d5c027c65018678eb96146b6356dd8611992323891045ef1f139bb4eeb4a886694b8db94fd048059972fcaf938dbae4590c6a87e2269934164bb7aa044b3c9d65b40920de29948c0946253fbaae30ed814e1348925b964907b39d7096f4834ca91c36ce7be54f0f5dcd5011207cfb02d1b15952d5a1563a9df481e7f38f365999caa024af67ceefb4c814d94c946743269e864672dff47f0bb4466461fd83385639f6dfd42539308a6de6366cd2e06bf13f39d43b2afb07388b5055aa3428716b131fd1d38b194798ace8b2177e86913fa8a188d7b4d41c891801cd0d51e075b559ee4b18934fc7b495f61e5de9e48da2e249a9e1d9af515baa371fb64ceca02b8f05375c8828d5f036523c35b0e8970cdcb469eb14371931c42b1f2cc02594c59c58b91e283ea2362b76c1adf0cac6ffb01c8b791b10e1e45229bb88cf5ae01ba1e8d0dadf016752244df78a866eb714df06cb9b3c0ae88ff19846448f3726b9dd48b26c5fd6ceb571c8af1eb4cf95d1b7e20740edb8df49dd22a097567cb60d30b26b5ed54753c9248a07bb04e0ccd26e1753e21d1148b5bde664245869d2622fe015f0e3067c0f74b3819cf6902b36e0050d6a815246787a38094683c4f8bc2e5129b34e718f0533ca061d140caffa148186c53fdf05064169b7fecd55d5f6a15473304c96911a72094df089891104e908b50af387f9c2ee1f759e0df54cc6bd4a914c4407e55b9235095f544a992618d0591820693fc19b843dca52942924d1592a5e2a5013d3b8d4243a46b781718d2bc2effa92fdbf52088c31078a9bb98e808d9a5439383739ca3b353186f974e0ab773d994154626e24813446065c9c85e44aac81201594533d3eac8b3a2dd016864244b4a2330f97dd48d4d6a5ba267d11ecdaac715d79486ba2152e857fd297801f06ce0191e74eac3ab6e74018e938c26514207e5d9287a6bfffc68561cdd899e8259bc998802a98ee7109f5e58a78073d10611ae4d2ba89e89b816cf6b4f2c1cc4261ecd5f71cac108494490fe5b38556bfecfc9dbdee3950784ace1d7e5aed9c0e8712f6aacb721aef49929b9a1c48c6ffb0bd1d0c8f66ca57d88bb8fa6bcb2ac5b56cebfcaab3f42208dd353e4c8cbc6b60ddc6a6c23e31cbd76d26c6b25a5be368a5d1e645315354158b223ff6388bb248975deb77a5f4ca4716c277cb5ac67adb0a3e46738f41c65a44c548f2cc59d0c54a3e6b4eaa3ac4d9868f489e327b7a37cf797c19f221710baff037fcea9fff6f38ff70a380bbd294352ec6cdff27446780de966cb527be40b651aa9b028c190a63cf1e584b25018a1a721912e5f93799e39672b858a70e70102f76729c0036471a91e6e11c71eca7600374c324af4eb8e838c84c3a0d7f6ebed987052fab42df9868443813fcf0eac0dfa11fe0b5c8f95d8f4dd913f41f696f0077369d3e1f0be7ffabf838140865b7b41c588bed674baffea3c10d6657175f910784c6664d302ea36e36a9142978b9255b7b9a62965579a0e6a1a4a7f90c08de1a29ef539667f106d546091408392a233072d7c1669c7924cbc47d69bc2f2e1f71e137d8c11246ead5243f7f1c7f93e7d4f7762c23991855781bd3082af840ba9e41706927261c85ad1baf6b229f0f23ce467d3ef581432a8fb0ebd69cdb95f883ab3778db8f2da1c461488022c3ff9b84bc15e088ca58b428eb720568929dc60e5a098ab0b4d78aec8574bdfd78d098627e0e1e880121f4dd71ca012d82f06817b5e5d60b8c587e8638cc0a3d19d191bd4a99b6aa8d58a0082f38fcd2764ebb94e4c82b145fe828ec8f6514d841c5427705f756b9c8b4fca199da03a7b51d85d93b938dcbc438e190795306b335d1c864d15177a1d88dd6e5cbe6d8688262c6aab1c20404bd19df49614d37657e6745e7c3ca214dfb3a1f9ee5c0534fa9525cd0ec989c35c937363fc5503662db702a1c863ada65c9e7e10ad37733488d44de39b98837f484736261e4d3801b390707808f1d956df13715a5016a054a72d4295ba43e6f737272a321d3e64b9e8068aa43204aab69866737d842774d73a91581c3f7d953da90f2b51ece2eb907d4419b47e2d460e89e6a992bb786042dacd9ae7cbf99a025268adec398cf89445cfb746662ea2b53e17d70368cd2e7ec7e6ea5d0076253337496bf9c1e17f29c0e5c8d555728fa8f3d231796cab8f414092bc65aebba6dda76861bda60e93dd6c061e0d4a805b19ef9317f00d916b616a971334854fafdfab54e953f12a6cf4b7cf873d3b20e502081b4c076b049651f8a1754bb9b9a80e12333907343dba98022181d14d9ac4fda10bedb611ea2d05c28650864c44fc130d5b0f4ef1ecc83c0b9d87b2ed8444d6063656800a547f992853fa2380782d36660200d820886643462a2579fcbe46bea4c4c8ce6205f56ee15f87c8b645c5fa1bdd2b721d0c08bc25875c17c60961f3a7ddfa12e5d868c22cee516cd4cb26390d00d626228f5152b268e80864873317a7d001ae52d194d1a09a125c221534d3cbd81fe30a618ee0e5bca2e1fee789769de0047373853e91e0e461e0ce38433c2c562e8fee0e79ef6aa954f3644d0662361cb0b40a4a0c59b6b5cbf39023b5e7656d6f94f7d2b16009a19eb9400b602152ea4f8cc9514ae154dede0052abb7666861489cc401bb487fd5c63ae8c2da7317532bbf208da5a0f7d779b7721ecb0f22dae2880e812762a6a21a00ed40a42e8b906d120ae4302728cebec015ed081f1e2b412fa7e5f3c624ff8ba96703dde2f2b300e8caf58288bd4fb66a0ba2e75dd8f24fae01b82420641fa4d4bdf61e2f80fc51d85cbd872c6f2eb6cb932186cd8c634d48929738f381014e360b01984053d6c8700f9c40beaa46c0186049048391246150c4fc5776e0dd1685913f876a14124ea3280ce41ac885f205bf5754909dce69a9681cdf8fc43a59d071449c4708556955e61e0fafc727daaf464f0bf2ffbed71a56812208298bca0d32724cd363474239616c62764dc8a36082089477e6a584aa99ace487df55f658fa36df53b444f98821aa1fe7b3054f117f9a26fee2d678c2ac710f0ba9fa45362c71ba9d509cebdd7c0b31ffe50e407ffcecf9077502f8c1c4c8b27f89d356342db4443387a44bcb2ea34e94e9578ca9b84706be965f364d8124884787cc6592844d1e1d3da18781534c218a5c14dee97b44871c0bfbe9675637b84efbded2b0902dd93a797895979142e6ca082aac7bb2141183fa1f69e1498221fd16492d05a8c96bb29ae86fa0d79593aad728d6788ba3626e29114417d28d309382837945cb88cfcfed8e88a956be32d464da7e8178ce091812726982b7a308e86e4ebe1b12c5eca3f67bc857015f6dd9e79f6459b1e022ada595af42f490ef02459f433399777c34090e9da804c969a50c85ea45a1453ba07e7c360ba62078c9d04f5b7248ca6af097dfe9f8e14f10ea5922f0a9df9509d7e7f4bdfe2d091cfa22d121e55bc1efff97778705844ec00667665d65b2ebcf861770135a35cdb4099c0d256884c0953725e80e853f03962587311d03e11201947022ec945394c2b4be7540cc5791bffd0b65cded6844c00d2fd37b26c780a704569ab7c95bb8bf25dd504b849afc11d96135924bbb9e54ff02c9395bc90eaa8fb5cb13722cad7aab13bb92a2171e6ae880b9b327b69c87a10950f3165eb07cfe5b4f330a0a6b65daea65305034af63383d19221b50cae44afef6d26c23760142fb53dd1589413542a5eb4724abc2c3e7c4decb14d2fdd059974fd18d048ef80709df57acc5bace9fb184db664af0e350424215fa2550695b04bfef8a58cd4a3060161aca3ff763d84fc64ef27bdcfc43f975980b4e26f130fd1f8722e8133e641ddefe1dc7ae8677d6de31e20bdeb7f0418ebf6c6109384e18d997c4db27a3adcf611e78fd7e31ef61c7feceac6fa88b62ed893f7b1a26eaeb1e157985f669a283b051446312b6876265baca2faec358fb1220762dea2724a03bdc85b659b8f9620a7fdffa07e6c785be3fca2ce990ebe1aeeafa9edc5c92915ccc4ae5ced2f986abba9707a41a42381f1c80d025fcb38a901ce1ac2e93baf0897e8b4ff5a3311a847d1795169f785690ff28fb201cc20059d88f101ae0f6d32343c4c20bc3f8f82a7a30924051041f02c165e119b07cdb139dcf99c7ea5f1588c4bd93a9d830feb0e8e027ace639ce840edbfc0593258503632400f84f7774c691f0cb502667ef20d6830d797f1087d54c5b698e915e0fa2721e4a7c239b2201b917d91715bb8d0d42183b708d2d826d8011ed0930a8572487106d3ec9f687e418a86cab39b66ce9f474f03ce9edcaa4acc86843d163ac9363d8983490ae860392803adfb1fd8a90743930239eb8ae37dc1b25acb1b67eb0294f4a6ad94e8a9686bbeed08190c40242b77160f769f49dd0320d9eaeeda070851f71e737a0ca897ab832fd4495e77d1a74b0e21b7c9f8992e8eacbf1957f7fffb3053500ad8eda85ded6fb2a79d67dba4e19b64214c0148fbd0a444d185421f844ae04bd8a285ff4c24edb5692785215324b6ad9fc3f9677b772d86d6c844329f3e9ace8b3d1eeb7c8957c3abb6ce8d380178bb41b8c84f0d5d136d5daf00e17aee210514d994a0b8d9dc614a72204b2d4fc115217f2f950c79cce03843cfa0bb406ab26c2c08842990f106abf8208732a7be0818da973d12be0f971c61668a8c2eaf313f25a74a78b5dce6e086951987a241c0833d138f1ddafa0cf107fd05200b8b3e6d0705d2b136dc87c298a6247d93fa03b979a86b07ac838fac2857af8bb8a7c1aa5b492f77e01fd0ea00257d12eb6c8b1693b4187343fff30250352871c7ece3540b27e7e22f047c59ea90b51659f8dd65bcb44cc5b851eceacde741836755cd7a436e72fe7709727dfccf10ca0d0cf59db85d15fb8876b728a593060498c8321b5d965ed3c4572859254407e7e903eff01dcf4a9db9773c5576a6a84abf054866f66add9e5b889f00b4fd489336c7942bb501d8740cc32a9a7a303010219057dc0c1e2c12b4b0eb448c337d03abc0f21292e0549c193a37901218a8af6d5ea6dcf8175fa39bc26866b06b8201fe95dbd97ca2b3d503ef7199c1a7b8b88dc40517b7e305622e7d318a713998e1b92e8ebc2122a149488fa8ce613afd741b38416140da3d699c936d9b02b2c72ad8608142848547f63af45f86687e53d006533d2f60d5f6c740f7fa368cb83f8dd7d2b5b67c7577f3f343ec1226c0426ba482b48d2a878d77444898b7a2931590d4eeeb7d979890789e8c2aeaae13f0b19fb7184e0de2ef5195024a8712e4e714b2190cc5afb086c30916f756c6b2ae8a9b5dfc803f4bd5f25656e7de6829dd012c05de384d02a8c9f588ef0e519177a029aa1d9b03aa4410a1e5cce07d35852c777c73bbb79967523844b99872780f2226b6bcfbd3b453c4feeff251d76accfb08cc6c2485bb8fb87f3e6245496577cd01e86f5e1705e14439425065a0d2a6d013f4bd0500c49697d59820b27fe3a402193f7b928b87c593696ac0818d15dc7f4f397e6893837fc04471401532cf75b8af5213fa785b1040e2fcc0f51f76026ac24a083dfa2d7e4629ef3db936318cef606fa85929ab20722dd36e7431526636e549de69f9f4ae7345fb5c974f2aefe399e20a5ff6a6e594440e4efa04b0af0ea8931741d41c56d131fe529bdd89a122e535369b0f6b2c982e0f7e36d5daf99ab6ce55332b582c7ad46794de5adda8d286997268bf61331670c43ffb372cc67e7a0968ee3a87fca1026194fad16d5ec2a2b1eacc417008963b28405d8b74fdd025c7f1b66639b71f9357bf37fc82dbbcf5a92f20783c103e79461845863a228cc7ed2f581ac85c8ac071fb30c68241958abc58f7651d837926b9024dbe6bf46ee3888141c76eb21b19d23798e6df4429e15ec579dd55ae770a0fff6e0b11aa4dd465af8c59b3f712059e28c370112ce6868f3e7338aecf730addc1468aeadc9e1d5fd3eb56c94aa3ad133452e8d9551a602a9b71a4306cdcdec253e912a973454e909f5c84a52bf2138d4b037f2539be5024ca482341edfa144b3b806108fbeff6b3402fc8a538cf777608d133672d090726c29e1bbb65eca48698b9eb30b0ecc1e26be58980f0ae249476014db9e5eced996aa8cd847388f4553ab0ab5a2d515307c553cf37c893801ba43e20ce1f8cd6e7dc49a0108b12f2d55e9276a514023765c58a344a6cb580b73c7dc8f43decb904276c0dec0411ad8c13f816b17dbc6c899193478060bf1d8f7dfa3cc488a56bd4fdfed50583adb59ec55ce1be0bfd27841cef33d0481eee83a6f4fc3910750fc3718b1e19c88839e803ad843d17851768beb2c52cfa0ac2982ca6ce78c7dbd45055311c5d1ee7f17d3b11ad872ddf4a8badd36aeecb8237c45c876349ef05445980433da4aba0dc234395f01373fe61030bc257d156eef05ad7be288fe27c3321e9f4c2d28df81b69a0fb4510916c7b0d049f215750d7b25866a2fbd5ab72869de0241b1602ab26e582f57495f5fa031c887ea0005b137d585280bf5eab8a20e6a58ee40d9c6c1787fbebffb4e6d3272e8a79e6af82e7b8787839c476440c51848d131e5401b20fd9e8a146a98ffd99e9b7ff807aa37412ca8b2b8cd07903095d478f99e91698e0e62daa2055565cc2977b805d2155799b98a7b1c4a35c62596d425fba628e2647d16e99a3287e6fe24b38f7dea4f96c1e3d898cbd489b243ce45b27b6e696752bfd8ec24fc509c9d1ea9e80d9814922da123fec79d17f93eeabd97e894ccab0c9ad9de394466aef028ed8f05a8f730b87e5e0c63f400f55aac1d441fa5db965005dd7c780fd37dca8d233c97536369c91ae6f384c2d3015e49a9a56720126ae1f15b2fe19d29e1309c09f2b3f443227307133eb5d0411283836137138ec5a472f62e88ebf58991beba751f8f7f709bae30c4e6cbe0f6185ebc7bfbee4bde8d3f33183df6cb993d13b459a4eb9c0ff47c28fffae96b63f35d2cef3d1567a3afc3740fcbcc56aa6cbe6ac87a0b6823db82a61a14d21d13c8ff0a91a161adefd721e12b40949e81ba9b99e852212ddb81484b7160eb85e098427ff1e6edab2f6f9b31bc60da3b417a48e59ae81c821c3bc69dfe168726aec8ac69e4f686358135fa328459232dbbc080d7ee7bcebb4fb6da3ed48879043b4512ec538f3c4d47bdf452b731e4e2464cb1dfb0df14e189b11162e0e7801bd4960e168d12f07023b4eee17e8f875e7056209e24f06c4411f606c3a633e709dd90b6c0b4b9f065006ea17301e8215c7bc8e99f474dff647e568e4c454cd7e49dd0998abf7d1ef5c3b6acf04ab2a748028224a42816058d91c6734c63a051039de32bb8da42f86d508c63ea4d8475941f107bc904320bbf0e2df6df4646fff0787df0a700d54984c76665c0595ef314a6385f038c2b992fb08f1218c8458af6487082d5792d7d12676546928e6388f0221dd0f9870f6e0111ebc6c30ad3ba3d1110d2f4d5759cdf8d53f791358ce9c4581ad29d7e2c74fcc76fd5bb54270bb714725ace726348ee702f9f98028d83a878645e2e336a9c76b38034d1b009e02ea078b4dd03107e531e150600eeafb9f432bae8844c89d53c8bbc21a6101efee6739b1af29de357d901fd57270ea37b6e8d3a06d930f25eaab920960501b0196ba8a084389199ce8712be20e05c4ced43d2a46e57c51b94d41108d27e3815ecd36e71ad000b5eec40b23bc12fed2e06caf5640ba521d092919cff327554fa47dcc5f42bbaeda684c38b1709bad76a39a2556c7b9b044db073ed7d32a7e57bf46568899549b082efe4bc70235c747fb008e005c0703ad94303c02f8f5fce1d19c57d016be07d964c55f0390cd263fbabb378029e7896039c9d970bd5ebdf8884fb12a4b5270beabc6e53e2ae7b0ab3ea209124665452f9918aba24b6c6ed0ac43ee8057a203db115044460d0faf6e5b4d4c10dddb3032e10d879e3f179be29990eb4471de6c725e9fccad2bd6a2f521ef32feaa7185184535dc0ca16cb6717c4c0e108695cf0474a72600c75b13700e3433e030713a282176f8c4ccf6198d3ce3648c41c2f80685c745915f7caf5af8b94159083082bc52df976d4c0d3f16885afe1ce8c6de879a7a04101c58e0214abd09a739abee6c244e3b049b9eacca053f5197f4fb879ed328294bb494b7ed625e14dbe55678375bfda5a765512a49f8c7e85afdeb96b065c919434825b04ccac6fc54c79b11b6180ed42b5d384792154e64139b6cb1047187b7e1f900c384e9f7cf2e3b07c0fc9f612bbbba2117f38a395e644404f57d6308393746b0b05c1f7d9af90e8970a71b0046eaa6ee2e79447b0ae56c64a61a67612ce9a9f24308dbd469122926f87eb3b0c6e1ba046e24de3220788a4c49f30f80db9fc1aea7a3af969d45c04c013aec3473cb2a28afdeb9a11fc4f24192fbbf2f6232ae96d48da710042dc667b80e00db0084f8a3d6851ef87e31f50549be6025b77c81c559bd2fb1c8937972df66cfdc26557fc6cc59848d4f7c3f4bc5ff2973032540bca57a5bbb1474ca503549ed1ca28a5cc102d3ae0f23d5fc10287f7a8584390ef88b854fe049b2cd2f467283ddef88eb07700924656fba41dc62db25adcaca4fb7be3421bce46bc204ecb16c984c94f446db868cb43b08e95e4785554cfc560288e67158b54d0099599c976aa616f3861258eea492047f4edaeeffca570e6cb2d7dfb4b3e32ba842a04bc0259148466a1529733216a4f09129cf891909bec85762d2384bd1ad831e399231be6b938cfe5b5a9f767212fa6366d9de26fc79e284b99e64416227db5e724f5651f16e363468ac9a8bda0315657923479f14a345fb87cd2a520de7ed0f08c2190fd7f87d7249f1f0227dab55bf393807b62db56c3ca4af2a8a525036ff2afbb485dcf7683dfae4b2dd606c9b049ecafac150bd25c8b1d84cb4cb9148b967d0435786fd4e6e6f62820e0aad379ea8de4307a726eaaf69f4b67d6b388300463f96277946ee0330e61fb114ef805e70ce04251292ce5764e26487365fd0415b97df18a6264df849173f0293a4973e815d3d676332d01c08aa5867ff20741f02d137ba15decaed586f8d093cf2248df97a9a871510c6202ca69742ba91324688e60586abc9ba05e97dc2f65d659062e7a503275a9ccbb657b984034b80a0daf96625ebf306a80ec3f1ca7ee1e506dddac77597378aae12400e89ad954751bb2fc40b36d5cf90f160eac059f896781a4cae44334448b6bc180d50515b815b177bd31ed7a6b15d83ea767f6597b24b4c24d5e9d1cc8fcb69c1622460152c01b52ed1451a1e32607f7b0ef816618dec9395d3c5607869d68917e270e6da8c6de621894862e5d81ed20a71fb18f40c037c20851360953f9ba47995590a2a949b00ae139dc51ec03e78f6cc09374a935ae6fec37c0a61048fae488e0e002510a622dec4bac6590560043ef64442e81b9cdd3083dfe437e4db8bf521153817d0821a3962a330d738af7752e246ed9f6012a09119ee3d781b3900983d3ec820470410f58a4c9f5e89665e624ca0aa0b3a5f899bcffd56bbcc59b59dd53fbe04043532514b53af573f50f15a69c524530c871fa251a2cbd9b941d5150f59410bd633c7429fd11c411bd2c493fa87d904c3f36173ca854fb98fb7f0c179b93893911b40f6f950518f901091bf1130db7559db9ecf3edd517027a68f490dbe68a7f7a074a0cca3c12b63ebfd78a72848ab6029a25b079120b21c781cb229696e7db0d84639d277baa9310318f8fbe4d3b2e9bb6156f7e938980d191f8d72269426d208fa9394a232318eec44098b743de030bc8058840514b347c0a562cc259e0e33f4a24801dea399e49249891a05aef9300fefae5ec8a613deb2c390026df861be0ff854e12280178a24b4a70f7a6c8bcf38209a3cc050c4348fa47a555818c81e29c0c44004e6e89955302f7c37f77f59412c2947d0915ea2b065dea8b2fa63181f98d2a65ffdbc1bf5702580b000475ea72908d0818208fb707b5d30986394ac55ca850b00e823761c5f48759ac2a9e584829f964f0fb1d133eb3573978120ee180d0fe14c53062e7c8ec5e952c06a8228f41da6041b1c76b571563d3eee589756ab2ba22485040458671f9274aeeddf5cfe8c0596c0c7b835d961419c7a1616d79950dd64d6c370a0e0fd952366a2feddc168f467517dea65f78f909c7b36f3840d73366f1267304e5602679ca007ebee2f93fa91270464681cce31a009b39a00e3e5102b5bc6539dc287d2e7c8d75c52f092fd2df53ea672bf1960d53bf73571cb45be308fa7ec01be56d22b971c90e4a851a82b139cc3bc04f4f7e6ac40c32132d78f5774de37a88f542093593ca734da6e1df2962c80421a9abc0c8671c66e0685592f938016c7aae1619f5e34440a72536d327d6041d07156036c956dea52819b1b019e9cec9104dcd3114faa798ae494bb1373da2bc9ab65ffe0e8056bf1f39df4e115210f727ae92fd0c5281d21324f9c91dc0e5b4fe01e24906a1e533ceccddad3cbeb9666fb9e892026aab550b156ad3903b6f481f206d6f5b677334faf89cf3af4bcaa57aa2919893853033bd257dddc0d3590f5a63fc998013410936a194abccccc514ae3e1cc0e49d040213f35bb368d2e7732b0cd3280cfec592960b7d33c9ddf868d1b6b2a22204bad009c8e198c3cf8063867a61d1299fc1c5d6a857ba9928332bf1feb5a349d0c5a4b7e15d9fb24c1b902e5e32ab21ffff3e71a71a426c88a647fdba22bf7950b1e34085f2237d4fa10a44ba4ec025bdb381009024dde6f89f55fbe8d9d934b93687896845ec42d48ec8aee0e0ae2ddf26c35d2b56134af746cede1180293e570c99afd7db2d4faec5f44c2092620f00724f6bce34d9114ff00006124d6fc41d0d5277d2c6b24be0d447e100f7459a76c09e56d03f88aba57d5c84f5082c059d0fac4e4728f16df28362a57f466f0445c34d2f9383e8ea61f23bc1cccbd8aad35dd074ca7e788dc1fb9eac3c8f98d900a4a58b5d9228257382d62529b12fa7175ad2d9b57aec0392d849e0579107fb52276dabc79c84a989889477b1b8f92438211d5781605a5a7bbbddfd2ec19ab6054a8f2975114661e0619b0b6a74c9540c5d84ca66e5890431b778eacfdf4b204594cda0f76699a5957092dcf86413297929729b60e2fa5971af32da7f0f7ff867cafbd8c495352185b0d56b13c7f8052e53fa484c540ec5ef6d02af9f39792b621ede648514030b0636b0bf6936c22abe5b895e6c0c1a8666735606404176c6411241aba2643a1a4ae344bda33700fefca3674f40490c8aa856ac4876decc7d6866be0a10df353f8278f76269860eb2165160d5cc4f51d592b49573a766e267db98c98259c38e99be4d4e069bad0bdab0c5ed49ca446ed0880f60817c444c81b67cb59ebd4fd3cf3767c617013fdd66f16474dbc27a7bcef4640ac240ba94fc4d62b44a5b2338054d11aeff16de94167918f7ffb1bd2320a1be4deb9da54249c505182ab63784024e71b2b4533d9429d481449a64348bd4c40ab9d43b1754db524fd213ac025d5c60e86cac0414fa3518fb911b2e0e6ea0fc141dd48a40a99890b0c11f121d9b0510aeafdf6c442576ea9aa4fe75a74090ffda1409693117197eb603ba3268720d861e99de25445a24558639b3ad3f30f72bfa52af3a85d94cebc78283836e37d41e6f8b3151601e19bef54c19be0c790409f0aa4580349bdb7c4697c7c23fa02ba7c6e2fba2851644e3d14f4ce276e443f310833e053844e3f056d1f0cccbad37c326bd3948ff9a72c1c0f8860c094a209640c12b51dbc8e0801543828b0ae3ab100cc7f0688156c62812a6c6536e0a78208474d22cc4ed146c08bda09c18b5e24266580d63dd12c1fee0f92f80f0781d460586fc98e8662d42e1d52919a8595d120578e73b95f2558ea9758e2ab4d4066fa7175af6b01a08d444c98123de5e4202ec6540082d87c6d5555bb6d55c21120d48c0f58e5d22d32dcf3dc7a13c8ee729c0c97c955eab71ba525702a5b8faf0bfa5689bf4b7113756aa8bdafe09d012491b8efa1c5fcb53e69ba0b12fca935e679d306231c2444ec0006c7a264ed9349ced4a3117bec5315a8c1d1ba0b42b1216d33dd22da1ad4ddbc8f4ed20cfe4d5f30233dcb365267e9163de164851b93d9b6225e55339559687df96c154f215e4561ed6b01bfa19d40b8843e5299841a612868483939dad8fa41a794a4afd088a1f9be5de43f22fed829bd03a6d30e72a63a2384c4f5373e2b66ec08b26b659b962065805646ea8f8a62813c096cf2c0c70e1bd52a14cfa9613a58edf3b1e7267636230a378ec11bab88ca1cea42313175fda7b3e0c108bdc16776ad89659a8bce61edb2612cb75b44be96cae6996d1902438e7c12190be8797fa49af2b37bea082b7105079f748550961008ee978bb42ba386c31aa85e2bf7ae0ff6bf8e6e37ac4063dad3582656d1abcb34e44485910d99f88d7645bd9fed3468285806245a710aa0408d60211ea7f80cde31dbdc249aff727a0ef08dff5ffcb4194320f11fdb6e1ec7a40eee55fa8d8e83151311ddc708bfda4c5a76fc3006a3705d6da54ac22ad8a92ef82cbd082d4b264404ffef33acd587c8b1633a97c49345590ea856bb77c6b087bb641c77345b6dadea3dc55f07aeea10de192c4a379b294d616ac983586592064a13747d88784462bc23a7637ad56c244e82d9548aa31e770bffc82fcac357ea9dde4f094acdf6aa6bb58e548b2079a289d7845721208fbd9681955d77400d9eccc127bdef9d3cdfc9dbcc6e73c527445ac90e6d0df424b5c8adcb08e1caff7159e664e538232aea7efafa66a86a62454d194fc43ebecfaa783631ae03402c856bb054b755611f6d45c288927c355b17ec9674d60e7b32cd15e3b7fc26279b22ce9ba10d4eb6a06454c6d0bf46efe84d4328ccb2373c9c5b05996bf9085300ba92c07c6a8c05e2106c32ee7573bb0f1b52fa6f906aec32f59ef19c6cb4965366897e8bd2837ab834d5161143ea14f5dd225719ac346f26439242433c3b9c8d0ea34195cbaa9066ffb9b40a361a89ad2225b7251d928e06e7b90affc27a4fb3191f8f741decc4476dd3abc4a930d008fb2858a21a5c8f44f4289a0888d039e0037934e20f9b9514fa33324f250a3ab194beb2108113394eefd433c32d2c167c5911e07575f179a3bec163f5c72bffc1b38c388e43aa393379efd1bdafc104762f30588f334f4a14f32e80e8bb9f82871c75fc4b1d2f5e40594b87e22fed7296188409ce398f4091779d7b6beed3d4fc6ab615d856affbdf07055a8d924f72763b07439cb64bcdb8b49c6be3645adf840250ce8c6d200b21886f67bba292ee2b04341564452d810967bc3d702da153c15777092d71864fbc058f71038752288fe397c1d6d56af8a71f7a35398c3c1ec194cd3b5b36b88b0576eefb563cbe7ed7d73e2fd6ef455b6c17ff2047ce1c3faa3998ff1152d8c0bfe810eb9118fefbc1d751bc72530ef0122cad04f268546611f65d6f481053aa455647f2aea032efa3e9367c04a9ae1905843628ce30c8b8f30bb3121b9efd5cf3ee928c3d7b216846ede222bcdb718b4d16ebed30c180e69fa6e292c26a608573683cc006508550d6f29136b088ee18eed0603ce44d7156efc1058e83b70611bd8b4f65069607dc4191c8f64a750433552ce294348c5c81c0e137e8d522449a79a9dd48495b47493f8d3a928d52a358cb1522672e4e98b86f19e110fdb9e544cc6313a65bb9c004077a72923da965e01fd5c870494016525810fc3e2c32658570fa8cb8c03318b467dd5ac2b4a3b5599edc913f66962ff579f69c6636a5b6a9e70071730b5120ae2953e64bfa75bf88a567b3e2fa19862f57becce64192bcc9c62e61ad8332e856268e35d5a5169ed6953969b853280c6e2e81367828fdc4554bae24d8c5831e1e814bfe2d16577257ea9371c20ccaf726d6ca0493fb1986281412c3e8e38d71cca6b5318452803fa5563d1341fcb64a8c6b3f204970dd216c7252c3a53aaaff5280ca96888b56588d8a24d2a02e0f4252f779ebc396c6c236d964c8973ed65eb312f7314a97879d326768fe77e54a8e54b97e7f68cf5a81c7f629f851abe353b3dd4667627245183b7405af3f5b71e58697e3f843b2d2df3a895d5bf25a8c98007266a91f4a8ef38b1eb86de730c83b1000beddce103d2523c521a48b581bff16f059295c5b0a405cb32cfecb27879ccc0e1285ca7c1dd44140fffb035f16a118b9ccb46154fa0ecbd1fcab0031ad201f7c136c8350fb408a7cbad9333b9d411115add370a8004bd14e1d356bdc01b881733b2d6eb85409bf7b9fd7f7f235c4ad3bb664536f990bd9011b7ecc5ed6ce09a4e71f8ebf177ab63818e7b0c7f143a07bdc1035dc2595586ec883dff3ad016d33b9cbb318acae20d78b7b57cfe35f32cceef2acc8beb7da0421ad39c4413bb8f2a20898b23133d93ccd50118692cd6a7abaefb79a1d1b2813608fc18cc0931e880f84c03d198ebda048a0a5c4541251688d1cfa679d9bcddaaa26dca93ac27f88738c03f0c47e8c370803e2447483f7c1a344b122b831506ff13c31397871b92383f9721933befa29f396eb0569b91f0eae391f418be85147042ab0b2c4fe28ffbad8559bf336b57f9a64f53a0f925990ce5c8c5c650c37c0d1783c74bad9501b42e3c07075396aa7c2372618cf5de804cfd49b0a73ffcb7f13773d86f6be94dab6f629785f6a32d76f4874390dce5ef45c4e9fa95bda46cd8ae57ec6bb3813139ad20c2a75fa3bd4d2f9b82f321d3e18aca68c29ed88178d69d04645730a633b6bb2a91bbcef7200c62fcbae6eca4fb078201f42da70c646d5e6e54ff5c6c2136766c89355f18e0746fa0787b4a25d8fb70ff7dca8284914f8b3a534caaea29331b7f0c5df8df7fc5e37e182b731711ce82d5b06ec2c204aca85bf8f50f1bb7435ebd395ba8ede9c75ffd4d8234faae9252d275a9ccdbd72d6f0e9aa48c26d32a06a63845d584809903c2adc8d502c3b349b422a8fa54be93ef753c898dcadcc17dc71c348ca91ab960d30a6da24fd63e9a6b2593a0e3f1652f257e3230837210b531c1838c28cf7e57399cb75964a3cdeca54088a08dbed967d96974ae29e52158c4718dac10f69a75d711090f2052e88bbb7ad802eedd6605d86620debc654fdbad8808ac6907616899deffd2e0801f90b7715b95ba4ddee1ce079b974e719343a5ce32215229522b5b60f997e194fc7a2911e7fb4a356716ba8037d13518ab7bc7817770b1347a4c4db226f849c349a5abc1bd0e65ea106a4d23bca1964c3a1d516b763c01c2e036635cbbbd32de5e143d76ad048ef08ecbf5fbd84541bc0ed2295c3e31666efa9f757e6685c3d964606398df39b45c8fae01095c5a0a07f61a0a9bf64e3f71f4975da2279901156e126f1178abc4d9cc36e4b3a54b56ade424ad40bcc3e1871c9f631c5ba57768dac912b4103af5db6f9547b9a1c6840c330e4e9e25120bff8c05fefb1f2843700846cd067cafa52a2eb496d11f1d42549a911045690379ca27778778ca83d04d24b9cc5ee1554d529c283013d1fbfc419cc49945c4d7ebd194310d2008fc6fddd1a61c9b56d9189903c05b9f976eed20fc32adadb484b7fb16b8aeef298ba8b9f73d965a499337caa7064bf074e66fbf2e89aecd9d2904ee7ce3383194582fb222bf5555685e7e3bd17c85eeae7ad9289de6a04d55e33cdbc2597297fd7f67b613a5ff75b4e845cdc95c95898e0cb511f7fd853461f8b5b765e037e1601355caa5d105cc234dafa65ea041580e28b2567a681f211be917da5fe09186c83d7dd27dd98463028de925060ccdedcc2f0b0096d9de59daa015232accc4fe7943aa28727519180945ed061fb63f3ac7877e3017029126f6703c9b93974b0ac34a65a7606afc18e5450fdf848b956080f60f4a74c75a1d98185e3ef95ac176f14dd6fcb76cf579d7ca708b2f12d09f3ecff591770590f3c0f3bbb22e34fba89d6c97e9207c4e0302ddecc59f7254d7b8b774afdf42f41e425d130982c0025c1146ade39e3e7bc1bf26a04e467ef53392193eb69ab96d44f850c772ae9164fbcac93765364746774e4e7fab52595b949f71b39018259e58e7856d62b74e0177cca8a0284e185904b984075e7193e1500c5585d2254889d583a837ac6f8b5caf7babef89347a6158457fce44ebad0b0c0acb5d0a9d70abf8a43ed9cc69e8950616727160da1ba0f8b0c6262be9e59501a133f3b5059aef8718a36685a972794158ae0026262f80c83efb17b573151daea867994cec38dc38b24ef9e5a14bef16d554e2523920ee857a1145a459bc019166efa76b5bd4ec327d1b8649a755e62c97bdc9cdf6827ae34fa39deb5c730f5fcca92869c78c9bbc880607d76f5370ba51effb1cc2abc3c70c38ba01433891112bf2622e502cb9e5a4871b6aee394953f69597210e3021d58c7b837344f4bc738b1e114f2996669e9815a82522d1735e9d69e419ff8475c80f06caef8fdbb6a75d627c2e10c6b9fc4fef7e927c4f0becccf69fac490a7cb035364f8499b5347ed141b0ee03b0ac10a12cfc568e0a31fb6c83d72c31b1a0a361da9ed4af623e45d5e17d22739c1489092ac08e298f51529c809775eb11469e244a0d75a396049efeeb5c400b8b76d7e849342e28128f6783c535c617c92f65323f85b06deddf4c41bbc62b3984120ee4a11c4858a8e3cf0594b8665ce27232631b23b16e956c51f810ed30daf6dce2015baf80a3bb863e6f51482ec02691e26cbb28f9ddb8d564523d46eb4c7dc1225e9dec48a1de7e1db6d96a5658a1d2f1407399fb7ad19f0ed080b0155e4d5dc89c6314f5941d35ffe140e420570ec829df8d06a1bbcd355c25b692c135c978ed23bbaa7d0448a682c5b61d680c05d87a04aaf90bb2348cabf2fcadb6fd7379f50e0de15926e7c8ecc2181995b519768870bb44d8ba8ad7c43dd57478e92cd35e38591ed91943f1c944d867a89902ece344c60423d1f03021269b1988ce5cf3eeb03f9b359999fb4f2d5ef60cfc6b5ba9a716ec12d2747cd6386b39c70b900d1ca9f3fb750e9dbb54cd5388cbfa085faf7abc02381b20849949c89c367d207d6e59bcc16e739a96a9b30d85e488f6f87c7f412d67808f4f20e3aa5708acca6ec6ca76db3498a4262f93244097204cd7163dfa30309b75e37f54e174b9295b6806d574152d73af463c2cab737f08fd1efc94c2cd19349ad905309ce5905832861384eab3c3f3c7942dc4f7185024886228e8689aed41efc2061d0c3444a94f1ddb425c80b8421bd328e4610be3015597b4fef5192e6621f30dcdb42def37c5b636e7bca89fdfc2fd761e823c49dc0d85bd1b624246171bea83efa0ce37fc03c7eaa5960403c804550df02df5004e88e34a73e0a65e88cde61e5b794e85e911dd2a836cafeb23464f7d5b155dbbeed9ff910abbd6288fb2e6b3ce41710a4555c1ad416d7809d38b8d9e15ad6c49dd38b1629519e6e501bf80c5b8f3994e00c1cb659aa7e853307a56e94b006fb42544cd73a25e8fe7fd4f0c0a191e2eb3675e4d2f3cbbdb88cd072e26ecce2e882feebcf0a48e4524d43b7ef36e381435ccbf2aa95a2e14d55ea8c86aa40963a9dbaf80b15441114b9703d24a153c66e9d3ff96a03fc2e69123a8b48f8c9f51a97cc63a2616a76e8a0b7743faf462ce8bc22b711685dad5db33eeb21b6a36627d359a4ec58537c6bcdea90cc9b3566e1449414029752b42471b2916f374cc80a90b3b9d79adcc8bbd5dcaec9c21ac1f841718bd59d1168f7ec75605483bb2f07b3452e52a1ac67bb8f29deb52f5205499ab3a0c4cf41ca51ffb7ed047a552b9c31cc82f0234873c93650021305098b47da3311f6af4aa01ba27193f5f604ca5690eaa54b51611115114901400214d21805522011845ec08ee9567d9141bb5520c1d413b33b5b45684ee0db3c84c3e9b671e00efe01db8033cf0e9e8e80ff1459a6aea30a916ae9a99848134b110f611e4cdaa4591bdb2bbb79452ca2f0427047d03493e8ccc205d76a8c02102098d3668843e5e84595c08f98c3bdcebb416b5238211135ef932b2d022e698c204359a58120314a45ee26c60d4c31d5084f4120707c7de7ba1f0b81707c7e220e0849698982728563cd960e6c30969b0a5807bc02ea615aa0894129e26153db08b875fa73528a95ef237c7125cecc14319f8f63324e33a9acf66d4027be4cd788397fc33d50acb9fc6c06a8f875a9102ffeeb4d674422f1f277700d6a79c576ba5d509cb6eaab5dacb5373128331deb8a7d9d3cfd31048db3446dff781e093d4931396aeaf27aaa7312c4a592c16e7bb022819d48a4fcb538ff3d0d5a1732252e1f403d6033b198d4c483c4966e3928d46ce5e908c24dde021e7793b31831404a968f4e1b97858640987bbfe6d6e121012f127621126a215ef3aad35258d4c376fd354a4cf745a6bdad1cbc7d999d122a6f60445cd29a8cf3aad39c97ae939f1745a6baaaa357569929231259d889c7e80334e28851eba88a78405d554a18063131c9c11080e8ecb35bee0726d277acd4f19ab2d324aa860c248a4bf67ed07bc5a6ba741f8b53a406ac722dc0607406d498ea1da122136701216a54f875b06984308f4efef6045d07e431560ab676363f381df017cd9803920d9e1ba5c598f349edf7b8eccf1c8b0db38873db0966f6085e1e5cd929f5eda74ebaa29d5e8f61300a6db2a04ac3838d7ca75ad5c230faded98d08e097170ec36f2b0636cec189b166638839c10308f9cd4694795c1ac542a8d13a759c279c249a7983e674501cae97362e749140ea11c3f54cc80a4cf66cc154cfa548f6854154345c564d94085146498109d3da795b365429d0b306d3039bbd12c71cebe9c41675396939b4348ce21e60713670ba08aa47c7662e092e38b912e23841300a694a4d3c759e4ec31d567fc9cd9a45d68186e0006a031bf9c484ef2022e0f26cf59e4b480b974be7063562e53670204c00053068a4584933679392b9019154755814da193001cc771dc0c05d89846849c75cc962a3339a032c3f1a509d28d3129a852f419250244a39cb4c9d6755323b4e8bace4befba1f2014c8150d96d0a31a8f850996a49c5046888513a32e525d8694b383998291f3016698b376527166658147f580226d8912c3a9c1acbd902a4ab25435029d0a52591c396138659c2c33c7e9800d2698f33c29608230abd149b5502a42ce0958ab6a8596a9f2c25980106605cc8a2589480d5a44c9c45054176a1930a71475a2e454724ac0849dd5059d21098dc14b07a7f9c38462c204182897dae3c95515adc54987d0254af54985fae0743ab7ae47043bba04b950a35275830b12932e03c6190173a7325d39afce1567032e557a6af2aa31ea0d2727064c2b4aab51b40a08249c5dce8d63ea386b0038c846c07305a8a80c0c2118784be7923ce832b47050a78ca14c48638fd187a55c08bb00e0e50ed8bfcf6d0de874fc1d204149be7a0e7e6bb7bd81bd81fbf85e7b5d8428842880e09e5fd206df8facf42e98b76ddbb06d03e58d95ebe76de62eef58cd75cbb66ddbe06fdcb66db9566b37cc6ea05e5e0b5abb613584fd6d9bbd2451b9b73993e59605599323c1aeb3cf711c57c1e738eeb23668933b4672d86fff5ad98c95692bdbb6cd6e59ddb26efffb6c0da762ab1f7e6961ddda2a92e82c510d499623366d2dad8399f6bc6ddc6bdb9edb36787fc3bebfaf6dab3ff8b767d5d905818988669783898db45e76b976ccc22c77f54de66cf939ef36dc2dace7a7b6923b563f6cbf39fc861b447489b285f5aeeeefeae7ec5c7f830dea1893e87afe19b2b4b0cebd87b7578e70e1ceed580d5177acebba2deb5fce39ef186ed50d32a8aa503961fb83597c4d47909e78897aa163eb411a42ca41c54469adb4d6fa2a6bad24255d362c3b8461a7352a211dacbb024a6fadd6d29bfa95ded07b2bbdf4de4b4ed15ec94a3dbb5dfcb5d65aebd74a5e8aef11e72ebc618c319550c718638c311691f8664308d58c25aa0f442a2a169d43a7b529aa292b43b5e248e0d21d833fa01c61d35d21ce03a690faedb43685a4875735fe410f11861ce34c288c9a21b2f081089b928550a6548461c88c2e7ce18245479418c5a44992f0479128f5433cb242c88209238c1a285e00e140f4f1b2425ce1a5c4a80298508716914714f10427a15552102363d00b4e889aa0101eb1b104c6b8c20c8974f1098dba9c187fac441a973069ca98c34914414938c391a620a2181742155208bb048d3364885b3a3081cb06e1142e28645cb9f082a44a15a81b328827625861c88882470c3842204164128228e5277cb28505962d1a5c8d09a350892a5cd1012514b364dc71e4011111878f51041d218f12683081f0424b0bd1052d3c1a94194798c1842c2115a9f184264a4821ad884883d6c3425874c2d84204b109c8848b31852c3de2922c26ca548db02d220c296191a67049492462248e00247ca18590c9905875238518a19653ec000b6cc48165672a4cb84297108a8a68f424ce96c8c0002424a4da3192a0c2d8c108e1d1cc8a07a20c5762e20b57528c208809c74c2959197a1256611a374022f6d4c2323d72e41863082599f1e5837088159958c38a0ad10332a1972fa30f965146d40c265b92cc50144af921a2b0420b13469e1aa2d30b240ec6b3ca8a10489512323063095ac644116b3c3b1183784e1a43300aad0409a576b8c031d2101a57a0e1e44558e5421c42c5679451d9099dae421c53e2d315714a5408a6490843d28ed15010d1c60ef1078e318550c885468c17230b2e3898e2d3c2949d305725a6c4a22b2444854f4d58248d2d18894182883c3b422138c29a50161a545ea470a181141ff194b213bbfa32259a574225512351139128090423244144a81d610f1ce309a191031a629217221417638d283e230a5176ae5c4599f2724545545293a32471ca484b90d0c88e1f1ce21321211a2ebcd8e1226c41cae703a91d32572296a912ae8cb128234d442501e0d3be675864a9d3597abfae4fc2b2480c6e67e5641080bf9f02ab9f84ed5957ad599b2c7f6686d4e44ce9fd66ed19b2c47d3f59d697ee21c98ec7906ccb366c036dd83639e79c7bfcb9857197b620ffad4110244b0bb3433f346fbff52ac36e39597aade75ff3a2d603b7ad17f750862e12b4b01ddbb1218bf3912b1e57255ca10843d51bd0692d0ccddabff7d6925bcdddafdc7d4ae6609041a71783dba9f720d6c940f3d4a7fa27677bda6ba6b46a9e1ac23e264b2da325c87da961dbd69ef79a2522c79265cdd8626badb533df69fd5b6f4e962c72849ed9a0fd48125dcf64a9d33d32a77ef79c8bebeb8d996998e6d1300d9b551e5dda99ccfb4f96bbce23358b459658571ead2b8fd6ba47f7e81edd73673396ab0a331486c7aaaa1ba0d39ad59115ad3ba0d39a156ca877af127365254bdbb5e7911b86b92d03cb2dabc0d3de8699bf9de791e5fefbf5e73369bbefeb68becb5dce6419b6deeba8f5e091a5d64ff3a2d683fe486c6fa8c7410589cece9024bade7d99bf4d8e70f50e7c2da2d4c901c9b2e2ff3e13f9efefd876b1ceb6dd9096b5b4cfcae80d57ab8501d58db3a5ed1dfe9ebc17df8b3f32e7bb6a2fc7d5a32f4f7f16f69516c8767f828e69ec0b5b6dcd8f86ccd1e1735804414896b6772f92a5252df9e9eff09723481bc32f0e9f0517040e18620bab21f0e3ceed2da2f598d4ad978dd50f35acc1f6ede770adff2c206bc8125bb26c655affb8febd68d7d66e1dbfb66e1f5b6b5f16466363186fdf578c3dcec9ed9ac1187fdfe1e73ed2c6b8ed712e080b8a708a3efde0f7393a7f6fc1d53656437c36566bb5b2ee711cc7d918d4d5016fab89e6e3c94c17aae307c51c3dd405b97eb9e4d4c5b35296ca4ad9495eaaa921cbccb46ddbb641fb1b9795b25456ca4e32535eca4c15c86210475cd7715dbd40d7236aed0579a9d57d56ea3253cd4c5d66eaf573a6a1c94bf543bdb4f01e5d0ff8676a66e2f635b523cbbab7ee887bf17e485e5a57d3226934645eaab57e3c1db4d6dabc9495bacfddacd45e2dfa4a994adc99aba857fcfcc5c104a752bd243b52efa56ee3f6c7e369ed7db5f2f4aecb4bdd2eebcc7567f5ce3a067181a55254ead3faebc2bebfac54b8bdd97f8147a5ee515eca4b99292f5da80e363c4febd7de831ef7347b9ee75d3ed5534354ddf37d9f86f5dc755da77b86342cc9d175d7bf5589a912d2ab4c3058aa6a7470cb3ac8c2355c3c9cf5f4bdfbdbebe32f51fc2e2e976b36d31043430c4bc40454a7444c1c11314123079113a6d65aeb5d817735fe46e8d7f7919fceafeff559ff7e6d9d6e10705d777bbf76278898e0d2c1ab35cc78e9eae0adf6abc596e7a8ee0ab60634c0e038dd79f4bf04f823c1eec5518e7bce711a34e095d4bfe9ea3d95a095d8e9db082a95977c50ab753aa55debb7f8a5a3af4ba773de095edfe646670d26b257c62c03a31ee000eddbf6dd6f33d7da19efbf172553d431198e69f79c88cd563b73ff73d97bbb19486d04eecdf9efbd37e79b378e7b8fecb287c12d53a64c198d5180d452dd533f607b53413cd6d95dd7bd08dabbee71f73abddb1ab8bf3d05f7e27b9974cf77bffc55107c89a016607a33f7997b10bc01c97244bfd623f37b2ffcd2f1aaad6de506769f3fc77bfc99e4b08273d880f7f83d12ebf4ee55d6bce5a5fac1ea6cb546016526ec6a98866ddbbdbaf274bf791d086a1edd13d46f09ddd6b037f7ebcf1886dbaa7b342c8c0a90da94b7fe54bb65f6a672d6da0db49f12189375fbba590f7b083bde306b73f48d88bcfb3e9801b9edd878c5eddfd98501f503446acf0b4d48d1912325a6110cbf341aa7711aff42a309293a72a4c4b4cdcb7499ae930ddb312bce4357079bbf19bde42d5b711eba3ad8fccd175e3b0f5d1d6cfe66c7b649e3473c7c9a104edbb10de3b49f5e5a1abf4fa3595abfbf6320f891a585d15c1f995d7cc7a0868064c8f9947eb08295d47686eaed91aa128195e852ee82a496fd2dba45f74807d6e72aee99a4e0725b90ad43751bb211f0bee6ad0e8d53f5f356c72aee3d1da978eda5ab77789b7ba37b597ba965f686fb1a98a05e562230b37ebfc38d987e69faa5353028fafdcac382a81920caa4e6a91f5c44b0525ba20ba2065d0ff7ea7c6426832e884b06dd231dc2ac6603d5d46c353535bf61351ba8a666c3b6595353b3655f6361ef6ad1b46af8b7de6b61d78eedd88e615e3d1962a07a6dd169edea08de6eeb35e79af3aef5ca29b9d79cd65dcd5f3bae5ff9e9fe80b77da9b72659cf794b7f8bb75abca5bfc55bfa5bfc29b89cd3aeb97e95b86bfe2c9282ab75abf514581afd3333cf5bff258b6395acaec5c9b2761a92c5694aafd39025ef5afc69c812773e42b3c8efbb9f79799deea91fb63033be3ddf31cef96f18df409c6fd83639e75bf6dcc2dec5da4fed66f1fdacf758fb67481bd61ddbb11ddb3156ed9e8c01ea07e8b42686cbc7dd7bafde9bf59a85f77bbf81838a0fd4fee99efaa1e29d9fc5daacd73cf959bbd43f39019104a8942b9bd521b3d100900063170000280c0a0684b220c6520c89e6f00314800b66a4385e4022a24b42611805511002310cc3300883000803000080300cc620227a1c0056e5f46824e78329a196961a53076e15ae8324e60cd7e66ae2836fee094f4c81727c6f35ea17cc69f2c5cbcfd476b8e2ae7d32087b5680b2b6bdd741be07c2fe26ce0b00ce9fd3c76d3ac0fffc959d25961675f22eef73b502c5339992c40e85a7927c5a3f699bb5e64a87ba4445e7bad596691fa6e0d1214826127283b1853231a05fc0d9b291dd15cbefa152b0b5c2bc01343164e4acbd8b24356bb967d68db08d2803b720c73195589b3ee2b9e777497eb83c1bfc0e20e20962f4df09c136350e5f7f3b4c64e73d6952e955450686711e2d88cd5b0e31adf2f0cdeb89018aed7740d4510ca1ae1cbdb2f449a206ddd3558c76e31a4db88d78f9dc9f4f97746466eb36742fc888a097133e84e257d4453656164129bf1a1daca38eefeafd1241fab0e466940a52de39ba79669bda2a0bce92006fe327302b742edf2f7331afac04cb011b4c9571204840a99827cd4a13282a6e093d71d9167ff7c5b4dca0ea2abbde37d61333620318fa2e7576b03e0e92d52db65c2fb56c63cc6f20d1788f782c38105fae86ac8563c41edde1024f4af90a8390834210f8cc45358fb3bcdacffe56bb0302d372a9aa72ce15e17b0c5efdc7df70f4032c89b4f449b34056f906bbf8d37da7a8c2839454267bbcf75a0af4f42f53a62cb27acd6db46df30872b2f1fcd242d6df67f186c03200e70b158734d3bdc20d5de8b541abf58d96904e3267d59f874bdd78fecfe5c02373a922e08ce38836dc61886e61d6369470015c5283d0c1ae7042425d79394f602358d2e3aaaa1104f1f20c542ebdcf9a4341b9657a4059018c7df60c6d0a94fb2aff46cb392da8f01c3c0314ed7b314f9fcd7cb2b04a926bb15d4159c5fd2f4d3d9ceb8e17adf7d96563f7da4839157a74a64115e39d61f732680a8ccfc537c2c0cc627b83caedf6b0760bd14fede67bc5ace90c300a4248837493539358e52b89234a6ac78f8312ccee176a66322d40daedf7202151c9e43fb2835ea4827418071ab9de06356dcb2454cc29a6ba33a970ebb2038d7e99cd19b584c42ed45a1788139ff2c3bc15655c13b674942e81efcf960ad28d110ae7a6538cfd33671fdca29bb710366e6a6254d174d6070d08aa6524280e2565bba873ccfae08a34337d30fcbc9f2b73274c815fb86dac55b0321fcc87c0a44ea44003a42a951af6c00aa14b036b55aa263960486b69935a65ecb1b7586c2aa6581163d6985a500a3f442e61bd3ed12237bf8a0c32b94465f473b2c56bd22f7ad6d9f427ba84957f7bfcc32f11060d0ada7334020efcb638a7840962d8251c0529fd37439b1d37aa2b0aaf193a7dc1d44524cec10f9caf5206a69bdd2858e75600f8d23ac7bc89bb80939d974f396d96040f8eb3475c46f1f171a0518696bf0fedea227c51f043891c171b85092a20da144e2a06c7bb084efa983c3b416f8b7aefff85dbaee12f24514826fb7e19dd949b706d8de641320a32646c24d3f802fcb736c4f5a61f1988c610f2f029b384bf9e52389faa1cb2b7cebb2eb11d47d4c8fa35aba831bc8b1551f7af9f9ce1a4210f64fb19df96f86f4f7d6c4273905f78372424f538194baac31e2f4e2f4fec78d8837d9b8b5d048c5a7fc329267ed21d8eadd6942c5a719699a49f4c90591f87b2aefe4e22e4f574b9cf1ebfb2dfa37d2f30d3066525ea0450599edebedd2267817fd94f7cd4471cfb75ecb38f0f6e19935ad713453d4a878312782a904e0b5a892608f0000d47b7770d0d86b914c192053ad0cb9acbf0008d224dcc440929917c54b4843ca7332da285933cdd322754dc8200fbbedc6d822c6bb355bf87e8c8eafa217e8506c79bf49696308c9e0c89073992d064f4270e0cba1ab123e8d5aef31575ee6d205651d16f07d02ea4bf0dc8dd8f7f963148bc0b1c1884c0774fc42e2cb2688f0893809100ab092e5dc79435fd3722d469a1d728ff3103bb6c9a28135eba9861900d39030d861cd39016f1cc050fbec7527f3ac225709fb183ef9af7ab7b9dbbb0de33411db12d0e81f131626b955ba6cac40b62bbf420bee99a9bebc4c6e496929d525ca310e25d941c68b46d28f5cc31ec48e4c1ea9a6f8e7fba41e267283050921d806b0d2d9c26df0c8e77eed46a78829fe837d726d737d3bbc615b25f8b9983dc018fb27735637fa5b88bfb1ab3d80dc1d6260904727006dafbc5393574fa8dece46d14a0d82fee8026623779795331c520f565a0de1606385835d8a15b312e981170b90e49796b9a442f6e8a8287b358983559c263090159741e6afa74490da3c3c86c62905738a28ed5f2d834983f8c8b454b9237fd48ca8f6bf36ca28cf65864fb7d936b69649195b97c4ba208926ea580875e48007d9077ed22904132aed25b6b444e7fc1d60fb707a923cc12c18b72c4a8054dbe1c0e710b396ad145df23db18052b3c7e61b53443d6502b3ec9e9a58bb56ff0ce0d5d55d225c89e47367841953141defbcf6e5ca16c7a67909654159779760e531b2c8dd54f0a34ed55c4623ce420eb30c8c01af7b7b5ccd86094114350ab82ca7a841e0e346c21a20e3e4218196356462c798a3b8896460d9183fcf047c68ad852cf1d7dcba782e7078f431b49734d4e92e5f8c30593dee7021c5f660c33162c9fdaf90b449a53b1072d147e86d823a3126a88770b11ef7ca623c7261ff6edb2f6b14c8fe8c168b72d015344cd3deee718a97db5328e377b319a0cd5806e313dbb8ee2e55b4a91be2ea1a77d56e5a2a5f5a9c490e46bb422f4e7f4a9bbce1d9ba186f0f2405aec591e35fcaeba0c5a9398f67d846955c4acd10755c03ee7959648d77e7a65d476b1eb655f804b5300ce851a8bd5ed2297ba603e1226277447e2cba9351b00ca18eebb9caad17431243785bade75fe4fccd60429929944ecd187af55b907d834c9129c3ecaf41f12423220e805710f5ca8a6c02d78789e83107b70825368b27428d81207bb213b21d1a44432d80ad3bd586d0c797fa2dceb5c5bd775737cd06f93e1dea1c73c55396a30b055bc08c544bff35a3ed99d56fa7a342494b8030befcbb6343c0067e6151fb3cf5a5aca3b1d3f0fee67ddc56d0b839d0bc8a7dd82ead9fdbe230208806e0addb39350f0ff87e636b1f831294ec4aa43ef35adc82b6df00b1762acbe8026b6e0a9c4e0f5ae0ba1592566e48deac3e27176f4eee9372c288b67f0c84e449693720f46c11814bfb357205263626c7133ec97a3ef954063101ed6247724c23ff4c9aa9025fe4e2f865d359a3177eec10af201e103fcefac308f5ac78c21971e927b8998c0ca951c4af693b6496852590728563cda015d224ec6762cbdb945ee94848540347387b438cce74e63fa02c73f12ab8bc54533509d245e1001f9c4712a9faaa406d273a63834775e1b8bcf3a74ceb284dfdf5fa0edd4d5768933a19fbe3e8bb7dca91b94081cfb0f90ef62410f5bf96ae9d27077fc279304f05054351eaa57886ffa0710502b4a74570daf22cb8707e3e60c4d4773e6e836851c686a640ccc0f1f9efc7997178fe64349f14a064116327e3f4f54ac8041ecf7b0b95e7b1b43ed326967c73dea5bcdb1e231dbe25de076073891e697ddb4d4d3a3697e522bd2c09665ef9397a01ce47fceeedd76c6e457a647139b79f6c1f6f24192c96673864b3387f86cc275d777707bcfdea1ab03e8bb80d520c3b9a0dd2d8309446277addc39260e2ec21128458598e15de42bc37b3cc8bfaf78d8292c1c068747f22b33770f14f014cd46bbe569e00b66e4f5ed0fe07a011b9c6f2866e52ecc330503a53ee7773fa3c495e58407b0c38d0f22dc254b6b1b52ae34b9fc68ed2a192875e0ab7af67f7170d64252abf868dadfe259a3a02678c8a1ad25f4169e4c94260a44c8a89e006479103eff792d07ade865fd505d1883790a9c505d54207cc82355e668eec1b3ae3d7c502281894d0708001884badc40ad0386f55d78c4d48c378092c4d6e0e85aa8d533e41801969a96544f0f37895bf3054b429db7b0e62fd3239a616415a94cb663eee68c4d863f50b88ffefa868a41e9a9cfc5e5fdc3f3697a04939e86614910a63a2dee7cd946eb7ccb066bccdea0b6a64ad8ab7a155d97d5c13ac444f44f330d452c19d450361af759af577e202b07fca4686ac9f9f4fa8d76c0021cc5ebb225c49de58ecdb4a3f950a8aaf155df24523f5388760716a9151c82d8dfec1af5bb7be1a55751462cd1000726c24cf8300b5adc6bfd23471fc5f8d05976017684646597202e1442827471d23512f801c2ced98a66b38f972e10b2173ac2d1d266acf0eb4fa48ebc20cffb441ea3fdfada29373982aea9b21fa43775c941f44cb7b845caa490bb11da104e75535e0cf18005c668b801357e640ea3101551b5d8bc243a2f77b1df238e5c406f384c36c139820460a59728a6addd5fd4a8a642fabd89207398da3ff235d72936d23c7b714ec4f878c26735c3089c4691e1f5b9cf54f0303cf0c177491b8689a84cdfdcc87540710cf9861416d697569741ed9f6862327b65ee706831aa187e0cd96220ea7a2166526486e996b321577e8fabb243f4002740f48bcad2e0032ac436cedff7ecc6260b038c314f78082920c933c92bfbac2d4c647ad0cbcad8763e2b9ec96b704d076ce6e5578c7965ba51d254874821491442b487bf90aee5456361dc23693bb62bc3ca05c39180f3ef890f74da3a2dae6cd9b0486558cbac06652b5532823764567f1a599bbefa96208b3ea167739106821ef8612a56cd3b7022718df4b27a480ef57efa74cec1fcdd6768fe99cc281c2669b0e3d7a68e24e1050178967d584b86582a0d5c6d0327c06823398fca16e85491ceecdc2a0f4b45c9f05f1014d0d0a6266cf985aebe7edac9d82794ea6565a796728f847dace50fe0f522702226f369f80a713aa26a92d323f3862b58b6d25e636d82aed9a4f87c450009f194ccf7af74db987297621828c181e0fd65d1fc88b76f1bbc40fc6e8f145d73f6d69e224184215202be68fa6b76bb05a3b728c4fc8a9a454d1d0f5aa42f48951150bdfb0f591e0a3e8e850373d8315620991b56b998e71a1c6c36d6136c06e3b031c110a5e3351f7ab4fb4096280c8b795d4b0a0353864677ec5f79dc1f8262868b61c2e052c7023641b06ec2ec0e3e526f93087fbf587bef7d261742046789a9a9f530cd3a97b9b5f59023f6befbcb2f2ce6586645511381d93f0bd4567b95f12d9b876d90efc142f0a92c189b64ad7bf13a415bd4ab37a33927e4c0df59c4aad17c33017e1ffc50e68c32b9e38f7604fc5743cf3640ce78d220223781586627f0b67a000ef8331e149922ef0888eb3794f51658d14ac5989830de4643e4a394dcb0992cd8c3d39c1abf78169e678d0442a397836b4595c51ac4f28961332659b18766c4b594c794ea3e6aa65487b512b81e2b5ae02b8e48e10204cab69b84d851689e6fb5f1638339906b034df1177677808b1f4222ff183fab0807f50ecd55499966c1719e418d7a83532fc3f7ab9e9a1cf059d8c5654b7ae49b8a3c062f368972779aa396434c95f9384b93ec0c2e11ea92a7799418ecd1e8f27843157ab031368a80ea6c0a91c2f9d0e60cea2d93a7f96eecfb00214ec62cd8051900e2f2eab7bf088c5e2ae863c62ca782cc04f234ae09cad8d7d9e99d06052ab5f7b930f2617b8aef8a594a09a75e5f643190501da7bd9cf62a23aca72995f0761381da198090ffe2de8b99883884e67fd80a4a36472169c063788177dbdede6c96897c1b017f405ed4868283da2404d26ed321e83791a777bd6fdbc1a5afe4ef004d16d85921e4f08f0b8a082d9327ad00af07fd1d05c0351fb40d479469172487d833aa29bf2143aa2235e885f5c8c70adf4fbd03b51359845c70b7fb1ff21dcdc50b8bc224bd17054c29297cc9384490f4f7e86e6b05877d16f18f7aabb6db85e14ae9b0a4a62cb870c7d2dc05272e25d876e108c71705dd5c43a52b87abfb3e29f205e235fcab92a88c77b98007ef37db0b615d70cbb9557cf0e41b6340b71c1e5e63205b4a32748895d91b336c61d5e412000dbbbd23d88728824f863d8afd32c305723073b99826f4dc55c784bfb83c71a596929f8106b623f0a1c4d693b27ae63f909ba290607f11a1691784735dc9873de9bf2082d3ca5ccdd094f86c4bc9afd0e727277cf724477e6ec25adb6ee22ab1d34a24d6d0591688dbc8483422c6002c993322c666f8c01d46eef277726d41166a4276a45cfca53e1e0dca1ecae431d122929d49036a228a86d83dc7e3fc9008bf27ae164b592c27cab7e713cbee093ff3c28a43d774b0144312b4731d0a9d68b6a1d6939b0825e19b98c58a921373223c7b097cbba32a89db30c034de82fea9abea0dcbe5713012ed12dce96ba825da0644e1f0701136796e1934154596f4d4315f21c21e6543f0f7cc30d1d93b1304e3619ff725db74029507ad3157c59440ff9db1d03bcba842d5b0c2e5886a36d8c4c3520836e452afa951e7fcd91ff86b1acb7e4b82ea73e3848516268ad907fee4b1dabab3063213fabb3211ed333d452011d679f633e21145618084866f85a90555206cef2f6b469ef62a43f8a84c2bafe5cad3ce880fc24abc0b22f01240cea8ca8a9e390c2786a09bc74b57974ecc9d70e0354d36031489af18229732c3229f7b9244c2a6cdb5772139dc55a88b34f409d3e642ac07ccf1b00491079fa7e4026e106503f143a515073cac34704439a6e8773874a90d42913fa1020c6d85931a1c91c2eed0fc70e10c2f3f8978380385a07506574f4a71eed1c4892d9483ff55b851640c494eb8e0469bca1b3bd8304c70fbdf62b1ea6b70a54a08f3c47b5724ec908f9313beae6d54c0603dc9c5355938f8c72a906992d333a22353e2f0f883217274a6fbfd57f0a7c923983b8a2ad9a044ba9c4d74d74cfccd547372a5b3fd9883b2ed8273e7a91bc970b9e55be54b23b16633c230edec6074e31aa8e03040594dd958160a967d23dac098e6ae11d2400c572aa7ac54af82fb456871f511da924e1436c6e992330954bce5bd2ff6f6060dbe1a2bc70c6143e6a7a227db06db0287c192267f6fea46d4cd7dff50c42de810870ed0eb2724e757f470f3145bb85bbbb7f782fbc35c0f9537916817d3faeb98c3a36ece50fd0b253b9727ea52d76811e81807fc8adce91328b13a9091304399781983aea851bf43173c15f67084ca5ad400399d2f10b361ab772f60db5e49bcf4d36ba80a3250b08f0668a43ae5acbf725896537fd6a5a2ad6b3e17dd03f8baa01676a44017609253244f44e0231119699f2253e1c25354cdfac1f8ea571181d79076216330ec6d5f57db94d07ad019d31e58f6ba48e1ffae8791068bd3c1e451de0bf5719042119f1095a6537617eef375b23eb7711499173343212a25b829c80c0f9525bd6e1c8fe961f0c82b9984bc0726e453d2ed7edc9eddb815ff871a7db54752c6e5fac2c342526029d4f585da467644c0f31be3ff61c7b092a43fa68de609bfd3865cc11ba4305f61ba84fb735e79903c87815791c4a47ae75618164167177a7c17e66af0c9298ebf4501fc8250dbc43c94c80ceec773edb5a41346b20b78086a639ee291c6c3bd33c68f3dffc96f072cdc2b17c8614d90b41b8220fc139c8ad262e6bd6844eeeac30b385e8cb967f838a01f5e941c89c0572bf6a855a038d401b7fe5e5b93f6fe520c5176553adc757b22878cd6c4c4347d8b8201c7f7c356182f4dae972fba80b60d5018a8f8fe1c7f346fe4ad1d0f661790ff63df7077023810550cfade5b98f3ddaaf3e038cb684ceec507d983385dfa069cc8bdc81a09fc7233545d43e243befff92f341eee06443ddf70bfa70bf17bf8a8114933a0f05fb198276fb2d84e1222e1dff818487765399ba95e6f64e584f1bc8fe13fba37c68a613938b509871bcc32dc63d1cb5b86d1b41b3ce765dd019c7262e2ceb89b4b0d5804dcdbd35ac0230c81693ce106350a7f70dc418832b41fbe4782f0448e009b83b310fef40d86713cd43c1e28ff496b25066fe1de9d81a1dac02f1e6aa4bfc84b09925a0977d7b1e051d2301c7fa985ee5eec0456d3bf6b180d4eae7077b7e946f742e89b1f1290097adcd73919aa99816d1fdb08561799f70f312f4c03df11e7a46b4491e7b5e3d20073acfb6148f730a8de000e6dc32228de0e8e0bee53a56e19b2d028ba31d8408a470c480b6d25e4861def112397270dcb331cb7e19418a4b023e56511478f1a3f5548c00615636a12c5177c0c4ed3b64e315c67c8395311b9ce9efbe2902c7b41a4d6d5b6ad0503ffa0678c127419061d9f88626cde1458cf18662683c022cc574e0260a1ecc75399af32a6f1cc21c842b8bb33dc562f7d31db4bed0e4eb3f97d709f9033f2e286768c075851a51a4b597cd8e20e1ea53f54cd46c669826f3db24b467c9d9d287814b8f7643cf1482b51def95ab85e75cb49b6be9472482b93c24a5b9ccf2aabe718ee6e9a0d1c89f3390987ca823c3a4bcdfb8c541da240d782363932d7e58089c1fd526856dd657418e840048796a7ac8cc19d77dad1f472c7ee1f20f5921c7528a53ff6a6ecfa8b1f300bf7acfe0cb973e40d1917c4382a9139bee8f7acf3dc8c10970b095ccb8052f3dc6e49e9780782c70171a058dc205795d9df664ac03cd6d748c2d8943a0a2272e591cedc1d7bd6e96b989d0f838263f6ea1d05656b40d1376668de9e4ad8d745d03cdb78cdedcc1a1a0aed77f9cdfbc88ce8774489154b229ee1f7ce681f47206e198ce1eca5374cef1b5ee894bf76e04676370a0df7edbc1709360b4843f555a7f6bbcc79eb3e74ac6066d245b75cb4ffbb1830f0a947e4a4008e20397738a56b14ef5c2f7a56e7353ae8643904dd8d0e629d214f0737979bdbbe393ad886d6ebcafd06a9bf41a72a06c56a46d3d3fe6bdca61530615afd0651402b9655fc16d48ae836b13ba0024b3d0374836c118359460faac12c497e739d9ed4480128d1d5d9c1104a8a62739f7117297f58ac3440e90f1845c1cd7353a710b3a44bcbfb4ab101cf849723f4fd76437610c3c3b4d315dac6a44f9042d4bdfb6876878628d9b4cf664177555493b10d44a73c3fee2bd941d607f037b56be13ee1c54b9fc0e250df6fe3081a8c20094c229673cdadface0d93a351d9a7125ca9f84da8481be8bde7049398092b77f1046f71541c6efa009759559d60b18413ca9754c8c22754c956b76ef9c5165b71476f9ffa13ab1d6fde39027a233c55d7207937219eee3f9c182533cd7f70d5ddf45ef11f6edcf687261f9c7d7063df7d22aa56a0cd9933f8b1041b3d47e23d93d1944737c07cd81e1ab9d76938edd17d080bd50bc0941a7f4f49145982c58ccb5a9194d8e2e9dd1a08a0541d738d7412397088819edd4b664c2bf0ce980a32ca102858589a9406433185fdc7d121690a4fffa7aa855915da3b2a0fae79497ccaecfd481252b1fafe6e95e269bcf4f202c5645228d3dc0fa08a68e2624def0450a24409f7f219124d8763d94c02091a1c1048c712c5a0399ae1660ba35262d076585692a4daa3c3b9b9a4698cce0bab4e86794a6e0c32ea11267170be9664697a29cd40f1663a081d084243db374f19820a5618dc04385508242dbe1d9c295528657eec0ce761ace47cbda7360c0ad29cd951480c82171d3dd279c815b54f75a226b330169a7f8302d180070791d9c64b1d3042c49b9cc19c4133447a2f9c09a5e4384162de119690d04a775e95b8ab63d9c9bd81214a06e8e769f0b1c25250df516f31dd9be0994a105c5043454cd34bf41410042896bba319180e7f8f54a4671228c3eecc4d48b922c14adf35df2a294eb65c35ed29b8f4f54285415838741c1972cb213475901b99cc8b34fea69706833a4662b17f619e87cb7051e43b2deafc51188c9b2cd7abde771b72bf18f534c365dae1ffd996b632da89a0fb1623f1a56c6c02853b27f0a235cf010bd28bc9f7340b46fe699a1dbbd55bf705af13dd983d49cdd9e7d5cc4796f2391b672769b95d956e20931c9f89215fbdfc400e852dce407c0eb05f4e92c3a3eb6614d910c9b809c378d7b30070f164e866d047d985cade96a196d5781e99f9152abf9c9830de551198dc2ccee27ffe9b7fbb6272b1096d0e34d0638f22d0b5641ee8e3ed579a711231bdb7d390939416f99871a8fb017678bdd7dd3a5acf124261688c3f9e0bb366f2801f8cbf3c4bda70d481a733fe16454349a4f9bfbe83d435c1389609158a5cbecec8f46f2e94f76f69ddeb717b0e87e0f7881d949de10819a4d7c2172f10f21e61362580136f1194ebaf1ca328424805d5d3e7c9db6dda019d595b5fc14c1dda79c97f990c5a82134ba6784b04bf416648567a98241ecf62ac55c8591e252e3b5110184a07468e50f343b5faa292dd8ee75e1d95141ccdc272da58e449ff95c7bd5756a064b7910549832deda64cc1748297e8d57ae6dbe00cde9cf4558f7377f5f95704ee9f2afa48dd77b9e85ed619da71b401db7ea141ce3ea31e902c243f152547506605acc8852f3205b0bb8728e186ed2acf78ff8ea4602ad7636df37bc0d4ec9465c7fdfe6d1d02c07fd04310d97056467c4f2c5b5f79a0db67a64bea83e0604d80dcff3745518068ecc0126619d66dc258420d75ea255f1a06953982ae3802dc0410f3b538b39ed11779990f74c10e3a229a0b32b973cac5c10bb1336d7b4cc801ba2fe86b036ae2e621335bba87bc0b0f1f1d92c8cf672d45536757c2e740d7f900f88b8891a5e1e10aa2150a99fcf32ab4168ab946493dcd661c455e45d18cc2c0b48d5df8d87348d8d2a2561e0023222dc2125e73e1985e115de03f1bcf386097c8daa16bd18efcf508217604e45e31363a2152d45c9a13f9e902c1f6b1970c624bd35850077c340ba9bf09a81d167098c9cc7b82a7d399502807d5f611dff75b790a0564d56e3a59321d97f7c698d1746f034a59d0fcef3eb1e2fa38d0bfd90796b85e553fb42620a01fea9f020410e600b1bc515f4674d19b14fd702f7475b72260a78d25abf6936ee281a29218f7b453eca11e71a53369969a7fb113ffccf83658e6a61094123d1d49f043e35fb675c50fcb2c5f6ba514f6fbc3072b871b881cca39fa801213a611720f0543a7cd9aae9c866b016c1c014ae7a076507cc419d6a3c03e9422dcbfb2cf9a891a240917b424668f551d800a96da9bcfe4f07da370c79ef2405bd2884d2c060b21f15081f68aed8cb4d560402e806e9c4ce9bbc27aeb6f494cdb027bb42250ec0f64e827fc7de8fe431bb35577b4d090e4ab3deb900bb48917da5b9ee8e88e05151fd688eb9ada52054505a02246e75b4be21794377de1430fcfaa3e33496b3f6bbade0bc5007886a6bb180bfab84d2a699511188116e3847201f62b1dd092113004434909481805aaef164bd781312625d38316792ddad5bef2ffd83a1230a0a6f7ea44c4e7ee8083a2c91447dcdc144d82288dfaf82c33c0b7440476a1132b24a171c082a023d7791a28747d7de92fc3004bd52efd14f4efa6071ed3ec4a2066acbcc9429d96730c598a2e614321bcc768ca2611a468cc732fe749b4db65a716889e907a5e9425e0fb3c64b7ed73151ab89e46b548c5c783bc890e64a38d081b10cb5914004a1b8117b6509239d85c8677c8493f2de4790030c2faa094da71304d307a1b92d1c1280cb569cb800cf9c3697487f8dac2c44b097f02ba041109da89cc1258ac0a2c2fce7a2cc8503c9c86ae90f8eee822146bb35904e3ec1d7863c5d04b9d06e4d073f3ee84a521a19be146dd5814646464081410e9f47ee118c979c0d632d0a183f36211d13575c3ed841c7268bd12612464481426ac4aa026f4133427b813ed89d744fa2734a043dd70cea10c0dd3c13885daad6510439e74474584d0d932ba213e57ca895cb843ea96f212196c21671d52a790dd114d0cd7187d39a8e60cd80df55c29278ebe844ed532f2a10807d61306ec658371362a8e1c515fd773f5c270b1ad7e27e235a82f43194d21b1374ceaab833e61b0ce79cce6bc4087ead0328a43a50e3281a2764a20e4c9aeaa2564a56e8d796890e5fc318b800264521bf7ea946b7364418770dc6e5e0c4969d0248a126a81b20a688d285139890c65e86c9c46215413275744e8b1711261893997c82086fccb69b4879ce1e852863eab659c426e73ce2986a6dc4024601442df1ab726141d0b3d374e6282243264430e8e53624113486543cf1a3741208ddcae43a6b54e2c93d024c191502ca830faaba3d06511fa8cbbd41cba6b54144d9d0ef88c056f0e778ed4f7dae7eaa765d58a93c808096574a098d0d8bb7971313c9adbc4ac84128900f3d72175326c4d0df6f952f7eede323095d02458fdfbc41b11b6f5749eb70d75a293846c35e877377c9bb916218ad114d3744e66a94a6495fd3d82788e666c8d4ba776295d6bc87318865d3141c59328689077209f1377af08f7383cb7c0046d82f504aa849ec446a27b50df782dd6232fe4f995a38b1baa5ecb580ec53a304d40671b7a83964108f5c4698408e1d11ded84f29d951230dea16a1ade44ab845b096f22728247132ca687bc2db51565b0e19c8d5a0c659d796233d131b135f19ef027a2264624e8272c252c7522862ed436073a31f4120e0644e81e2d83133a7e37d73294696609d21465bcc1e9ee4514fad4d1352224a733da116abd96a10839d36038856c1b172934f221f26d8e44a727e8da87ee1b510237b1544293046782b32670e10ea9b7c64b64b0439d6d30ce42365a61627e6239a1558283047be23de12d015982c7135fd084be8e85448b26eaa285eaeb60b4091da3652c4315e334cc43768db0446e628984d6847526a093ecc070ce0abf1c194c22420ea71d228bb6e722dc510a659d798266827b824de2a5c4770232219aa04d584e70aa355a20b71b0a89eec4b612ef24fe0941ef91ae0c677105c41cbc8b2a0ec59b5f82ded83227f2eb630e04b1be3bfa3274cc96b108c53a9312b899d0e80a39c2756109bdec42970b193420d173e34b804ccc26081256255049f06ec28b4da80d5a864248eaa24b1493b826819ee0fd84c6265487ce6821346d1c2a91410cf997d3680f31da52a2df2432de17709d7ceee9057497287f66c1d4d43cd1a70b3bb457d40155036a8bffcd62d48e3711223137b140222c441abbc00577813613a853115d3c85171e3249105ff76d8429f8a5e8f96ec48cb2a7a6a2da129327296a8d7043db00e67d1a0989af58daad29460c82f14effa167a6ea2faf8cb7bd7910db1185d991a9a2742f3c5304d1daad60bddb6db971588c628443b4b8b9cd389f9dbd8a83595e48916d8c544c4c0dec2ce785f5affd4600991e516691dacbda52abcf8abdfb893cfbae0c03b79f578c48e0264b933ee96337b8035c1e9f6aa5a3a29f18797e6ee9f23ba2180fb32c9a82fd3fc1799a5f74ca58f4090ce3022ad66d5f5e5090e9a6039351906fc5e78bcb43cffef58880df9df920a00bdf5e603e0d3bb2100eb7e816986d93e05201d7bed9a15fec7c6e1c5beab91d7a10c7103ddbb9bd3bbcf9d2435c6a792f151bf602f65a8b501124a4ed2df796524a299394017805b90521066f2a759d49979e44caa10db7e5bc695fc518db2cf2514e34fdea2eadb5cfb95b4db3f6e65c2269369b64cd9752fa531c504bb4fdfb294c33e993af43fa0c71c3387368ca6c98e3beebbacf256d32994ca6c7a6c76f02a3a74c8fdf83df1ec7d65f283d30b1987197b897a42fb4d91ce5382ebe64edf89bb4586229ebe33b634f675f7077a7d153d6e2d834565a69ad9556f71dad77840961edea1587af5c80a60c6a89e65052747c9ff457eca88ae0bfa7e2474f4917526badd59d73772f7d2c71ee9ce49ce338e76adde13e7aaafe66adf62cfc5ebcb198d77ea5725474a442d18eb014bc6c9ac2966dab07ee83a1f6806c81e709590007435c5d0ed95e1d0c6ddcb8a3278be9b328474701cde9d351409ea7c3c05820aa2825b452ed632ca00154a31aa598528b574ced2de06dab699fadb56a12fc469f547f63a30cf9bed60bf0d4fd420f638ccbf2ddc2887bfb6c1d7fb6d3dab780b7ada6e16aef9bf05043522b50c4d8a993d71e3210899de080180b8845c04787912826370ee78cbd73c165cfbe7d96129c130522b109a240247682033ce5ce752f9f44da5e7ede3e4792a4b4b14b1b9e33c4526e1b0b8f0e626e487456fe6d4a67edb97d32cb5c448731261f050eace1427ee83cdb26bffbf30b6ddc4d01143ce54460f413f45da5512012731c769254902b1a1686b6f46147d88fd8ce8e6ae910a37c680518d1a1caf49bd49e855349272d997e2b917ea326138944d2dbf49d3ce7fc91e029ff384b6ffafaa18248a5da447bd217da780d9659018d9eb3baa8fed549bf99c0b0f4db36a3967eab1f2a063d9fc5046bb898efa27e68fa4e9ea7e687b73aa5940f236e09d678f039f425f87392485f2aedc89d6d7af985f5b769faf0d61fb74bdf69933ebdeb47378bd9447baaf29bebefa71d59c0a0433c31fdf9331af0f53b61beee2c514129d19ff19701b95131e8eee3971e77b4fb59515974fd7c92916e3721b771bf711d89447a187193c09ad2e790fe3e098c9e22fdfdd26b8fe32b91be10bfe89efbedb1c69d4c6cf85e7cab50fedc93e56719df8663303c6d8ddb5a8d1847ac8558ca9b3f0d4b89e3c7e5f849e92e6594516af193af61770dbb477719dd23183f7ea8223ad43b8caf4fda96657cce3b5d8d028126611bd984261ae0a9ca42019eaa2f31ae35681a8d4fae82c53c51c31578bb5f3930c7d0de3ec67801d2f670461a70c2967d424c21256a870ea31104728cc70f9e1a514f74fc17f4eddbf093fddcc6f7f8d918a38dd3da4f635badad3feaf5baef67b7addaca325757fbcf448e41ad301f85445baba14003b3b795e6e51a3942f9d031fcde7bfd56cbd92bb3fd6efc5877d09ef3784fdc810d66443a9c41a3c30d8674b8c16af5421d79b1e4d6e46aab6f719fd5f55411700c6d3eac2b81f604018d0e45e05c8434f5458d1071b4bf1d7520626855397f1c1c8fa7684c311fcb9e798207041de8b0b4b5f9a5bcfde5be725f9fabdf7dc7e5df9ec5bc188cdeea41bd304a8b0ef145e1376f2335cd5fc288bbf4dec5d8b62f819becea77f54bb5244960f71cf769b1c9ea5b508e33d2255021d0610a73faf4748bd9bf10878baeeb1e46dc1d5843fa9ceeeb77607d12d739f7196f72f2c8d13d07e23c63b27b943c5b62913c5ee8cbe700b576da69efedb145c884dfb80d82e3a93204dbf1b363c8ae0fdba163d7df749cc1b9727a624e7b6a6e60ce9683df7f7632b6afa02e117af83d12d3732f7a1147d7d086e76c403ac278b876fded613c58db43bc3b1855c656378e38f146f39b1abe71c4899182777b99e34ffd857d2ae5016ac83ddf849826cdd61e833532a4e63b7732b0ef8c5fd088af5df375d32d3b19726767e31748a309f475b175c9d1e98fbe1f77f836eeaee9d3bd8fa526a3cdad125f5a20c608c6393fa378345645548ff68fa03c31a9cf496da8efee608e47540b3ac618659ec7057f43fb5686f36d8e7c5b3b19f2ad9d1f61dcaf9b61df050a143aa52d630af911bb8eb6fd8a669751528465c71d9500edfc96a784c26f42ee73a7f3dbfc755faab5fb52f7d96cee0b6b95324ba943afdc6adc30b16d78bb7b1b746baf33e1e1eef147fafc6913e93169eb6674332c8867d8af234c1bd670cb35743060bff437e8b6f5a5e46437a3f47df54bd5dabfff61fdd2e75a6b247d8d3145f7f9d3bef4f5eb9ef4d9ec0e8cf96937c3729e92f9c3db17d611228862b3929394d9f23dc9d196eff5c3ff1e4594cf62e6f87334fc1b617f1294db41143e449e5fb4c11d61495c9ffc744a9bea48d9a6da674d56695fb3b56e196d76ec5ce8a0f09b982b8f24ac8db323ac75b4a4b58467479c1af2634c613fbc6f234e68afd760bea55ca4a0cd461153c4f72e488eb6bf2cda1506fdfbf7e327d36c8c6d9a1de3c390e0d43e82353728ed68f84bf0068d6aa9a560ced4a4ab606d1c29daf431f739d32d7f97a56f5476b9a6e338eee5471d3983a3d904fb1b8a19cbf35fd890fb3e6a8cc6ada27d3f23c1b229ddf273727329e98b1a0e0cf3cb4dd39991c11b72c7bf3f3b1af4317843ee0db4dd045fc4b7f25ace3e0a4fc918f5ece2fe1368bb36f10ddd8e5cc4712bf4a9539dd2f419f3e976fadd08caf368d7bfaba26d3f62d9f36910a0edaff9ecf9942797f0ffed60d4e8c8adfd0dbabd8e94edf83aa51dc1cf6f3b1af52f78436e0d0c270dfb112c85f66bc8d03b03feda5fed3dee6bdf86f61a98a3bd7f60cb2f7edafb67893b9dd2ce408c29e6a4e1afd11b723bc5608d09d3df86dc16cca92f74e4d6917b933428d3bf65f67d4b1fc38ed0771553e5aa988a55821224a1040a8d56a5ba686dcf69803d33d5f146d67869685ca871953a95e1e00db9b9ce059523e0ef64d8c73f3b1db96fb5216d0872f41b1d7a04c410f8e16808981f2490c20c4e10c38a081bc460884f0ca6cf98b903f503ccc816580c47a8a38ee4030212681fd0a0033fa63d9ae470a1053cb024518cb4d05c4032830b9c4f0f530d7c74c800b3e005664405606ca53fa4de06e693824dda11e653b4c3577138a410b1b7b0fdf503780822ab01264688100851a9c2a8549e9702cff33cd41795ea0beb88080f2ea069610854110c061e180c2dd80b3bb02132958a042a15aa04a1e680e43c2c614715b8457c94976aebc9535b15522b8e2a2398bdb0c39531ad282d6acfaeafa5ec0a2392655730a49ec6b017966c6e47d80b45c0c088fcc060b0e2a17a4079a9418874dce1a37ad0e1bf4d0a0590edaf59dbffe4c15ee8e141a5fc598b78d0f12945f9a0e9c79b88a33dfd2ee2dc1057dff9eac81dc2ae1f7e08bb7e08db3f01bb067fdc39f7eb6b5f0d064d40c0ae17ac9181805d1f01db3fa6993407d82cc29b457828984ae593eacfbfa06023a0c0747ffaf5dd0bfe219e883b2cc168a1289e88db82617d8bd241a376dc5e812ddd7188dad39fa04410b3de43894e9e037120cee340bce76d6cfc65435366a456c597d24f0ec43f1b13b17a447242388de519ef052d286ff41b7980fbcde8023d1b4fedc8b4cbd554c1d538230f6e056eb502c7c36119d21a42f43f34d0a1241a72110d0db980843c2d5751aeb8154e263c56a5052c4282f6cebef41d7f78dad631184a325b7e48e3f6ef7afc0668b77688370aa1207428634443b12989e69443128bfc18f5c9793a209fdaa933b81b0544d39f77479810d78fae8fb9eaee8aa9780343480d31355d8790d56c47580f991d6ad911d6f3b3730946dcfe67249659c491b31d1b90e70911479e1053b8a8b34e561c1271649c3164ff213d3a5630d132943fd8051dea3fa93c9b9a927206944bfea8265a6e5412bd839cf199cc53f28ca7e4952c309e927eb482c68f42a27b583af499dc1913fa6ccb77327e5346e637f3514cb4d43e94ac148a9dcd6dfb8296b2fe4431d1724b91910e492c72480a4dee8227fcf7f4799eb26ff157b7e5ac094fa15c90bf2286dfd4d8d38d2d6a012ecb696b8cd44b8c36d207ca35bbaa6d99a4e19c27a9a4b57e18716bb0c6f4391a079a19994c46cdc866b2996c269bc966b299ac7a3be8aec4993ef8e6675bcc172d429368afe2f99a5a481d97377a2d97377ca76d555a84cb1bbe5e355a84b2282d42599445599445592bca8a29fc294b66ca421e9c40f04a042000dcfc6e8b2c345b31554c2d5d74184fcc7620b64f99ddb250869285b2d0898a2fa9e3f2466fcb7279c377da3ab57079c3d7ab36b5cc178b4e2df3355ff3355ff3355f3185ff7cc9288bf376d05e0913213c383fdc62c58a152bb6d98aed8f81288b325994bda2cce572b95cae6d158bc9988c19c9188bc562b1581acb68c668e446710003d8a1afe20006904afd7b1e76adb295f93375f335da8a98e95969469a75336eb3b3ed1571f0fb6f5a5eafd7eb856d6b5b459cfbfe9b8f6d45da56ddb6e2b655de56db762b9151c4e9626464646464651a2be2d4f7d78a681c189a4c190c4968acf8692c8da5da1a6bfb7b8d451c4d4c2c168bc562db5f6ad815719c4415198aa6994c24487cdf89132ad70b6d53300bc514fe697498c908e14c86caae1165d9d97cd996f7c87c861082466ac1bdba0df0e8602821810f328c61a18c0fd11424567a932e3dc4f0d3a22cbc488283a3144009e263254526efbdf7de7bef7dbda0d9cdb52fa702149f7db5d63f53205bbec30af1606d931e9004893274adbdf7de7bedab342bd29b74e9418679afb5d65afb85850ee480e50439c05e51cadc106484a402d140455e41308009924581a2034c574c563e86a8c19734678ca0f0b8405116274508716608178486e01012640b111e5680831cc0204848141894d47befbdd75a6bad7dd55d59244b7e54e036072512892359418acf0e64395e40810e4f5a48302d00508f235d86944c596243500c4132388fd593590e38d8e1e5871d0075091a3243cb8fd01348d023082b2f701204430d415264901905f633832ca1065790168278a2450534b8103f8acc80e001882c4ee6953a83ac2671496b6debfda4b5684f9f74d45a6c93273dc86618a182ed5199046199f766f9d4dbfccf20698670820217662e31414b82c6ec9b635f6badb5d67bc4a0c10ed90e2113c4a0ca4f972015d0345287b5758627b296b5f755f8870af5cc0064c5440622b881eb0557ba2831d26221280725d4beaad6a0479220264270288181941d1e57b884c9d18212e50848970211477cf852454b0e244246680a4f6880d2b81cc9a2a41e113aca32efbd4784c264f9a12582131014f9410408172555862767643db2ed6b2f4fb20079f5f921f6f3a4ef35e202185c1f5a60422bf862e5a84c0a2d6e0cfbbed6027b42742485446982efbdf7da7aefa5f7de7befbdafda7c983d8a04d98004880d647096989624f1b33ac9b60811c1c48b989f4a0426a48584124a3fb933186a36f4ecf97267c71d65f3b10dad3d6fe8dc6d22a670cdc5e45ce8131172bfdf7833dabf32d4dcfc42fcb89b817fda93a7dc0695728b740be5ffed667433e4772e3050ac8bf55a90daecba9d86fcf927bf893fe5fd61ed6ff6372f5ee4cbc80b18ad855bce6682b4d67632b6b79b7de1db3b1326be187fc55fb7e06823ad39f5f167da9920cf7432eaadc1bf5d60df18bfc552b6d030ff8ea9e13ea6617eada1be3579603269eed4bd90af7d6fee177e39a6ce0489b1ae60cd0bdff7f3b39897f36c5425c43801050b7040478f8f14bf19e11db785d330085cf49bed3d0787f6b5ce0459c3993dffdedfb0d6b094619dca382be94512226b231a5e3bc29c8c7132d45150c7033b9c15d4800825586df92e27ac2d3f9439db77fcdacf50db57fbad81b3de98af5dd07e604bf7f81ec4b3e376f978cedff6f3c3326f539ba51de2a85fa86d3a411ac6d9852783416164436462856a6e697faef84af094e3971a96de8c0353050ab1d9fe2fa6a8fe28333a8c3116b384eda1aaa61c454c45800213031187848a2b45e12910d8689f2547fd07cd8eb01fd60c6536e73793c9ce12fb5f3917f56f88b033c0004ba98c11b6ec18f0c2df625c6d8ea06ad06467d37726b24dbf36e9a99cfce4ced467788dfa3774eee6620af933aa0d9dbb714c217f82dd8cfa14bcf1cdb0e28e067d97513f3a5170e5a366a0e747cfa92f84a68f9fbe8c389a7dfada06fec5f369a572c653b4e74f2c403e463b841d614b84f6672cad94f2e2d8452e6b8f3f5b9b3b17d696de6e8f6dfe4bfad20bf973d36ad0076b37e3ca2b431b2c168be572b95c2e970b03de72cdca62b1582c164bc65c2e97cbe5729161c55c427848bf3ca2374a8151c5582c168bc562c9988cc9988c49a199b36658ee68b9923d29d99a2b168bc562b15833952c45e6631e1823232323378a2f0f4f15136bed79ffa9944ae50d210034a57972bc7d53bf21c1a36bd0edd2562ca59c949a3c136f33b18d09cfba86a9c94422aa28ddc0d4041dd750e6ca981d6157bc5c21da117665087645e80a932d9fab46d4c6c6c3a785adbdcd6b3d34cd4705283f320b5a941f5b53827100b3534513e9341ab503cd44e350a403072325145b89194929ddada0b1221b028303cfbd17e39f920bdbb6e58c03161ca47438146dcf535a0a82603c01223c654640e547119a1658041bbf1623b1a74fa1fe3b7d68d035682b3e3d7ea8951976f82a189c706bb7ac9f0aa5ba831ca4d936e6cb2dbf6fc49e236cd9c128b127582be7e27a57fedf8803829d0f3c30e912a9e3f2a6e16b2b1d234208003051538284870261c4e9030f4cba44eab8bc69f8da4a613978e9c9314a29a58c5296e89151ca122da4a6e7f8baa5ed0e316cc82557e8d716d502fc26b4f168824509264cd80c81d92109241f31ba0f91a944acf86c6a0466e505b302c50647da172827c830fc986a5072d89aa6699aa6c44a0a7bdb1136341b42a5bd7f9b148a7baf97836ea2a914f9b8453412bd82e6d19c8f0efa8a5ee92a39d8e1abaa8e16740f6d830e7f879e12c48ed4db0c01848795fdf2a3b6c0074cca0c30c618e325dad3a2a59ef2f343ed8eb029ae7b05861f5b921f1a50107ca5856b021eb6871aa607d5623c66c4f7a96bef7a22d050f40926258b94293bd49f6139775c0d1b784faa7de83b92b0bac9ca6b71bf7e3a6a01f04faefb48cfedfbf1f05ad88033dd99524a65a49744ad578a2137ad9bfbbefb9a5c907e530511d2a44993264d8b6be231f670e474f0f8163112203c1520d458e32603e4b636664a223d0be7483f3f1298b77f463165f44949796f9ffc621c40f891f8fc06de14bcdddd38ee07268e392f971c3dcbf7e0893411790e7bca6b4a98edc5c9a19942468cfca604715b90bd309e619839092d01550fae586b82dbc30d13bb0106527af60d676037d02095b96ddbb6cc016dd956c9de971190600731f2c0a6e040a38285cad0a6e2da21f6e1a814cd3927ad3e9b0f97b3d6da7ba994a1c2024dd3b60cabc243054d952d7347589517e7e395e48898c27fa74a0b4e236055bcac98518e38c1a4f8ec08a3b2646b213ac2aa085571ed509f224e7cff1111a74a0cb0df7895214c51b2e7839498879abfd8166c0f26650694d25a6152d0c0a49cb9f75eacc1a60c814de1814df9f1ba247b3d682f47115e510e76a8df051decf07594286976a81f488e1dbeb6a2c20eb517c30e940eb4ef08bba107ec061f3a68ea6db41d6137b0b0c357dd1594f8017be282d9f02ac1837d689aa6454868e892849c6c8d8492123fa2b860773bc2a2a0807b09892ae22c32934c2713cab4e261a28a15f3843f1603c40a335ec4ccb162b563c480cc9dc81c8acc8a162ef12510d11f9bd1b2082fc7bb0f21ac1d4b85f92e1e971dd3125f4289ae54922fc29242a40f568e1dbf035185b3e6097f590a881713bd3051778998cb8e2b5204f813dea7499876543157d2b565fbcf1705c246dbbfe2220b8463180d9ead746032788cc6ba2ca1cd078b8f66a495d1c86833ed8c0646f3b271ab9fed4f6a957884b6bf06ead978b6d776b481d9c86c6336a26d4bce9ad3db6cc8f6f7a0c73f98aded7ff2a1796743e329cf42b928136530d94b1eca52bc1d74385f1fccd7880906844966fba3bcc812f2392996d6f6af3e9e726dc4082ae3d9fea8957bb3b2b63f893a84d2d9f62f41cfd4d09a9ae9b20693e883c081d0c540a1b8f9c916733555dc2ef31a896101d19ad78c6b17b1fd77aadc6b146d0bcbf62731344bd00e86dc5f685ddbdf4407436efb130140010080e9120098449f0409dbb245c413fe37c79c73ce66b665934415b688996dcd620aff99b783a635dcfc668b4a649340d1563c2ddbb958a35a892a97a8c226b12d6a93d8966dd9966d59db9a793b684c5b5059e552bb682f7c3cad2ce254335cdef0f5aa552e95a812b568e552892a5125aa4495a812c514fede0e5a13a1b6a0ac19359343fbf084fc2c92aa2d974c4659b4485441cdc864d58c4c2693c964b2569d99a8048f3635a564232400040063160000181008088442a128cad21c53cc0714800b707e426c663a920564510ce3308c61200c008000008821061060983386d2069e1f4c64de4f08c332977187ecf557a73df2adbe09637ec1406646243280c5338ea0f6151d4d458bee17e6a2bb1ec3e8162e66747f776ed62b5cf830d3ef983a6eb4b7e3b0e806367708b2536712d9ab7ef5eee1b793e5b89d988edb9d0e6d86bdfa41890d62c030d317a351aaade4567e2310e8b530db2f5461a58119b29c80e88406389da3010a3422724b483f3b7200fb84be47fe8aba61ce019f726eb9725b037e2c94250014bc0b228340a20d84039c3f98e5f0c118670fb7397890bb77b990461334808911f51244822ca9f23616b1a8b119efc50a397f0073cdf27a0a1b6a9f35ecb98a1cb678627ef2c4cef6715c9690b95a2f02cd6b496f056e88b9bf42cb410ab0acd66a495cba80e944f19cc4059de548c255232a5807404038618759808b1500d86e84cff09f2dfdce2a9176b1b481fcba9983a54ced102a522b99449e9199c52eda636114525868b44c80014dfa6886abf36b32cb5bac73f18689c8a3044e09130b2c0343c69853b3295accbc3f39e95f3661cd3b0ceafabc40d21e269f170643e673a727bc8cd26c154005cb9a9f349a42e0bda38e107227641d204dac0f03884922a8346d240559ea2069fe78f4d361ab49cfd6e6d17f91e899f12979a095345ec7463e0824ab651cb45bae81f070eef046ead5871b5c70c37048af73283d435f3c5340715f6fb1c8e6ab688bf109bf46b42276eba7574bc5c4d438fbe894b458fddd20f8e4f2ceedb2ad354a7fc83c575e9f3f21020523d191181a3a92d06131517924c8748ad531e5aa640267239928c5d34e66d2da3aabbf5ed61a5187865f7d00e753b4050af8d51aaad2dc431540f29c3a204b93e60362c7f3c3a3403c826408cd7a8ca5676ce135c226f11a29fe1d6ac1c86ead57b83df06af829a21d459274b98e0ddca29b1c193d8971e8b1cc10d346b5346348bb26efc3343cbf7a0c12f3a49d628f54e58b8c538e15076fb1e0d41adbbc94f7ae5c99d9f771b083ca2be67bb08697488fd747ed720855b2de955d1a70915d9a47f525b9574a790453ceb4bb806204cb3a78dcf5afb5eb37aa46d4d0003644517e2def6b2cb0f1b80648c61a2cf807ec1b2b0df4face8e460089c81adc9d08daa6001e1de2c5b5a725fee50c05676b839b0d1efef638d005f112242f76c1f8325cb2534811175fe8208776b1cfb4560a69d4d0a95a1319feb63ae10f3731176f164a5b8af98310879d6b6e20a2c27e6081085c4a50775ba0dc0034a59042348b7ed5f8b09a631357c40e93d15bd6b946abf1e61781b9c6e5a616fb2345a8f233140e4b28aa1cf61f347ecd5d7a7392f9a9ef64ccb31e25377d91af4fed9d88d29f89d3b2364f5eac9c0b60fff469fea3e022a4ab2a98b1ea650769f1ad4c8bc52e2ee9a4324ce8244905e9e86cb060f38c87e6a0619dbe8ade0bf4d357171d049d8751b7accee9dad445fe2efa3a99533e0d52acfab5c646e665d3ea993eb98fed9cfbe875f1c156209c2a1cd5aa330c239724a8940e8204b500603c0359994372b0f4619bb73c1eac9cd80ef3322b801ffaf23136699f62f5a19a29e3d815576b9fd16f26314438d1bdbd02d2ec9aff5f81c9c19c054311bcc3d7ee09f304f9e25a5befd63b46020733d49ab0549d51677809b5e5146dc392829bf794b81bcfda6ddfb1ee3913ee7c0ecc5efa0aa209ba40ca9941cf69eeba7b763bf0676c07cf8c36e0cdd4023eb69a65a05f10167b0f7ae783e527eb36f036d8728316fc64a71b7ece5288b71dcd0f52d7c9bfbcc14e523906a828a5eadfc4cffe19470d22be7638cdde0ee7192de19ba5052e335ac0334b1b3c66b48367861678cc6a03cf9c9c008bd9720353d8070dea704163d329dbbcab4e6efc49a5a13d4032053a44acb3e83c14062a0448e2947b81f31463cb738cda763f84628e4cc717742752326a80727de34cb63a3a6ea016e86144b7da94b193a16137476b06551b623476d8b1c2e0043520082317fd2e67cb269638e4fc78cbbe406893821247e9dee2702c52d0b17e0dbfcab3d4b50f41fd02aeea0a8dae048f41646a628d04288f6cf11fa5ac36bd1667d81456eef0d6eb9862096370e475f3f0965e0589de29e8b44e2df0d28cdd931de2a7bb92a3852bcd4230bd8fc5461cbfeb21b05cb46542a919b2aac8d2a3ce5d5cd0459af4e3c97e7527bcadf82ff1d233574819c0efc401893c344acc938966feae7d65539a13cc35620213720e858a3992a0010b462af84ef6e1a5c6bb62d7dce2290b483b2da385460d1033eae8e2ba1320a3d8971bbb7dda3929b8eef85c740ba852fbf32818024759e8b8561aef408466289108972b3084d892bf745b62df80bee1ed9bf86785f3d6e75f64f15b2dfa078715593dcb1121df0b780d9e2c5802099d8245a744a65a08ac532ce1e3277bd7002f17ff14cb0acdb501b9aaab43d7e49172290bd6561d16adf57d60d585ef9579abf365171c0a0bec7bb5cc7b086eca98101addf7a063a27342d4433b3f4f88e72140338c8991b3112b4cd014f1c85675be32382694bd0f66c0e38740d11b28a5a1ce3c37bba16dc15cf57550bd05980f5035e3bd5eaf5d733af116738b90d37f23c139c08da1dd6262de8867a6b4ec3c0da6c8d055821ee6927732838a471b668ff35975f98d7af1f7c135d3d00079bdf5fa9cb9dd30428d22cfe0872adaadcce3ad10e04681d3590633b73510ace6638df06fa0167c3e80d5e86e2ab77a48e2812da2633954261e044d405b2ce2efb76931e03087f433d58a52ccb769d94d4f03f422fc15503935f2557f4f04c2c9760d18a164191cfa04c64366c9ff11363704111f3ebfbc7d70378ae644fe5d6c1c11c4abd7715ede3ecbb6991b89511db0d59093b6d54045c097e9915162e66a226e3048dd9e3af6cbd996c0bb70b785104ec142e940431c57ddf8dcc071644cedc01ac95a1dedcc742c08c7fe9f39e9bbb726258f071fcb7589e293d8c578990dc1e68c23044afddc8ddbb6110e60e52fc627b6d8cd4473e5aaa25f5af06c8f88813c60ffc9f89b6d5226220ad877ead741bf7d3bd6b7dfe4fe199d3cde4d68ecb56f7757821f485a7520f46e9e05217ca90a39d05034afe3d908d11931810127e7bffbe8fdaf01f8c2e087652d47c0cd3504b32b3a5865da510ec5654a8f796770397974e49024e4948068767af51ecd2fdb470dbf7935580537113a547770a34c8467a4097e9c8182f373e07c1fa11e4fee9900e81e56d6501569c4d9ce627868fd576570dcf4b978ad38fdaff70f491c5954965621795c7586d1e527f4394ec1c0e85bf1b674dbbe4e94de86dc0beca021205b16c991905d6a7e4462e8a8b197e17739dad4f16f637ffeaae6b891bd626ce742f3b8cdad2ee36a46845521610144502097866ed9ae0cc31e0e670f86670f83b107c781f603dac368ae974de0787a078304fa4d3a0d5e61ec5976ca1a18f8bf98c56abb5a73de5f6f69e1e9a117fee257132a4b1b971e735ad9974cede204664a1bfd2862ed34ee053a2d7c5df4d35f9b18cc2adb3263409a4bf46853086ea20376598b278e6d990314ca3cd2327c156fd9655b205117092189e2217d7d2ab8e2b35d07afb2b9bf82cfaca22e36ecad9cad0e323f4fd28866963777f607e62589b5989cd1c90ebd50f20be71e32cf5ac494cc7ecd48cc79a618eaffe4dd376a8c4ba4f7ca37452a990cfb2e62c351b44bc1ad77bf7f914a972827091d5f4724637f14f50db4aa5bb62d4fafca45402a8f26d224088415b967855878745372c5722b0e40c4870c8205f65758973fe73c181fcb40b05b38f66795ec5e61ddc89cb5c97331908f879ccd0e4430f87c8a074cb2f883cb31b42d2002a74e67a1d755c714ec215f931323916f70bcadb7ba85867601f5ef2dd773c90102260b06f3644ee0be19b2abd7c08010cb9a7b68c6144dcd8e4b2bf7a608a626c79dbcd4225a797022b8e7c8dc2b9f8be2799c9985feb2eda80c264893e4d86bf0461cee331b044d2bc20e76a45b90fec767e09d90c95af8189362073c14303152a8e962e16158ab48c7eba3420648dba0027530ec800ce20858e0d8efc347385472df40a191aa1c437294daf02b5239f25a314d096b372f30dd56bda5abc9085022e384042c6f4bc7f5a6a6200016606679fba2e1d97394cd80b82d115ff9127443a0f90a705f08476fb1cf0dd735fe9f901811d30dd29efe418c2a92cce93262bbcbd9a0bf7e99611a53cdaacb642f424471b21d65286465adca4936d6937c3d0d4533eb3f7a27f1d0d74cfe43283766adaa048d4bf74faefc32b58deaa46b716a3edb43422f2b5e35f7e306e2ca96ca199415c84a1907d7502889574f18a3701b240efd8730e5d205efe8a93e6a6e3148626d5c3dfb1a8786d850fe076e97f4733421c510e197ff5d364a284d109c7bb67d7a7b7062dd32ef221405a6069cacc17f6c2e9aa7b5100fd09b56de17cc7da7fca342a831b8de7e079b5828296777c04bf01370bcb4075fa0fd80c1c7c952e253f1a52b3522624f2a12000c5b1e64d28cc518be37231e8e82aea1d4ad46a7600d635269f4bbe0333d32ea3b941c1311ee5f1056aa38e5d3dba41ab8647e0bc60f454bd27c8a3c968b75f47ec1022fbb80e66670ae8fde045dc817614b92639a65aa5cdbb07e822026395686ed1ee7566a8e10d2c55fe3a97d94b6201d1474b95956a30990bb5e54be8d743eab2b451ace799fd84eda449892198513bd92b4406e621da3c476b4bf6181233b2a6ef68afdbdb59af149d553aee48baee535e11b06af93a432baae01ec5b25707ff2612a250deecab183892c35c47dad5860d4e2112ccff53bd4f10c8fe2268a995433cbbc078594cf408c3161a43cedd286b5018749e34742c24730c57f0d741eb41e492babfc5a87d03224e20e131cc821f14e2a3429cf9940969a4c42cc6349f44594550df6f2e2702699e75b0b9da992cf76645122c0fa134dd3dcbe19ecb510d0e90890bce2e0a41711786e22dc0fe45a49834d57917dffe348f9042b0ec679ba061282d120c97f5d256ef8035c0ad3d6ab9550cc008169f86b4d2d46d36c2d9744fbb3ecfb961ccb72a67dfc08cb33fc00ff2d915db114959cc6a2e4734216fecc9d8745b39fa526a341b73dde08228e689ec5e6438a094f8408832c70d812349b76f42510e4d52cf9ca3575b42e29c1f1c61ca515272ec9614c788ad5df4edaa3b60940709bd7a417c8a8d3509001b924becf2ff08d8730819e730da639aa831afe50227e1db61680806c013ca65150a3b35c0b791712c76ba0b96603eb9807be6b68400f5b51c49db0799a62dc3036e3ce021c2d07118f702c0aad6bca69b2100b5320452ff89cc7ad7e2c49d1fbc865d8cdc0e5291679604a7886120ad08f808a644ff39f614e946e93f2f8b4f64c3d632220d92964ff1b73341cfead3ee746db136aa6c3848635e285a4657aeb27f912560bd06cbd1a179600cf25b2e8b76c7eb5cbe399cd5fd81c2fbef71c50926e3a402e90d4b2be9a4feba63ad61d195f07c21f62005ca0b986a1eb01a0ab7c88e7cd6d0b2da1245f87e60a730a0ce073e2ca53e510e8790347997143890048f00c8b7407c53adf6e049bec437d269f4f4e11145c8a27e11d92c2c24da6ca9c50fb24850711fa66194b2a69937af81bfdffdf786281bbbb0aca6850874b9eb6e2d774b08d5b69eff1ab91bc28bf2376f43d2635f139398a9e0856fc82a8c577a52ff8337ed9aafd6c4e9e89735a16919a06bb25af432cfee26fca0cc6fffa341348661c171909b81a3fce15b3b0c60f75b69f8735a489cf33796666af54a7a8a1a0054afc3b2dc0203115e80a67c5b8ce1f8006502b92d0445662d2202dc2d124c3ab0fcab79b72559a59b33254682402d0066f11ac4383207e4fca6b84528581a05211b4e081502ab4a63e094711e2599d4f5088c4ee75bcd9a695a277bc0344f66a8b6ca2443a52eb0758c3c0e2be28de29a50b5b3b4020126f42587452a444472872e2f94fe3c9d2272d35e76bca32beab3b17f10405a0a49b3fffa1016a0a0462c218b587d913088ebe3803e03c24f370fbaeb2a00dec2c95a270e59f27e10dbda2e0b03ee5979e2f2bd4555800cf0516f972d835d98f6cae427ddd7099e0b226bee3b372f652fcdb7ada35f67ed8625ada0482bb6269acd14aae260d00f88fa3098fece7c38f7e75bc6b9fe2a43fa92e3e4a63c6e66d719338e0568d46c0a306f5ecf8dcef552abb45d74c15acf5e574b6c2affacc698b627a736da32893de69d26cd6f5e7d7b73dd13b1bf507099b1a8d81318e9f245683d40af4298be05dd94430227e4e9234786433dd9f61f04f32bb17fbb1048e2acd0198490adc4932cb40edd0d9190ce67476d62708c7d4c93010c943c15769a9c4ed6192b24ef2d357413654f7900fc8f9da465d0c1302600081648af35504a7ba4c044fcf6f468f95cce91a6235639c7ca6ade25fe1ca0b33557d8bf187a3838a80be92cdc2f7ae14fe2b2cd097855d63f40ddda41758f270e3b345d43cf1b13eca1cea6acfc9f1475cbac0e8dfc78eae4a8be52936e18f05874d20a2c4136dd228b17e1ff9cfff8f831e487a33b003f214ad0f35515a325096817e79d0c09df2536404ebd1c18476df269c1cc2c35a4b1645f8134cdc281cf4a5c770df66f890fbffc457d5a0880a2da01e44594aa82aa26c0b9830e900016822645aca7268f440b4fb3c58c9eb828b1ee676e61c0386e2cc105c1e2cd7691d861805d8b1b38649281323266789fc336d485fe922af0096076cad65cd171971ac18026d901dc102134fb5ae71ac72312916b11aac6c707dd0d2871a6911861262f8b036ab29042d1762f133d8031aabb3568b1ae12b187a0c8d81760e6178cd6040b7efce5adc4dd662fbcc1b2d4350500d158f22a5fcf1609f9285e344c9ddec7f731de765e8772dbf1a29009821a5f2c6978be3699cdc94eb1db336025b0df0b67a9cadab22a3b544026fecd4253d5bd6bd1d4bbc534c8bc848da3f1d4ba5db507cb209bffdf9285635e98121e253069ad661292e3fdb9e9902f673dd41090b6fa846c31f70d84e752d0149e8135b785f1cec21744e89082a2522fa66f9b02fb80f017e58454b87b28122595fa27fb6c22d67330a573c3480a2f08faad66730baba431cca19833d862e23f1da84cdd9ddc9416dfe141f7132256551e9c2311740d10bd12c3391652a982585740dc6c3db38a18a3eb2092ae157b0ee8974ca28e40a0b5700d0f2592d5913308853ce6308d5a027b8d14e6dd0e7c5f104a8742fb01c94b086459e85155f22e80ac6d9b5acaca6647048805b2a1ae17b61dff124ed86f897b10d80a00de1ad710264b1a4d3217ecf9e6f1e2ad3ab62cef2c5de0aa07236b3612bd351c4850aaf4943cf420bd464a114eb9764b90ba6a590eb326859d68039191153c82aa572ea1e6737c5560ce222eac736149f64959c44bd80d3b6801be6848244d32a7801605370f64e718bbb90396213ea4ffac46811163e9365379fe2f4ca096a6e3f82bd1458e7aafb48b5235936e7e15f1a85641d15fb68844f4ed43194b963090a7e46e3058d53c337d4b83f35de6aebd5167db41a9b9ee7fd4768d2dc1951a842ee54680d4377fe3c7ab3d3334da818aa6a4a9285ce36ec87fb6eaca23ae86ec956e4f3e07908187009e715746e6a58c66ebd198aadc5320ae2be8e8292d0aaf0bf2736410222adcbccc518c71506f8096ef1e0160f558016f0e5d5401b88730412c8bebacada68e96a20d609b8b8506445ae397d9299def13b4c1735701f1537ba040620b310cd329359a68259d29b769365a527fcda20a7c1e5f60e59c42af66ae266d352dcbd8c37cabd74defbeace9e1cc3c1b6ae1c7eb5e2fb716776331a1d751907401b3e666d5aba5b80254d43051790bd24839ea526b0491884424209fda3bb475c5625143c14f77ce3cd96e31d0afa7fa3c291e8ca593a6af1814311a7e60e441289677fdcab1f505d90ceb328b41e47c5dcfae725e123e38d70eeeee28877ca313deff441d9345654cb941b260b963d516879b5d125dc3c02067b248582080c4b5002770155fdce47debe6765548d9132f2daa00378645482664415151a723092e6cd07542b5b18fca0e16b831613316dc0d5dbab7abc26604edcea05aaccf3725d0e2166ed7f5eba3c4efa512ee1075264fec06d91ae3629345170b31f6556c897924c69963b605a0bb92c839e65ad3013cc348fe2a3ec649eff2c98daa1d1b6f4db2c68890706c8b7d6d0850c2975d7dd8bb8683039f079118583631c483c77560d6b9fab83cacce7b76705e856527b71630670b2b485309211e81197b6e3a69960f47cb5cfb6c4be3b7ea1cc5a086864023c3a35a74e832ab4df20ef9ae243cfe79cc7f73135b59fc1bede16fceac778c9b693c1a72c369c3610ef4672b599a031a7c5772d66a396419d942f0f587b1ec84ecdc65152d3217fbc050cce1617be016a642a973d8be821825fe21f967adf8c1b280b55fc781db11593ec6257b6764b5f71e1e6425a9c387a2f194820d883f06d703bcdb8a719fff7a2925b5a52ad27d3f5fda549a41b84df6da763937c9e3ea8f3abd448473f2526fac2517d378ce68538d2853748f542df0d1903b3ca74915d732c770b11b715f48e8b9e349a4e394e6a1c8864de5ca036397e667e37d146723c90ff42bebbd11538dc1b72600a628f2d39d41cd5e7e003168599b8f47a0b840a781ce50923463da24b8a04076578ec37e63094a99274f491bdfa906eb4a7f7e07453ec6b8c88aa655e541ead6247185bc02113af995bf0150f1796b72fd1cc3e0b2f77a2b47fb343a254e0b69054ffad6dc0458b7a76d20421a90b26947ec0246444d7a8679ddefbdff540a9b9452f3a7064d04583f60cf1bf922d45ef66a4e58eb4e7cf9c513ea3efa682492a0548f38932862256e3b41c2c19661ad46321a1839cd6424b11a165c34a81bcea55c52958324c715a62a42c04aee9642430ad9fa3b29e4597b6074eeebabfa3b0e496f11909f9786ab26d6561a0b7c61a03a8890d7866366bcd28ad7c072646dcd90c979791f72084229a85aded92126f1f04e8efde134192152cd74e742c150da56b2ea362a08de78de2385f007adfd1240d98c7390e761035628fe3ce24e7b766db1437dae18baee26f735c4d58e65aaef5649d186d200cab02ca0d99c8868218095aed3dcc0f25e17b0aba76568464a37fbacb472b25eb0da94ea2636976fa0f715a57282aecc1399a4cfdd32e55072b8d3954a9fc2acfec8a7309b28625101fe2c153a58cae7891e4f984286b080b0dec2ae5a493151d4843c72acc34487a81664ba37f32b569ee55e1145d26b84e78513426f517609b2d292c22efdc8ea28b9e8c2e0314aaed1043d072fff49a45bab8e954b783c191a7f0302f33d681dcd13ff43cf604716541948ad43a6857b05a726fa54fe4d266bd439ca81f530d5d54a7e28cfbc4cfa797ae939a9f9eca414dbc50ebb38b2600a347ea61a6ffaa2d264b6bfd8cf2d9b93314a1b8d3ee16941bf1cc0a543abbb31bcd446abb98e03c6fde2cb256a795858300a198f0f1cb3ca19b868f206d6cfed85404431a55fc1eb99a97322b7c1e8013a51d7e80e87fd2c031eb66f43e4d9de16185a8663a604fe41020b260627514cbe1fb8cf77fe562e74334c8591e21f73a3183a4a2f9871c90ac1e43b16c331b00a4b1725c08426972f4a84d4a6c665da5fe2a1c300aae85b8f25dfd2973ecb48e476eb1bc25b3de7c0380332a376bafc37bf4a099176ec8d56293def9f9b43d944a8d9ee0e4de2c4a28af4e74dd39296772e8b9df0e293e26f64caf69079129dd32ba6a1279a451c5a5a223dc28547e60ea85b749b29540ae8b043a73710b5e2031723b4961fe1d9702be6228d539d495c983f0f3cfdfda24ea8830cccb65d0acd89ddf6ebc914a26c74941069cabd1009d5284066d9734bfd60332bc1e97d31003b0586ad23b19af576c29fa64725276b38140e4a5f3c0425485ef757a5277d9bcf0210ba5bc7414cde64613c6e5db3bc6e2c0af087dad9e8d26bbe275bafeb46f0061574c7aa49f2815cee656cfffb832dd3b482f0370b0d352574027986ba6d8ef910cd1d4e58a5cb7d2609d6207fb52000ee5f080aae46e212f6d35e94ce1f469aa4b3e976c8e5266f6c97ba63a2c1a0085deab15caf854f4b48113b9eaad9ea31504c7a15dae0a5c7524ea015b64b1320c185c28a42bde10211196c98da77de9b25643a36629a2562bc7f425becefed12d5d71b380496312cc13131d61ff6b1f1da88b89cad78af17e37d3fc4b3af07a5aff05c68bae833367fba4075e8ec5512c743c6a97cc68a15649edec01ac8d82af12d12b961f9c0ce2a6105023cfdaae9e1f24b6b5769c007e32b88891644049fab5f7e2165dd4bce7b7f62ab9df1d34e0175ffafffe56e62d38913c2aab56556883ada12acbad68b3aec44ae5cf70627c87e088676c2ef41b6a09aba9ff061f20b78bf90cef72152df4029df920ad11b0cacee4c5dc7a3aa98fe0789fa4261e7ac77e9387116a5e9e3cc9b4321f7f9649a6daf08b73929a31b3b35df43f6893d36b9b18dc704d67973af6c4a8e54d11e05e9098dadb778ad510539da99ea28407453a53458d46ac4f183a363b5a50d45aa7d9ecf6d8dfec6a09963b820358d7342c333d08cc7b1f4e2df66794e309a29e5b9e30033f8430a4eb0c89487e92fad0c0a3f7a893a67858b22c4c0b5bd43e0cb46937c810a6588b27631f6933ea1a1ee1f34b11b82281d444046f863c79ae2ef303cb18365a6122144ea96cfb412ee43a5b5a55108270a04718748dae4e7442311ae4da1b8583f74194810c571ed0e25ae0043fa12aac88ed410f07e7311054ce7dba2e712b0ba103930e465fbd88dd43e05e78721b6c95016c22b4e4d231c7cbb96c5214e82e9571a0329a49adb303abc3a463a9f798e6745e2e5dce6929bcd7ffa5b56a341150f645468df74f05c0c6daf601d6f5f3713ce32077398803d31168ede80a7092357c94ef0a64a15d712266811f300e6ca2d300dcd6817c8cd1d400089f36f3caa86e466a008a763ef82961059e81175d3a6673253b6b0e846fc9f08af3e07ad1a5836d2f0025cdbfa18e44512be2a9ede429e437623dcb633c929430eb0e9bc36be521d0389df0882ead561e46578763ddd95b995eb03ef50ec0aa75d17c0972323e66398564114c0c225b780733270ff4f0efccb09637a80f7166d8b98cdcd04ad0ec69dac42c1b1d8d9791dc6aff3aa9d5ef5c8cb9f918b5013da4d59025f5d2f6656c0386c4e9d68e8389b77e81ae53b351824104e963c68aa0d87a708111dc5d968bb2f889779b9633da9e9dc839819828777144eb2c1d0b7d60ebdc0f855e446bc82aca7ce1973d35a16629aa465814248db3fad62d4a5b3ffc1ba8d7f2a0ae7642638ad9a5b22c227dacfb1bc082e078f44e149a9840fa917c8486fdb68eeea06a5708a805a378bf0631ec1556b3c1430a7f210969061bccb2d943b138e639e6429e283a674ec98b47ace7522ded4af89def11aa7003111522d9badc3fbafdaf8ea9eea016230f42ec776a5276f252d91376eb174d4e1998ecae7c477c48adc75d2c002926d5f21a9d513fcaf35cb9c7d6f3abf376694cb064a69535a58e56e9d02816fa2cce1bd6f0223549680e22e7c2aed350a26d4a69b1050328205b73abc80d609c90216e2f03f734383f63b99eaa47392f4d55a52fa0dd6de61beb65949233ef71171f9bf387c51241e72a124fcb9b9ffe3dca0d97ce3dfd9eea29241744d701cd389462f5b73ae226064c400f00e44c04259d737827543398d4226da9be70ff30f8e15a598fbf7f676f1173b39d6b47ea082101de817e85388dbb25b1baf04154a52eb0b2c748ec2b05869c1381f552b2c762ac7a73838c933f5af68d20c04b1d091c043d029c57c6349c87b3b181e8333501abd3b4f9ddbe8b0296976d159b9c076aa521b6978744aec88d0fc72fac05094c809f377e9209e362ba86182ab25ea5648c6f4f79180a3b09310c4a4cf64267546d93b2b9a100b482239513d87fc614df7a527ab6b6d4ba3311cef7a6968aa75dae87c4b49eccf7990c2f8e445d4233425236f094d4dfca6e904c9281d04c0b5bd66934fb1fd7baac838621d62ff27841d307cc7aa1efbe03ec3f165ea5aa30812e4f12822b129fcd5518c8d983c5a9341f52077fad0caab2f42d07a837419e868fc11f83c59025b76c7c3119a4318e67be02ed141ed326d6db25223b7e4795fe913cd340b76df777caa4e95c952107c6d2dad88f28f486be23e94fdb50a42739cd5639c89ed80f7dcf9f709c4571272dddbee2d7fc0ad02fd58d80a871cd76687414def08cc049349a98dc2d20187b3d0d5184c9df89dffcf4a6556659e97572a74877e32741d080db130b780c6e7e0c5bf6eeb3322cfb9ffd67ef08990f2b80d24d22aa7c42b8d805eb5c4b5c5fdd5515d29b2b83b751aa567145ad7e24e5f0d8b9f91e4ac9e202cdd2c35466be9ce0da09048d3e0c3fd200f15c3590023d67c6fc73c60cc159d278138de92af0ba8071a8cc925a8382b6c2d0a5b4e918f46df41c22175f7e36ce61ddebe94cc1ffd9c9252ca0527f11d5dac1311bf8d56f8042a394c6774fdf1c4e40f13b04ad7853228bc9e7b9066a64ed90912ade7586242c8ce14ce19c028ba9e8755c90055d6bd93beb54aed5a9684f87a020d397e3f5a690206d283a17c4b26e895efddee6789b61c66f0f570583f71957d469236a2b1ec51081efa3073f46d1aadeb2411a0e99c209c48d453411d63c269f68d86c2806cdca410ca01f5221e2c333bca5c566288cdb5cd5c1f431364eae3718bc1d40279171b201172283f6b9281983eb45a305606d5b56cc1b8208d687ef9fd56627dabdd11b6cf5c456e4ee245028c7b58ed9221039a6ebf83a139743f1444d44b8d168b95bd011def9af51be144be3dfb3f8d4bf9beaaa97592ac37ba1800c5a5e24fae2be27a1d4fecebb78d38efe5f7ea15a913919ff43932cd6580eb54bc81103c1312d5f661276828601bce0ef47f272a5f70673f12a7f1cd0d866f43f0be2819c1e54c7d1fa9fa146e8c3965f1f52fddae0b383b0c623b739084ac871bdad0594d26dd3226ef4af0121c51645b9eccc97cd7fa3a5400ba2a9ff9c01e06741ec3378c24d70c752ef4ae7f56d5c1c69b07128f18f09ff2d2550156c378bba4dea111c550544e66ae2251f7300149a32d2d8c6720ded88a6034064c52284262b815776868e29882fd1ec42dc9215244a0c1b72243a38634bad4c5ad082f65d81c05a4032aaeeb20b28091b08d6a933cb837829c212af64e210c84763b405d189e01cf6f26a7d39083547802994cc76d01bd28ebfadf26bdb63bb35f6f8af6847681c636ac05a8f62272a36b6f27175c1f36c2e7bc31e8e6f185b36934503006bb960ae7f772ff9c03e8b3075e20456471aabb8174163de8f429e04aba8f8b28d175608b91016b7426649ca4109f3f69d672af37f790567f4ab26666aff67f2e7de4777fb670bec531f302d0c8c04b61499b8bc1522d20cdeaa6f9c51e85a851f043675973eaff8342b0685ff7f7e36e92ce7de24983a2e5367509cfa2754ba02001360e7ee430d320463f6c11d09ccd1ef419f50d38d2d3e8e4de9e71abd2dc9a63dc16551159bd9d3093be409b7bf7ad0bee632fd1e13efd27d7da4e243396f73dfabdfab49779efb2344e2d667f03f0a45f7c6366b667837a1e2791437d9ab86aa394326c4bbe9ec91dfbdb36824cad12b7701bf7f16a41009f7def545dc526c32fc6cd50cc2ff74da20af8888416cb8d5974ac64b7a1a82be0720162da3fd3f115812544c14090f68796186a176430034ee19187176e6f7ad377c0012c06133484150f40ad9efcbdbaac6fc2917ef7e023ad0009c6c380172a476dc241d2067dc2f90c1bec59e08238453e3bba4b0d621e1532d19a62506d006bf996c9b133bdbbb8f7d0ac16d63da69b9fd6c68b5d20e04f20b9776cc440fcb79fcdb7d5394c1dff9465da1f26dd6c67ea70f14d5cf6b13407576e34b326f117c5ae82d7c2894a6db0a398be0f172bccb4ed52e9d9b6eb83c69e01bedf6fe73816deeab21ff602eaaee512512f54be744f97d64654ab2b05dced1b2884ee19a8db1df65c9de5e6f522c1af36c8f64ac104272644bf15b0be68e549d53100d081f57708d0c7cc2be718c49fdc6df283cc5cddacfdcadc615497f5cd82a6ad42286de3c308a20e692a9d0c384654883da746b778dcb2a036a40e66a127bc65e3ceb4b9b43b04859bd9655453bda5d4cad952758f7ec48138168551707ddad4344e07c9b3ec66d158c7c0bd836f4c8da3f636c0c48c7e4af707442cc18fba901885c4ea0f1c91640bca41ab17ed655c01b24af953e3986758454c2c22685d65efb86c6a2e1d5645d560dbaaa73936dc565ef6a54130b2f63f40f8001423766fe12b349cf1ba72f15c819409bbcc488c32587c6d024455bb6781a232ac54b09501f603d2883bef591ccf72f3ebc64674a59ffd278cfe4c7eb142b03781463e950426cb0ea76aa6aad8015620a5f8d1eb6c71c2212b66524e0be6e4f86c825dd312d59126bc7123800a94f8f158b7ea5d3fb930f2f581e82db205bd62b9a4297ac59017ce87d64ead51177e5a2572d355241119566b12479e79b00bb455c55f17fa42bc83e284146661df197e78133ecc122dabe4dd88dd1659909a2e37ebf41b2bb427116246b2507a5cbd637a6e993fc09987f346d58bfb45039535cfd487b591c54bfdf8301beaec17bc8329b4227d3f03a4b49fb408530ae0ccee7a931a9ca07693970eeb8402d3861a56a48b9f85a15d40d0592a9dae84cdb93872c4ffa028a13f81b948844975410a4da85805f861f93656e025e5da1005b15087d667fb947754887c90dfe6aeef15aeef79544c155efcaa35323ed92cea4396b9841f7f2e6a626807517a69ea4be192715caf59af4711f5299051455fa6ad27f82254a8a8d6bf1af6662f747551ef34a5a8e4ad1138839967597868bded57a254fbcfa69d4178334c9188ef076990a4e12d0dbd5e7919006b8c200f8600179914fb204095abc09cd850fd52859ed3a0b01d99dd72aad90b3eb8f52b8cae5ade29438f2f964fd0702574ff546383df8fbd4b3b25fd4d3066fabd3f1dc1a1cb174fef2bcea5b91cbb366d28868edaf6254cdfbd5d6aa179f5ee69609378bf77f1ebd1b6b243ab40d00adc59be951c0105d0bf2bcf5a3262681f56c227063570124df775c8b92b48f54757be376799fdc3222912938808ca75c6f79fc898f8b1a5c2004abce27395f98ab802857ccfe6cdb23e8b4cdb72b3eb2d2127a65f624e8e9c412b416399d19d3aff98b985abe1d2cf4d912174a51e1665ffab097ca4444602cca6e8f321304d54ff7253994147d3cee608a7458e807ffd6e47a1d146222fa394e9e2326fd0214c5a866f9ada948ec269ecb7e7f29c93c9199f13b0e4994025ec86d174cca80cabdc335015d0560469e9069b61642501bc2d81b24f3313253f3da0dcc64a23cb2e2bb937a6a19605ad7d4cc0bdb5a2924a020099627709c172529b155fa6c6bbbb68468e9b081b97368667cfa519bdf9e9ba999548cbf799973ae52ec485bd982161937f0d3e02bdb57a9e7f2240a4bdc339057e29acf3e70574c212797b19cb1c663c796d7114b926f9c885bdc352c38b875f59c92872b302fcfe6ea19d56516d2a1c15fce6d307058dd79f525a60a2c6620e8925e3d144b74c4e12c9dc045e924760bae39d3e33fe1ba2dd72a973e58fc6e2148094381a85557d4a9ae7cdeb520ed4d34e2d189899ace2272b3bcf89b8e01febfb597c8432e643628af8ed39d84b30416f16372a6381970c5cd8b55fae5004ac7f68a1257c411ff10956691a96c3d786a2de5c446fbac1054458c4eb444745a144ff36cbe5980c9dac0014919457c59e6455b7c683cd3f9ac5b0ed740bbba923decf82425fdb76ae3644814878dc98c9e5f84b5750c78adb3ef82111c957230e2760c250cacef056183444b3045f0125fb6cc49223b8e9aa23c3683ca8e7eccbae955b6589048fff3965748dbb2abfa48d4a667979bfe4788d7cce7c9dfe0eeab8c7c88ac5ad61afff8f2f890c9f35a6781585c2b9c753c9fac92b7cf1d4623d736f356722af7ae1c2108e3e6d9c7afac9f02aa480d722f90e142f2e8b4976a699c775c1a76a6fa808f1450c9f0c9c49b4082ca586d33bdaa102cb96bea628c511c7542a2ff856bb74235dd4f51d833f00cca2d3c30c98983071bc8c30618c7be65dec3c0160e4748f6c72b2976f05182ca1ed9e771a3cd8c86faabbacff263cc4cebd523c6aab0146564cb905ca9d427b289c031a410adea0b37f4c5557afd4e0dd75e5bf6e8e7fffdbfbe2060062dbd8cb55cd72b6b21b4d6e2eb4e3ecc4b77df77e83836acb38a25d48d2a5afffc4e4d51daf6c478a2e4c49f7663d537b60e158818be8d45d54106dcb6ba76019739e1bdb34d6754f5884fac1e0abef69423c1bc8c5e875edf2a29127045e557ebc8e8680978e9c6e2d244b31b1ce5c56a7657737dc278a7f83943d4b5938d5c3766340c4d7ec3d947db261dd2ddcc5453e9614bd16ad087ddf99f37ee8f9f87fbe13a67ca4612f58030892134ad03c908f328268b66d9662b620e8161f384fff3ef1917201fde85c12205244cd846f4cb1b178552558e7f7b326a83a5dc9e2179ace334963a2310487b32132b27f55aaf074bcb094cecd6f168012427e2a086829245094b563cf650c88ff12a22e583efd281bac8855191659d148bb043c95548bbf9331ef4b4b9375c9c9f3252d62349d33d47850b704eb589be56977c3401abe69e812764470558169355c7f9bd83f842134d028feb1029519978789203a67f683871d31aaf90a104cefa5dc5ef469f81cb2dcc2095316ae4007f541f6bcdc506e389f2d587bac245045a92b3278882b3cefbc656e0bd27db99b47d9b1da08f43a33f3089423d14b7695e76c5b53dab24172a628a7e5a8b6e7222fccb3ab9ea1323ca9c2fcb82171d7825c2e0ac60fff308c5756f819c32abb1f9f3487d37e5e3539f5a2be99cf683c6d39fb3f5c818a0c312b0f80701b9a5537ae2461dc3e58a9641a2b301f8523f054f927fb94abc7c49ec3f020e5088e50eb45402a64eab295c6cac2f8bfaafd8d3d24131edb74f2906b3a2cb1933da99bcf10bda6d5f70f2138222c7c396915228a60b52227f3eaa84e3a97ea970278796b917a0179acd9ccd3570999472192409f1c102a03484c5872f0005a0e90cb1d7b0a6b6df14fdf938524c3be6a38f25b8b3159e87e8810ff31e9e57c62a8377893b3a785b76416fe7f41f7dbb2206ff22d73c31c003cc273f9e87ea74a67c38f49e75ce9a1149efcfa72749c231a5e3113a8b2caee97472040f6d08fe88a6e1a87d5414fce80297430dd7a2d894a3fcb1b1fed0a73918f20313f8c3339891a3995b938670fd312ebb6ac810662ae038136f0e5a4df6a7e781ab425d449a5f0797e4d36c216f39f817d0ea80a3395edfa2e4b6a40b282934ab0279f75a40ee282519a743dc2424a2cfb3164581b28e62671400504549af752b3dee58719ae0a701d4306039454bdbc0a00c20e682dced10a62d67327ab72c960982ff1f35421fb4c040618dc9cfa29228fce18a4495d4195efbb10cc278a2d80ee5793353a7b31dd33b30c84a4678d5bf36bc084351cdba1f6ddfd65521306904d35a7bd7cd363a6ed2270b00273f70a170baf7e894b39d4239d214d39d5c3c29ccf3385acb2bcde71fc000f524363b4742981eb36d3528fcf8947631e8ba26c476c92aa4c53e049e81524a11288b2a8bad9c14250e7ef1c238b1862cd3890df6593c3092dcdb0047a1431a7e6010bc4ca9648f1c5509667975918d9a95e8df66437ae995ea319da26a5820f4dd853d55f612a8f30df7ebd7d4de447686b46f4253a6f824350ba7709a03053656d8113f1d241918e1e146eaacd0b52a8c4aab62cf9ea6544995fbeaaac186f44cc501c6bb11f9d1480161340c36b6456a1697b2edf5e374e343680f24b60ef944de98e4bb5cfb1ddd7d761b416ff0a76ef05ee907e3e8da75d4b87c307ff0dee1c0491777d4a038599cb01da42efbcd9ccacb1104070ebef5e2a32f17850c28e095dfcdf54d1ab6c72e2792d787d3a267ec711437e4b83c99eb5afdcb0c4ae54db242f8f716e93ef6b40bf4a401fa860ad0ae5bfcd174bc00526b579f63cd38ce245d4f4b7736362e5b170b52a86ddbaede480826b3b188e6a352beb36e67b3176d9f6f82e3c3c4fd33146350ca00fd798962f3a12a332932ad30cb6d4062a4c211782e6e89377d29cf4f2d4fe81e5cdbc3c85a8a604b6a5e9e91998456a2ad44dcd3afe415f0b452de921151fd12d7f46e4d2552cd144a35429b226a40d84278dd5e312762efd6cbbd7fce5f4cc0c8c8b71c4f6aca6a64b9ae70db78d58d1c110ea390973fab6e29fbfc0ba9f949a34876a63ce40ae913e47dc4810366014d4112a7cea6fd2e3639834064ff94686c81f3ad81f004c0cf0dab9c62d29b876aef0572f67c1559f78b72f9cd416bbc6716405afaaf7ff92041def8e15bede77b98868f0a15ba5ef80378ada40a9d06135f653bf89c0bbb3737cfd6582fedac1e053b3daad0799b0f4460b451bda4c9f3e2c6e014a0516ceb1f16d7d53066b572926be5fd433185975c4c7e91b6ee9c3ff8bd2203facde85a561c7f65cc496c95b741121817b539373694f3e04555806464372f6d6633751e965b3c2221458b64c7e362822e4438b68e0dcca1fd0380f15d1f79213294daeb5d7876ae85acfd303be9fccaf3afc4f568b520422355e2a075c82191f6843ac1e32647855c978e5c368cc624c423cd13835df9f2d8034f9ccbcf6ac272b80897ce6239ae4bc0e0c4e5c3754fcdef2b44196e41d0a27b24943e7569a9511fa18a945bdf28f2f17605ba7019448bb7e8ef36b4228a488bd0546b7bb7d79bc5e30160b1fc8b13da2baa7dc002f03b01609df64ab8e4b921c779120e00568f52bb56b94384be60dfab1525b559404999bc64c6aadd4f7aee56cd9241f7d2868fd895964e292e63421a327bddc99ac7661373ba514f555b9eee6b5c59d21a83188bebc625b6a5acbd7c5ba468c00101ea7cbe255e3d201310b7c40dbda42bac2ac222d18d38e4d030eab247fd3c0e4a43ee37d709bf385ab366dee2d4013c4a73d99227ae151aa140574736bae551b2acec018425cf6470c4aeeae901ff6d7436258be60e41a22964160f040cd195947050ab3b177cdf724482f05f978be3a48cc68f0ce20ead0bbd178c14f98d3eaafe893d4b733cdbb43f1867468fd89e342fe7f0d850d4eed69ccfcaeec9d701489acd30826ffc824633f22e8d4c8c7590c81feb35f9e5681c663e88826fb7557ff4d4066f692f2e5ac8bce0db08c8d10cc492da0745a4e1c9595163f34d4e06f1d77c036b47c3fe3b908692037156616547339d8dd9c97d73e2ebe0037722a7921d37ac7472a90a08285f8e453a6c0e32d0ea4ba22558c16fbc0f49cb079ad4518c4efb6f785a79874b25bcded7b5fbfd772939856212de3570d652f15a1015af12c96bc53bcc512bdf4f65523249a5175a96fd503b0c6df7239fd94dfe03aff7e3ac0b9b1416e886b84ae7b00580a4ec9c9fc8764533d3253e97cc118acba101c75e2bf374f31cf333c3c51528ef39e9631f81eb429f9cc01bfb2ebe2189af14c6a6942340e083375afd44a7eb097ef75da62fd854ef837930cc951307ccd4b63d01d58aa5161c95b05cbcdbd5655f08fd99127435245f073ff87d239dc7bdda04ab0de7ee3a7987a87fcb29d4435807aa14044a101a68e5512fb800a70bf49e1883dadd2ce2eb3f4fa0c7907afa2dc4475ec4a614413e6184fd2bc08b7155065b24a8087c606a5fe026859780d48328ef5e2f38c04ae98bd31e9665ad4a835c37ab08f39f5ac7e679008fa7c7f308333ef0597b22d89fcceac909a35c5faebc522294dc40ead62f74243f4b7d9e17e04637dda8437998251528fbcf639370afc7be966c880cb505841e0890a32ed0aa25106e3c4586e68a4a142ecbf60edf07a96311b44966fcc5cb5785c66693490b583bfd2083d26db7845504dc2fd8a5089b8e454f04cf6730612bb5020e9f032d9b44e34f7e4d13c702d2f1ce54b83501c563099f9aa6d4a63ca04caae6950e802cf5d051944d6227ff092bc9724cdac6c29edde2822875178aa3cbfed2fa8571c781c0aa0e6a06f3002092086101190d67036efcd75033ce962be7684daf41218235da4c42f4ee70047c3a448e57fde68ada076407f97ad6d26eb25f93067370954e9bc78602519807ac6db5511a7726bf70cfc1291aadfecdc75d17378c90119445e98962b6299b2a8fe5b681f6dadac26bd21724ca1697dd4cf4538b675442898e7e3716d73c0d8eb631d941eceab6a6c0695856637fc339892afb9b60aeca5e98dc0b3119b4f258c753ce644806ae41cfb889878c2b1d0adae2aa00d66cc07d425bb9228a3ea9c02f8b79e311fb57207bd5151dc80ae2ac63666ce93cccc40657d346dfa5c325d94ca4afab6cf1c3ec1aa83dd067d252a9092502eb98d33c76bc7293c79ab95855d9f32a7462be5f83a461e8ab2074bac2f7bf87f77bf9e6df36a99f239107d3f8205289869235ca71fb7b170e719d77d9e2a53f993fac50d9af52c62de3f1f712c2a55775c145cd61218bfec17ffd2420650024dfba9f1e1938c76098668676c447ec0a944ed8d63070b606f895a7d20a513c145b70781b4d4e6edceab811ca77856da8e2562cfae24716d59759445ac152b831e66958a7ab6d370c4a4a5a3974479bfae52b4944d69f763287000fc5cab3c0ec0048e656df83b27c06c07561a59b9c55e71bfb90acc287d4ac58a86395a59438499b2a9aa6e007f7b40625c565b9b0f4fa271c4a96541c2de6d8ac9c045a96f6a14464b9a49c13753f20ff1e021160a5af8677336b97e7eeaca1c4735e1d50f8aa247d9fe13a876b447ac8562d4b356600d201061b54bce2a1a165487ca686193edef52cf1ce4923052ed7aed4d276aacf80e5e0c6456142cff0fe749cde801db49de92c8813a4bb975ed811271c4875ab215de34cbdad04d6c0c101152b37c9f3ff1e6bff40506afcf47fecbf9c0973843ccf03e073270c5b340c3c311224c5753c2eef8e8585c66b508bf418dc7d9728dee42d6f95f9832bfcf9252f3766b505a66e2ec0e08c8677c0d5876d984a4d023f89401b4784fbf558dd3da0902280ba9f8421bb300d027140ef614c7b6a8e23785883bfc8d2e7979e62bcd40c11200793cd69b0618e663654f487379d33e14553079f242917894c1971ac3461fc5bf6665df9c46d618e379a420e5a0fd65b1ae9bc6f98d5ef6e0545cad8f5917cd8d647fda3615cbb5300bc27e390d74801193b426b1180352d74600304ae28674a4903a3a859e22c8cea43984395617133fe589c7ecaa13d8c633f98d4920fe52341f492f2cd83e300e3f07ce2c49260de08d51868bc4e295cb1251563aea6b9e5c214b97bea9eb88e051e4b4a5be662743bdedefe5182b0670e43b9868eb96bd535d9a33b407741de08dea1cd9f1199fe2bd9825028b396288825058510075d4370427fe60c094bb6500e69ea41aeee3a64f9f8a5af73bdb8ee006b49dd38170235fe6b20e8635e000fee3e2a8e7873defe7341b589a5ea80504c8aa077ae01a8d5333582df4848819a51da1e5b920e4960b177e69fc27688654a7d38f9b98c73bfbacb3306eeef0962d69b01f8a00bd52220b9360a5e053f8926441b417340523d41c7e4ad598f72fb3eba3b52dd340baadc3d3d8bcf1417c9f7ca88421f6c283a2000e46f8545a45faa733a0ff913f407c77e9873258c9bb1da1bbb0a0f53ae0f5f7c7e0fd2396c730266bd58e6dc9a6d814c82b9b852bf2808481d5e20ac9b9a7bb1db58db4b62e7da462b9812fda66bad4cfee225507a9649983cacec1ae92aff1e78ebb80a22fd08fc71f33ecd22cafffb750ef5b826538e984561cec6a43624d6bcd8e6781a46d39bdae0564d68826c87cc4e956a01a41c35e99f7b95c230c96b29e663d82194305b1b35fffb4df159f8f0e6c9147ab7bf82061b8103fe32cc9a7e595fab856f36f93b624ab2a170208e10a87c7f5da43b846ac51225bd131d805e43f674e3721e083d3822dbed529bea37f2234eab19d11bb687f449b1bbd8361c3c90f302c1eb665a0d5058184284056c1a5c0023ae762922beeab4e8e9eb263ff9e65a63460904107ad0ba19de8489a45f4ea15f4917e6e440720c559034949b4d93a0bd2de7ab3e3c21dad0ae71536cb3ba4a26bedacc228aa4a4e164300d0c3dd9b66eea988c671febe5ae6a44c336c69765e0012e66588339ea01eb38da181f6d32c57017ce05c751f582622f7dc1bf2e193e5374fd927b0a651b9178cecf522d1f4eeaee3f206314493855cc30642beb093f8d9f47b221b0fe03ad0a8f7a62f5930c0c2d19c7de7faf49685e884dfcbd0976ff67718e95ab89b2d8d49987f07633546d182e3b35b1481e658a0355d42941567b787ef09e50f3fdf014ae84890af5e9d40462921565e812445b11658277a51b12eb2c8e21bed81103791695d94e8005cbbd44fce7b83d4361828f3ffacd12e8a1d45054de0aaa8e511be3e790cb44d92f9d4ae51294ad3d01fb8e3f52b080ed08f80cfb8cce78f6085922d51a283a03c53df19fe22565eb808440822acf308af578b7c40ecb5d03b7838ae6db6f20cad88a50bc2b0e496cbcb465bbeffe825f37c41e118bb9eaa86ec9156b6c1d2172a9cac4782a5861595b66376127f9afac98e1826f462bd3f4b0ac657f8f3c3590a5761897a27033eed626639473f2d61b75eee147d81b9ba54f3163f55ec9d4f686eb170ccb17ed487587102f193921254a1ca819dcc3f5c36c1aa69e0804389cdfcc1298514c8aa4c15ee6b2cfd3368399d477c444d885c42c1ebc08ac7a0fbd8f9b09fe3a9506844d9507fcb4a26759e9f551a5acfad74fe2f45082fda44324278a35b8dc39d311c68d8783e3a3d88ebd0a44a04611970002bcca9314325c65f32da9f7cff4022a1d7c860f48bc68b9a65dbfc32a9f1090461c2d7bb843573211d681e528b66a86fc7c7fa1c3089f573194aec448a3eaa699fef61423e6117f17e00185221b489913640944c47e8351ff62e0fa38df4d59af26fc1affdf31b30ea50d8b5eb555b96b96abf27363d231ab27fc2ec42b85229b66644006c9a565e55a4313794d9731da3f3032c9a573a7c1f8026187c04ff2c307da143ad52f03c07e1017ea2681763d8e4c40ced04a51409b5dd22ae8588c17a7c63063cc2fecf63ac0a4aacaab80067b4d2e9613027b6cad312669a8955d4ff01dca5521605a6def9d6adc25b66f08feeeda078a9613ec9132330441af3cfad7449424d3e798b8a2d2efeb97d183d21975104e27a1100cbd1bc97a0c1540e1468f37a3f62c87b0f803c54f54061cd31335b48b0f2cb5ff646c3045b1449fe170eac97de09f2a7278014f5f57e4ad31d58fe6d46ec2de7a5f318b65ce912f63d4e6e4245ed82501e35fc690a62fec1fa1c8d2b795805a31958ca5a3cbd0bd947bad5c4609bafc76a97317902617fcb1bf8477c3c68480571f0d221f818bf0d1f5193492636a1a14255350df48bffe435cd4c946098bbfdb8a9bdc7fb4e0953ad2fc4564725fc05f81f46025d2835605d27d9c2b60f3e7bcf7a6767da7e77f0d5890f8a5a14a9cfe7be36566f769ad25271ca63f7258340d7368e881a4affb98e0adb1a388c123615fa5ed5a512e27992a9787ba112bc2de0dac4811fad59ec70ca462dc791679d165994ff43d4a9aa46e23a3d1f4c15e360019d5b9657304929608aafb3c39f6164908ffd3284dc5a67a05a81055a9b338bbb0b388631061947d03c2a63a5a732333a49e6e44be0f508946edec63e2f41b21e4a666f08b5d7af031f31a2eee8ae27dc18575a2ca1210ccae4a658c532513238eb719422a56cd6b58e24e34f3c11982b0acbe41df6037008de8678fd2364a096c9465922a678732661582dc98048fd286f25e7c02d2e53d32babc75a787e7c7089067b0392cfca74361175b4eac624c80a2980ca53728fe9a815942be64d2b1ad4a43a2aaf39317fd4abfcee1cb5e1a972666d47d2972647ff419374157b298da8a6cb8cd7906ab927d847c4bc09ff58af9012e85694cdadbdfe888932f048c2950c3739a2bb839f9d57a4f565a45424f108793067244491f1f479d4ad968b27919f30144b6e4f481281d0b2a6a01a7136a5c3a9dbb725a289439be68504752c4ee1cab13987444c90ac3311739f567d36817238f833ed534efad1e52c06dd6f96bcec75ae6f2dfc21cc33118cd73cfadf51b1f45291dc346bf8a6037f64871f6584081da413ff084cd1be3a92ec59fff664eb81619eabe9ee7c4e8a35823072a1a79d176cfc10206472067173a09a56fbf1f400f1d6d24cf8c7b46ac209b4d07568f52330999ed33db3db6d2813a9b3cd281107c7f74a579d8638f0814cc024aeb29635d40d619a7030d3a6818c258481c978f052c63c4006764e129f2149d57fca3afd02c31cc4e1a512618709a933a82f3c1161a52eefd095b77604ee4bd4fb4d278002b6bc1e403342bba53c3bd9282fd6e61c54f92ecbde5de524a296592326006fb05ff05377b7ea5a134f3ef46bf4933df691c773f2858f192591d90c5282940df7e58bf6c9a3030e4d28499df828b8ad0610b986acf0fb30b166c4f5864e1eef92e5831c6fc21b4fcd0056b0552816ef63c79a91d57a0ce448e30c2bada2e1f471656be7bf91227b2c079cf5c4516b49772d2ea17833eeb2fa4f25d1a952fa44bd8f2670896bdca37fff3d927b6ffa7bdca47fff3d5faa1efb0fd435f7e156a3ff427db7f7b95cffee79bf7c32964fbd72df471aff2ddff7cfec3f60f7dddab7cf83f3dd8a00f29151867ddb702e20f04ea4fdb9fd776ea598514b5a310f60cdc874d4591900e51573baec0cdfb82ec910331028991d1820b305c80a7891358486f984629a57b13a318192db800034f138a0ae90d534a39403350ab0636c08a2b1931fa87f035c0810c6ce0066f31325a7099970228e07578f65c85f486b325bc100c7a5ebc90e6c30be2a7172f2bf1f284c479899f4e42e36cf9ca404de66a6490596c981863e3e4c7ed1bedf4d830607eb443830cece4c05482c8a21478cce3d8b2c3825a070ea65487768e107164c76422f498ea4a6b3d120508110e3878365a85875a7552bbd65a6bad55270650ec081d5959725211020f201d25ecd03a50c8e193c3a6e34108ce4710db0c7ec84a7ad07c1044660325ec806d20a587ab838de541446dc2074a64d75a6badd552d7aeb5d65a2badf506b05d6badb5d65a6bad3798b26bad94eee4b093838aaab0800e21a890028920181184ca64cae95286ece242dc1174f1b7213fbe832e365a82fb86943439e7f8a467cb8faf3d5d1326ca205bbedff8a43367cbda63cb89eaf1d7a495642fb617c74ccb99965b79e3481be784eb723ef4f3992e3f1b274d1ffdd12092ca0acf4a8885b2b04c972c4f56a46cf9a110cb3449174f1c88ceaae53c405c1543f18802cb69d2ca7939c9c19138640f8952491f12474bf2cc19547a74d259adbd38d3b296b7cc759feea33f209595100d85a6cb90ca4a6885a5858b162e3c8e3472c1cd10b5b8bca4f0e215f01f478a2005c1e912c4dd4b01b6fc14462fdc0c155e28e005eee2bb0162397a9c51ca9c4c4071628467271a39b283b80ddcebb16364c7c89102c4284065c4488e1029577ac78812466826ee8e3b4664a0e3899cd6ce91e226670839507262370cd18163c80f4986dc0c11328467888ea5b00c6fd8cefc117f3f342e8291e6def001afd8e576ed0cbab137bfb9f3dcecbe5f2a561d6ba53b73a41979d77799db77dcf8fba15dfbe188be8070a39ff6b97e746a1ffe3cb7cac4fc4ceb8b98e870342b9e5cc553ef29f3d45cd68b69ccba787784e135f8bd48e4e843803004e6ee3ee78acdb6f5e03255a7bde06385e8ae0828e74025e483959d1a5411b2816587169e8f40201048456548cadb9c1075425a3412974e0acf4744ac70d95a5a5a5a5c5c884c79d102f52c4d7c2a47e4e7f9e8fbbe0f045726961d77564f60acb000c0474700b22004d072c46893457060443bee144132006fc79d2204d8e457e40905d8a47e41f6a6ece4ece43031040840c01802c361051a0cb962471b20be1e3b5e65f3e20539a82044470a457878c9810e2e35f0018f961a540184c8861d5e24b2030f2e8ac02d36c618e31468bae30e11176cf94e8484f0b082e4a442440a1e4044a6d8a1895891c387c8149b0e2504d74410dbeac80f79b5831eb4d59320b215163b601d3ddc206cec1144d422383ed0224886984340ed1479c20e467030ed18e1b1eb8e3b467ad8a17e00a49418121a02083ddf5b78e8f94e4be033368a84889e30435aad7f8c31468f317a8c20cd020542a4ec50c720c7861d2151d82d76dcc9b9c186527bbe9472ae52b4da2bedc532bb9bc49996a596372e46b96d1ce7ad2dbffb42bf8951461973f7316df91a14e5fb6acb9b96ab62b0d6eab5c2915215638caf49276ccb1859b12e893274bafc86389af1e79228e3a588696aabea40b2a24cad2d2fb9cc65f9c86f88325749cb545b5e75e0f6fdab5f595589fcb9248ee4d7ee7595b84c27451ccd9ff9a402ad5db105af7e5d7b4eede54087f9e48aed70ba5a9d14736efa61e764cf5fed9a4f7b7e3ecd1918e284a94b7096695ace9b96b78de3baeec3759f8fd620908a06a9a8acac84422c9125b670e17d9eb74591688b2d2e2ff4e565ba7c71d9a44c417ea1275fbcd8e47ff4fba6cbaf65fb5e5e52889f0adf0b8ee4f23118ea2d55c09df40fa5265dc044a5543713673547f324e3ba5e3db0d82ca9542a954aa5fac0d4d39b2f2346864b05a351f90dce8ad542a1503337d01f1717f68d1db91aaf5bd2708e845f466bbf7e8efa3a068c2947aaa004658c5139120d5d9108e3d5ea56efca909171966518b0d8624cf218336e822ea4b81dbc61bffe8cc182ad96cc38127d7fca818094377d52defeee3f338e860441ff3dbd067a63ba56845c250cd6d3233992a4ce911c4f4da2db7efe647f63578e546d0686b5bee6fbe7b85f81f9fe31cc182e585f917e057f54577571b9d69034ee359cc6e7ac3169fc29a535288d7fadb5d6a834f5b3d65a5bc3d2dc7befbd352e8dc7688aa626cb610e73945cc955ecd9a0084787ba06a6f1cfdafce1609df5f347660146b002918626cb6d7235224d169b4eb0ba264cacae0923ffc6a49bcd6bfa8c4c5542652acb47f5ab3d883073a606a11547feae83edef4a98627b8b8b0e6d63feefb8c3595ff83db91a190d056bab7bfd785a2bc661924133411d982600db3fca04843016805b220b22d05b4a708610687f9728935fc49999e5ceb3ee180f63e8b6f6713031830c328bfde3477d3ab795a186d20d320b1adfd9de6e03b72c1e40fecd3874c8a0b1a009d35030aee80d396915337fe6679718f0dfaf81c11b3b52a741df9f44dfffc68edc8c3c23bbd4b7ef32ffc6b620082a48ca9b82a4bc1d8c92488e9e55c0b393dc6cf91ac798a449922731c618638c393bdc429ca48a6a05ada62c82419254121e4970ec90262921816d479284ea4262ca53a494eea82804299ffc40ca4e144a69b53c76441610960f960e2b07b39a6447c822202e5dee3ca1818e487890189943983aa4a0a3064e2461938309506832d7ce9c9822c332cbb22c0342127887750424448082b5d4344594f23c9f02c3a7c09053ce9818afa2d4c4c29aa674abedabeaa4ba746a4f7d52593ab1c64eb347ad1227c88d8eca472ae67befbd31d38d2e61881da37ca2c85ced40c5133f4d2172063f442af4d71bcbb3fdaf65e120d6b5fd33db440b9269ced365c69dbde16a2a576d0f8ed26987855928b647eaa87cdc94b5d67e7167bce19bba39db5fbb38f9eaf8f091df54c6d5d0ebba512e0c97ae94fbba4eae4be36af4eb1310158a074e10140f7caab5564d02d9fe9bcab9897170108a5198878f1c9f620cffccd5684e77a6ce4797ea6e3a20dda9dbd19972e4c1ac4dd24c29a53463167eb2fd39fcea306cfb7f30148c794014049a2e41b8c35c4dfc1c53590e8e8f8c95e964aacc4796e2b89af85a6bbd65311edbbf3b7d52db5ff72865527ce41947d2b66e69507a9ac4b4289aab898f5c6b714e7fd05fbc5aae5fac2c5bdcf68bf58bfb657e727bcd40b5cef65761057a11e6d6f67f1ef97d407ce459a5c287236595ab542a954aa5922aabb22aabb22aabe637abd69a30fe186bed79ff1f9a55593567b2f06956d127a08dc5625866b06a2dada5b5b496d6d25a5a4b6bb162b1582c168bb1582c168bc5b2cdafea2fee15b25416cb627306b358598c95c558598c95c558598c95c53e1d3e9d6cecb3080018b3b6ac71592a4bcd990e6f592a4b65a92c95a53a2ec662b1582c16ebe6972dc61476009c23693beb4c26ccc22c8cb5f6bcffd1288c91777371366b19be768b65296d6fec8dbdc134ae1884ad249fc8aa1db4566c0af910db4f6eb9e3f70940c327dce113969f2deb4c9d69ce5c5767e23ad3d6997267d23a93ec4cdb3fdc4adb1fdfd4766f86bbacdafeb767c2f863ac3730d49e67f1879ab537386bcdcbec8ddd62db2bc65a0343ed6560e8fd05c391cd5233b44d41aeae3aeee3d41e9c416e3943db1ae46ebc1fe87073dd6cffcd35673e608837700337d7763a6df9289cd22a0f9f70f069854f2ed7cbd5e382b962ae59724d53ca53aeba29bfb929c7b9295fdd94b36eeaf6481896316deaf1538fa36ee60c0e5e6996d77ad7e8467e099bf4c9437d6a14dbab2d65adabfde245b7eaa48e938ece69227523031838114489ae8156e99d9c9d1d243b2bffec1f8e2f8973f29eb9428514b6e3afc0d5c03419060d00b095ce2e244100b29661da85251000487f3aaee691e7a2054b68c576e10960fe850a29bcb8b4dc2eac61003023010400003056007117ded891009d7c4973a8f66e9f965d4be35f69fea3d53f7fd3fae7cff3275d3ddac631067ee19c7a56a88a459d7245e334b6638cf131cdfc4b6369643983a0f6c9fdf96e48faf4b7ed6590db6f3e8a0c88b2b70f999eeced5524f782cb6c2e1e4b20f4bfad7c8bdfc09005b4a13da77dda6f5f6e817ced9b6ced3d1ce8eced43dc6b5ff75a47ee85fc1be8a2fdf69fd71ea481333e20080c69886deeb9df7efb32e83730cc77dbbefc91a3c1bd06926416bb7b170dced07e7bedb7cfda83be2e022bdfadbc06bae0a0b9cf9b3601b9f5e7bb695ff72bcf7d076e1458790dda58f90eb4e1bdc68120f53ebe732f481a1e185d8021983bf4dc6bdf02a42c6008e65679ee436028b7ca97b5cfda27b3dd34fd96a3412510769f3f0cc1dc2a607882cdd19040063b30e43e6a1c47c35d66d3dbe3edd34ae8cd41ad5da07d902d6988659f5b565ee5f3cac739e839fd7d7ee5e3401f30d4faeb9ef34e7bae7c72ab7cfab96fe15e7334b6cf46f69a7c0d073eb6f6d9f671bf7ddc6f5bf789406efd024723a321b6410f02b38fcf7d610772af73177ec0ab85da83208bbd6d9f3f11147afb90e3b4af61060786356cede3f6f133e9811e10c06888d1f8eb5a2910452059b67c9b62d14ebc2298d851e8d8e41d77a288e24e262eb28822070ba2d881248a22a2400247144bf0dc80c7c54305cf1278623c53787ee0218267099e2678a0e0c9e15929b142c7548285c975f0d8c0a3e3061e9b1deabf158892d50d934625476c94f09c9438d1e1832a81a264081695e72a3ba2844789931cfef223fd9371243f47bf9d0972f493121c3e2841e91d7794dc04a197d04de82456dc90c4093bfc982740e0882b9844e108ae94115238c184243cb020a269d08391eb648a0e4850261fd4d3a11d242e8842a374de7107498a0947681fbaa477d03c34ab89cde861b08020b2c1474a9f2ea4fa6e257571617e7cd25e655225d197d2390a4c2bdf7eed6c7c4a278e4ca9e428e862ab9c53eaa89a94d3c7c4713a593ad607ad965eacd14cdb28cd5be468a7a16cf929759a7ea01df40b1d8a07711e96bbbc89e304f11b295b9e4a523ce53dbc2425a6e3644bd923595247aee40e7903449ee40e69923d3e63515c2de93e27a555fa9c94d66a69b5f65e8c2fce324dcb1bd77db8eef3d1fa3dfa852f9485b6a02e3892dce1afc4ac84581cf0d7563a659c1df6eb73ceb9517bef9c36b356e2c7a103d3dc693569dd4aaa59b73582149c3d3e92ef372a9d8192238c5004dbfef2420ec2a103d3dcc7f9e4a3aa24ca6420f2c965b42b031165f40fa49e22a3e68c568276b98c7ee91f68226c67218b2bf44bcbaa5f2d54fae5b5506dff16aa1640324ae3da08c83d50144569eed18a317cd0a17ec15ce6f5c4657aa0d4566421521f45116cc715e8b42bc8f46794b995048dc1dabaac7889d0b303d6049348c8d46fe48f9421c3dd3d14444f77c13787adf3dddf72242bda01453a3aa45874248b32d33a77a131df9f34c11b9b0a91f3c79a68fd1af935455068fb1eb78b8d8d67d23377cfe69c9e514aa967b5d65a3dfb6cb7fb596badb59ee5524c53edac1583e1d465c14444746851db3dfbace11f1948baa0ebc6961c7e10f8db9086d8ee80356dff29b186822c575e4351a81a568d882562d5cc98e13a728411c248c988b704eb2bacb0595d534a794d38589304436bda4a13c6371550a539e38558bc1512babe63179eaaeb9e050c75f72150b5da0e8a655a14274cf7393c5369c278e8499e0d7134f20f71353c1b228c3f088b0833774887b8cfeba7a1cf35e495620cff10088aadb8d4d7fe064df65580fecac7407fe5ebafacbc069a40fb953741b6a150db7fe503699f579a30ee7df649ae463444571856de456cceacbcbfd722bb26cc674b201828d602865a048678dbf7b71e18d69e1d6ea87084da2ec010cb67015df24320e8dab2a4f2859e697b69fb83c20d1582b0f06cd80edbe1860abdd3f6e7e67dbfbf81b6b4effdecbb3fafc8077ada1db94a278c14be9d1031d4673687f18fdba5b43e8d42a8adea9a33f8b18f3c6a99aa83386ae91e0ff635b6e3ae2f29a5d360b0ba64a0c39c9a2f554f4ec1722a9653b494513ed24194c9457cf94784f1ea9a33d989088372999c8aa322b212db89f46c28b67b4ed1177ed794ee49e91e0d65b64237945a14cc35679c66565112f44ccd99faa9d484a135a736a51a2689e4f939c62cf31862dc9d4a8ab1284f467b3e66e9d010778ee874c09a2ac54112fb934862625431319e275ac2f35a30812400c683274caf275348e9d18149912498d880491448307101931b26315842c592284c7af4f0822a700c8142891ea48a4ce1e910131f76a8ff4e237074a60215254b710113204b7c30042196e83862490e4b7c58825a72c4121f4b58f0a387b1040694521f8d60c098184f4a3d8fe474fad3779082330b8ca3749ff3894caffdeebd177780bafd9a8962da093afbbbfd06bad8686ddbb1efdffcf2e677bff9e79c57fb28a5f46a5fadb5d69badb5d6da9b6f6aa27a5ef6c65531d80aa79a64a9b5effdcb89a4dc9b7f64f0b4ef8deddaf7f1d540171adae3276960484dfb3e06770962bb03d4f4ca50b2a4d22b8296d29b21cff7d12826a6021363ad6be8cb4b9c01038e1c13a6c6c546fb36c5c546fb4797dc0caf48c8f4b1d473e2e0cc5289528a83536badb1d65aab52dd7befc5228678bf5eae72150c8683234bb21446d6463f2c6680352e367a6e710637e36cb2433aa79c2fcd894ce9fb6824418a63f03c8c9d7a8c21aa839e9e3fa57f3f87bd5807174541bf78f2058b74d0f4d3381a934e8a23ce3a7f6a6dc073e239b54ccbb44ccb3eb5665a663fdfee5e70e239f19c73deb761bfbe05af605dc1ba827505eb0ad615ac2b266aa25e2f57b90a06c3c1892cd61573c6399d7236e098651f29c2f85b19f1060bb05fdd234c37c673cbc85386cbc8fd8038e322c2f87f6c98651146bef7d9a18e31a28dd89a51e6d69aa941974a251b689898cfd5481bf57f67c9cdf85dc1b0c67efd6b8a316c72d061c70e3cf470f2e10720780421440a50431051440f238e50410a892494f0b1448ce13512bc2b8831fc6be4fd1163f85f93fb45ddd49e2b9fab09e3d7744d726b5d1d394e150e0af69aa597cb69b537da8bedcdb4ec62f5b0561cddb8eeb3c13e9fd28eaf230887e52a4f79c94fb01e960b47dec8934cc952cc357b368c2aa5fb9c9456e993565aadbd17e3ece24ccb5ade368eebbacf47c7e8104b0b17af63f48e14c718f10da0aae1878f249e04c19102238cc08224f012224c70c8ae0e792f37c3994d96418131c6b805db4d7cb1b25ed4434792e4b6afdce46ac070814a47315c0d1913667e0b96bb5c8d1819b6e2c9b9d8e81087b91a2d4c98f9dbb67d7efb8ffd80a4bcb7ed7ee548746f5c0d0c39f484a93be32e47b27bd3b2b66d1c89fbee378e345fbaf6c91a470a71a418477194fdc691b08fe2f61a48638cec3147c21957b371356226cc7c191326bec691e4963719bddb9e84b3edb316a5b6fdf692ab892223b48d1f336708d8f1078d19e3c8f2639ca93943dfc130abf6cc29f963b5e7efb47cecb893031c3bccb19d1cf0d833c81c8b2cf8cf2bdf3f50b43956840e555239d5b327fdec55523110923d9f7e29578a155970a1515b3186bbbce05fbf8232c89d6331c67cd1103ac4b13da3977648c1da8a31e6bbbc506dd0f0c7055d5ea06fbf7a2b19e4e8de141e153dde115e3ba4db5baedd626d6f156905a9aced19216631829c71af4d7fba2ca56088b5a7dfa337d40a1dc697ad20bd69023e61d39cb91f625366ba80cbd89f81d21b1f89a2e810dfb84c9b7e8c7d6a5f0343199b7eb4d6be04e3b5d6b42d9e34dcab561e727cc51873f69841e64d8c13d5f3e43529add5da5badbd17e3ec629c659a96334dcb79dbb8bc6d1cd7759ff85dacfbe8e8af1823be03d159b5785c15c577b82916c5513c625160394d5aab9c9793550e8ec4217b48140e598a4f3230a30b3146fce873525aaba5d5da7b31ce2ece324dcb79d3f2b6715cd77db8eef3d11a0452d120159595100b4b0b172e3c4f246a915f18da72d4c285fce4c791422db4a565ba6cc19d8e3122f65174c550b2f2ca4f4f13863eaa2705536d7ab36975899ce810bfb2cf8d86dd30e4cf740ba914da69cee89336a13c6df26e427acaa036c9406b3aaee5b8cc0c6203280fe401e8d3d76ee64c7efa1a4e64413ed59c6caa35d954e3d934d4846c7a2385761363d02774a84d9b7ea8dd6cea32ed27dfd4befa1467ce4c280ec303d98029847880faf471ecdaaa33caa6ab4d89b07d8794079bced784a1a65d4dbbced8565e77da528e303e9f95cfdb67a37bdf39476e86feed037dfec21b333a30bf0602c36dfb3e9fbfb86283db3eb957029d95a8b42e1606ad6434030000080317000028140c89039224c7d1408d63fb14800e627c406060389cc925710ce4288a0221638c21041002800100332445645800eb31c940fc3c299b96c2f4a2920e69c20fe14386e55c7b593dc3ce46d3b01cc68efe95b60e4306826abc1be1a6a13a158b335576f5f12823df0ab919f612d672a740ffab9c60a720f64e4e051ab55d7a1fe23b452cbfd59e65c08ffdd11e07dc6cb8fdc18ec5c3b1941a7cc2461febe441ac9b062f1db22bc79210f380911678973eb4818c305e2e0f8f9cc13e1500ee41194b925223e01d37abb0123ff8d77e808d33f9a987b07d291e89117197ca8735997158ecda04cd32993666027c3a0419a008e4c2f93b9c566f7f49bf88455cb56aaa5434257f8e9b468d153105452b0e79ac3204e30fd91f4262edd57645152fcc6c23028271c70ee013218d9916bc3dc42c14554722677a07d905a8e013d15113cfe48d396498f62cf87787875f72600ed65d8a99d244926fe6bdb3b107b0700270796feddce345be7b57ea5c0f8af4bbedadb3eb1b8fe8c413b6276f13a876545cdb92cd0d78929d26d391a6f505f873ab716308c5f2c0da6076d435950be6a981f50ccf0ad26246251b7f42cad864e892ec877af0744b48381bfbc53ffdcbdad7f20763c36d4c639cf8425a8ceab9d041940965baaa7fd0fbe0d53d6c1487afd078fb27d7e9312b0eacdf07da49642a0c96a974d6ce168f33bc65037e6f063ad53b2e4b8c57b812cab01b291235d11ef26f868969c25c013f9cc79fdc55c762ea8a62eebdd345ac2a469e2c11dd38acdedf7428470ce33cfa8d213da48bd5dd60fe3b692b8e4832fd574268472db683318f1b80f0d5df5e2ee9922c49f4461cab967caf9ef77803969e8792d3a2a779a8234b4ff64564447a5a4866fbc9000d2ea209a3a755090fc011bbbc41c8b50cd0d342f588536be3290c3f88758e9e66dcd205d011ccc9f91bf3a770aef6d3f3d514f6eeecbc1f784ae0a359484f4bcb303b32508d2b72145ef21d7ddb1ffea6d37d240be4baa90ae40846b127f43437e9673daa1f6529cdc713998591856a8cabf4287c155733f2044b4f13ff4300c7c4692c084c26905c307fee3869e2fac8ea34b1cf9d6dcf5ac5451be269377ebb34a3f66de4037cd3ee150dd7b1831190f833e569e2dea5713ea954140d8e4f12fc3b3605ce7000bda0c943ef1d0bfea5b2728de087ee57c84bb5bd7a9c5a6d5f70d09979e1042a7c87e5eaa4ec24e0e7e2eaa9fae28ea78180a6101a36ada79d45b426eaddda66a75fdf3337beaea929056efe3c732ed0103ae8ce65d58f27081b8c3294660d117f2564188f110f09fd595d6f3fe85b313ad44fdfb65538c8442429709f0447bb42d14cb59ca222005a798030e4c9f86512a3feb77c47a717a091aaf6027882d5674f3b1868b6e7e82e1b8ac7a6bfd48059cf205cc2231c9dd03837d6b8e62881b3945951c318bc02c2d13dab85550204252efb6ebef921dcbee309e976553729353c9c71cbd2db861a12c9230f4af188ac34190d78a9c64e4735937dc743abbd9819410f84063a614ae77e2a802350d444d15cba060a2373bc42071d5fa24750d6942ad7c44d40cd9e6c21b7db8d55ae8d533f54982226d2c4260626be9a50e7fadc1ccd84c3525721a409fdc8184e25cfe19d6111402cf9135b396f5d82ad20a47a5056756148b0808d8e99d6db91287c288beb4343fb6f37db84b39b3fd4e02d86bec28c88340d92528137f39bd463dd600e52b7c9bc685d482401b091bf803aeaaa304d1a214275116dd4b079c0ad0356f064cd55fbac4e81ef7794c4cead5cb5dcabc007d7911e0e8badcb71bcaa3bb8591055d2ec7a9ec2b0119a6edc860bd01bd80d025eb244baa94089b678f52dee797079abb7861dc27b28b17a525d561d64db8a04694eda4259a6f3eae16d36a4c64ac07f5da18e72099820a635bc5df4c0b8d57a521a95e4a8e3e78f489ac9fddd01344774372ef9478026fa7df8f3a4a447f44951211017eecc29c975374c96d9f3179a3a4a9de0c5b7c44f07162a23ef399de15781bc6fbc601f081240abd571e17da6048269157d86ced1371e4b2d8163b29c328ee1338d2edd12a581d5fc2afdc6f0c010afa0dc82b0532c7ccf9797fd4f238f27d06681426859d05ea2331f565f6ec73bd9b8374a7275423eccc484bd6033727b75e8bb5fea7e32de0089c6bdd194b9c8888d6bb09cbaf2c7cb89bed48955be0094b278ec1540ebe17d407701566590c5b3aa586e3c65f17c0dffd1e78757943de214e5e7857fa6963f4c59f2b47c02a7ac9e17fb7b6c2e8779f6d636ad122ae9137ef892d4d7093d476e7aa48145aee88a8e63c1e9d98bb361a9e72f3e31ab5ec0d3fad49ebd447c8fd340831104ea47269d8e27fdea917e2c8fd284942f0120e67e0ccfa897556616ec5fbd2cf13f406c569cffe4086b8dfb75664ee3c8995abcf878f679feb404735f8fe9ea34e2000cfb83d9f2e9e84b3e367bf7055f8d2242a0c6d3f63b277c87f24c2cdca38bd0d00d86d4b292aba9c33d82dcc7fdfec6b54705363b6b96cf133d77d41592c691a52a96c66d79568041dc66f638aa5073ab5bb568e332175e8e78850f41af3d7e2eac192f0c88c1c84429bf7131aea75aec570fb10d29688df8b1685cd258132b06bd4b3aab45c7a077816e35b1f8a82ed15f29f6e2a400991b35cf6a90d83f69178cd3b3d5f0f135ff338da8f83806f02fcb804c4e15d115e0466daa9d8c81ef423d58a9cd19065974147654b23bf5baf383086959b1bb8a6f9c767c9c5ca31b79898538c9a9ea15a990df2bc23fce39b9000b2f0003537089a25a40dd7722b7ff48ba564ace09fe8809276ed9fe26469f8f6631f1a84df61e2f31a9e409c1113d6842b0ee3bb3d7cec3e7b8f9913b14cd77c7cf17dea2706a3d46b292022819b6b15e8c20ab7f9ebcfa7ed59f1d63fdb843760f51ab718c43f83308b786e0e7cf7b7a35683edd1e8b098814691d3418170f7c85552f3b57d1d0f66137697b3e115357d0284043e24101ae8ffbf7183e01a6a70f511fb9f90fd27deadf7a095ee89dbe496abcf4255177040e19f1ce6e2e9abc45b8440d3b6ec31d2e7257ab5dbe8d5217ff074201cdd1547002a50f1606b2e7b448eaaf181a2ab60cf665018a990cf9fe1d96d9ada97c38275967129393287c353f547d1b1758e860b1350b417f68986eeaf2a6b89d97673a5d9baca21b7dc33cac1374e2358442432367c817475502a5fc1d2819c4aa1c7a12d38e4f5f502f025b398ef659fed6d3c61775678f0ea3355464b22d887d3b5788762e74f81ad3320d3f198ac736333b27906c76a5804afeedd6eed29a469f3257a3c0e370992b6f330cbedb45cc51fa106d6e33a23d7f4154131421d8e365f73f1fe9b64f384a27c274b8952f36ba63ebdd17742c95d9f0946305714f88f61edc6c26217865cfa4c398193ed4a797fc4123ceda5abfb23cfa348299627dfbafd735c6095310e91e1bc518e5a2bf3f000f0aa4e45ec7c4ce39e696e9e392c366d9e6a955d0c037cff4d519b59d526781299aaf18efa6cb652db2c88647ace0f2f231d8403477168d3615c50746c474c34480a60d88947189b1a95186f6372fd33b02fc0c4fe487f3c9cd953fe6a020933515f98d96a3c6887a9430c53fe08a50bf7c4003f0001c33b8de5083d7e2b834c8fc8a3e0e69b54b5ad6d1dc74053bf87a04914ee92ac096392f09243bfa2506d9a3b830f28dd1d101d5f84f56ba7be8a46f057710807c0032d0f26354bd3b6c01189bbbb7c7cd69403cc10f18753790747a933f91ed1e26202f972ad7bc069fd9b1885fb18928eb83abca2f41702f8fd1a7aad2c3fc61b3a6705a11f1e9df66dc7e99183f9630268043a0987d9e239d2aa64a6c895fff2bb988060048c68f985b02630edca727bd031162feffd0425f7976006f2bf7ed7423fe852c6a174cba6379b5e498dcced8e928371caa0c21be998f0aea7e58fefd7beca45f5fb07525871055368683ac5d2a0775272846a61d38582df1adb8306701462519f44b69254230425e4a57cba405b320688c83865c91d2dc49a37e2bc107a6d3a274976b5a2c1d8e0c641df63e10c6968345306d11193a3f62e0d027cd0bc684a70b69c34871228ace8f4bb78c06b0f8bd9f35900bd0ef864861b17500c013048778739126fe460323694f71513d440b0c81cfd3e61280dc75f183e744f3c441c800eee5c11307561edd62e5a27b7c6481a62f3016c546f96016549afd10f43585b10da08ec2c6be883c8a7313aa6a8e1d6e002a2b3889d24226f5bd722fa375e26bcb79205889e30b8c82d0ff6e85d8a354559dcc34f48e81ea4b66adca5fc638edc714ba4d3bedd3148a7c67ae7b64eb73b150094c509ce915bc935474f3106ef669f4ab53c52793c573682ab69715b0ce15545fc638cb4d7dbdaacd037ecbe55f602c36427508a4650c33adde290a8ae2400094d47ad12abe8c79453025be58f915158d5a66f139dcabce6f5d31f2a9fc6ed9d75667a7b555ad484cabdf1aa52502232de2017f6610780aa24dbb8a1aaf2569784a29bc1bafbcf5bcb3fa1987799e97fd4776f2eb789e95e4c2147fe74112b1c60c9817edb30b581690b879e5eb950bf0ce9cf46b342055ebbcb80393c4c77cfcfce1a3098a1cd065b10aeb723457a2a1ff20868b9332eb2f192ac24c67397ddca93a6729591e508ec3fdb2983e9677c7bf1366f986595b0743b0ff63156b464b1198249ffad559dc2134ee6623a488c30ecbbf370aa2cf03b7fabfc52224e2e41b15de3bb26c08c27d04b607a8a286ccb3097726e5e9a48195df70b2419d32b55723341dec8458e75b80d69ceddf406e46152b9cdf0dd2cee1a4d3281f10813db26bf3fdcbcb9a15586e7000ad22b77abca3c5f4e8f282bf074f1661d1ae937268919d254ad2f4e9d9debb498b4a408dcacc3c6554745dc51ef1f29e7a599f47c603832a172d89979b79f07a05daba0bcd89ddbd113fd8e2e9f683b84d41c3f60b526cca9b5b86b64d9d962567710f949e1df6353ee949362497092808d5668280f9b8aed57d0536ac1bc5aae5e1107aa5f79497fc19009141ebc462ffa67d62cb24ac8982c87c78624f4980001a886c0b7336481f20e14554e70bbc5716e956eba7bf039bc09a6d80053dda0d02dbd0bac83cfe61ac6c6fcf961a3ccd22962d5605b286aa405001879f3194e308441482a592b9d75f74d7688f520012553e3e0b91ce112d9c85e5918e41668d5eb8a3cacd491d9d71302623c09393da84a323f90b12f2d04eaf77479c99da993c0579015dcb51640c4614ec35ae3487fea8cec68cae1009bfd220f70fd1e4578961ff019c0cf895e6ecd6ee48821521753409884bd753553a22d35887212e7009e4a0b140d3e590768bd20cc9d55fe332bf014a04fee2263e409f896b3c44e149d1129872267e74d0b4221f8daf6c937af8f5cd319a009ea4d47dcf36150027098b253e7a28305b87482eb266cfde3eb77ddee3fd4aa6c200f40e73b9b972660f18974f96e9cb9a5b557cc0a323df5611129ed4f33289afa0e1bd4f68c30f5ddb9f1c6c19ab64f1c23efa893028fb462d15efc07f55a00e1a650e2ccdb9023092d9e0c207fbeeb5a0cbeb5027a2c5ed1aac2c228f14dc2a0b457e894b45aaf7aa8abf97e9a470b436f434cb57f571aa56f585afdb89fe70566d2e3c46fbadbaddfd1b2d635a3dcec889a7115453804a188893c7583f9f4989f417f4fd3a704051be5621a1372596f25fff55fe2cf0a9fe00d5bc8bc4659a68e39d21fdf0a30c845ee9927bdfca6d4e1a2414be17ba45ddbcfd3c6e1c6369f5ea936dfb35eafd0574f39a8766144079b506cebd418ca42e3f62a1a08f706b94f1d3f06cd09f51feff0280855c428026764574836c5412f2a871456cf7c39f3f58f4f3655f3d6f5742fdb3405c20aa5ac8f0865b3e39f716d6979a0bf26712a8a075e5b5da75b0b87ecf2438f1da8fff10d56de829c336275708492623f9b22b94081daa4b8c865e992e2dbdf41f65481b21e0a80c661753a22e798a7f5e192405b928be94cb0c24638bb0b32f3b243242da2a641af2b90ace916b6b9d443deefe30831a41df3cf225cd4f965cd5ee9e210bfa33df6cec60f3164a141b022c343a5ddd03712cffb2dc234dbc24701d5aa8211d9db7ffb6b5aaa06f25b1fe6d54d80b2b72a934f51302ef4445ed4cd8cdecec4a4a97368012beb165b236f71616e270f93361ebeb559f698b1e3e78fee7f8824ae6a82397ee7758c09a7ef09767d1d674931da7d89e9b4fac278af3f95178918f756ebe9fa7f7623329694d935d6ef20d646de0ca173cdf3d859b070a0b6cf14e5d5d2824b433f0239f631146ec8c7bd53720a0cb9d949be29e089d08274663a04cd908aad1ba9618f94ac7e0f9183d6bec206f6cb49fa43275233e7a29c9a2ce1d2a21a5f9c44655bf6b5949af186e07a234e1fb573fd1c71a66a92d9b2166ece11e486fcbd99f1d05dadc40add8e928251fe97ae89b3fb0dda3f4a023219936421dcb685377ce0bb43f1561bf7c7fd4addb3955378560a1f5840290a49df8d626fba22f3ddd5196e6835d0b81f814c3e0c9042a8f0667925b9475946b0d09f01ad138f6d3330b7a29afb8ded1cdef477d4516ab308f1b9c0fabe57287c029322b09c69862602b26171ad548695318e09ac151318ca9ac46ec0141dbd25356d3f6070915291f86b5ec95f9f8030ac06d8f94625f41c7afa7a258f8717a831c7ca1f283d8b34b081698a26223e38e5b2a60bdbb062e3fe36c82743aaa52df33a3be6d4347f0c8cdfb5c636a8798987ba42a78a01e6f7da007f8e3423b04a9e179112f1edf0358cbc399f0876a7ea7ca3f02860981de07183d9583101ee2dd7506c053c0d97231407eea92ffd055959ecf423b7a1a6e7d2c985e3ecdb40e276605f53a18241e398464e3755d32ff8f92102e3ea992f7cc8d84724e76f41d093e67edb3909960fae122d2a10b89f4ad49203105d910fa0b857485ccc4af10f87b9291680f10a542f7b2ece72a2e5ad0743d6fc350de698e45c087d7c012cdecf050c24359ee603b90fde0a11a07e27ef145287dce023a589cd4a60f3d12040aa93e739c1c1477e6967b899fae43ae482fe856b0de7eadc1281bcf5c78c635cec4c94daf8e0a2ffe71d0335851a5500347673c19f1c6141a06602db3cfc8339d691ab355d37c21c36597f402cc184360d70e3aeb49e06e8836e65d9021105402817347a8ad4593f932f4c60e03fa9ec1808f5a393094869687478400a3724738aa1c28ac91ef70adb1663aa0fe989f909a6a150f584c101ac5f155770f3929db71c174c04785d8fed860266e2f5de22c71acf412a1b7dbfb04848e20a24538df56100dd74c8f7ae9ea5e05a7f54886e5e2d5558443d6060657267033ce69eb7d0c89b4e75f7ef4493f521e8e86adb3d969da5e1bf5ea80604c2fa9c48340767c0031085025c66bfc57eac67d322c91bfaadab23343a39c8fa88f72aee817758967a6ab94c698ddc323d5ade03e15a6a89d81add8e3f7f248e82f2e2d2d0c18910f415496bc93b37e650780db03153247d179541e5a5cdd2694d4f3a5a2086bf03eeaa563ad379353befa4a15a1a5d22720c079f82604e6397eb72471d2871db4deec302e1ea9a84e604e1917c0d152dafa02db6b5ac86b33690098f17161b1ec8a3a0f1126f7e077c6af64e583c68e13fb3d70a201d4110798813844f2be7423a80dc9e6bbb3a18e27a68d895449e2af67881519bdec3ccbebc50b7cda7c7574622d10b6eaea00c40a2e4e7e666f1fa46449607143787e8ae2d532c0ba241d83ca39aa824c29da6db20541b7f239bc7467dae9fc55d5c0900ec3cc20d39e32dfe0e15c8797c34d290f6072fdf80ba1b61a213837bad1d1ac6326b60d1839626919f93d08ebf6337eaeed86435d79280a393f253a04178e873bbe8d64899cab21b86e109cb39db8cdbc51edd91000b17bbd0dfc757b7fbe1d7a784fdb11216579fa993f6e9061e6692f29a49217f82050f5c0efd53d47a0cdf6c35113e97826ad7e0856f17394d1d4716701f8b218157c0b308b0ee322ead47dd145c7d272e04af3879e9acf7042e5fa058ac76343577d5851233bee0849110728a0a3cc7a67b0003a043376c0a0d2936d574ed017763fd4794bce972e21096f6b66a7adc02c095f50605255a92f6a5df88a784f0aa6c23821a557c7522d0853698856d9ec769d0e837a46e7fce58acaa9f67de87f32a6f32ddfe332604ae2e2b50c6d26f00e4a7fcde3025b75ee67d863123db3248b94fb47a0237402783d1dfa400262f3665792542487f779f0f7b06d1a6e7051b857ca2a29901daf2800a7b1f2822313f74b5427dd15dcf50fb9067df5a66c833f8fc76d84d9a4fe11487f7d723a13d8ec91425cd1af8dc98afbe1c2700d3e8e23ee132928c313e3b8c891ec6d49343904cb14541e9e488a7a7724871947001a479764230688826e5908d784de8b3029230fdff90f18f74db90cf943320f9c71eb469a0c5536732b1382894bc4a5f14ef930e05b1c8f0ec02f6badd50807dbb45c82b8110f869c60a1df1d9f39aa5a4307c56080d1b7ea4914bac311cf443cfb3799306f56dd64e00aca050735b5e3e603662c8a8d2170ed0a436df407cfa91f413d8af629b6b043b73d4b5b2536584656bc6d7aad86178a99c7f9a6393cdbdc474b3d471d0ee2f3f6a3228f8fae9dce3847e1e6a58202a4dfb23f32cd4bd9d3b5ed282081cba9a4745916eb9fdd5b20778279993c1117ff4fc32cbfd28acef3db9a7ac361cb9446c5851af9de285852fab61c4fd16361b62528850c2e0cf6679c250d01919564ca7bc844cfdcd4145cf45e6c93f294025b0b12e207c0cca37c1cce7bb4453a84cbdd3c43ce1be4e9a00ab8704da1c3e4a9d94a9ec346b06ee3d57826329e2d72f5ec698acaf8800fdbcb87dd9dd0b6486a1d76182af26f0596c55a3acf680dbbf626f8c724cb1104aee280234d33c4c74f2279b990bd2df7498d5b637886609203b784a4623c3c9586f3a7367283c7589214efa91a8bac4727473001e14d2a25269468152891d53a9352695dd6e4681c7a8c3cc9dd6b8fae5aae001f039148e07b2ad971d9bba4b1e698c889d2bfc08cb62f93cfbc830d119255ee834307173c8b4337dce18af21154e324cfd68c83ab83d8e9eb5484e5e605c8c29e242363117cd1e662f6a1fca25c75399b650cade19a1f1068c975af7d3c79017b1cf10d7508f82c7925cf333bee0e97e3175a517bb585e69ee653e7de87e50675723b72e081bc75ef24b7a945545cb26006dff0d97d9c31ff99b314c69faa61485ac2ba9b97c9e8f76ad6fa93e0bc8c820a83a3f4fd1df69d445c2e73bea28627b3541d88e7e5cf36bb36a16a628a0fec8be0ba6c36b20e9333f34e73f6ae9aa774759b537ed19dfc7583f9113d4f6dd1b9c53261b6a87240400de285002aaa4de2aef2a4f202aecad22fa65d59de56f7a91c14881b08d975582dc08d17f6d8e6c5f6eb9afa910c09cde6150e3f8835beba263364d3f5d85e5ec2f877bad2b9d395e5db9ffc057e0cde178c065d3907a70851c19c15de3032ad340a633a60b662a85eadc9624cd8a20c35c651adad037de739cb01b8b14e9acfd5bce915a4c055fe281a711b0a685ffcad78e9c3f53fd69cb82377265abc5af3612c81348d8b318300416914c94f9fd1f66315d2884883aba170d2175b5eb5bce03da1b67e0e303c2080128942a06a847363a9e1ed434d6a08877c7981a40fd6beacc87f81544a4c2ba8301f207e1dcc477f3188551428ca9a25e78d062c8fceeebfca48a2458f4200b3e5ba297b1593ac84543437e0b7ee98e4d5a119bae99496acd7e1823cdf8c663e0c95c8de24d530ef58d9882ceda656c96f2fd41855f2ccd520f11a700637dbf3ecd32f8db61cadbbaa6e15d38daa75f8520611e9db637e63482cc555306568f315ee37e8234a3176473451133a5475d3c22006d46c72dda6b391fccb06b3b0c2a6eddbea60b2d02ad431fa982d09ae667207a7c48e9bfe8e1152fd598a9e3f0f340638f2a05d41fd42ec8a620d3d7dede28e19cf4cb0f801ca06d9e5244760ace36443f07e3a6a9fcac0f250d0b4273d14e1035d097217d03dfe445e662cc7e9834980b12ad492c4300211857272c193a48aa0aef31946888303b6351c5686fc83e3d095e7170a08ef8fb09286ef87e188567802233745a50d086344da80c6e988f7b18c1539140bca2891d38401fb8bafe7cf2a27a39cd8aadcc4c26493d4db6bd5bb53e4fcaca6e3dd9b1dc8aa90d9169ddd91760ed98692620a34ae3912b3d8db2a06940097150edb61e67590a695b7d94bcb4d6731300d8bf007aea307d807bd4b58b3ddefd949dd582c19333e75705605bacee9c2b31014d3bdb5a757ca5fdb6a67a21557edf54bfc4191c5582865ceb97f27dfa326474342643a24ff946f8d7c2911b1b7d6e27e5e037304b3a3bc3ec879db1c378db0aed224566f4b2b0344fb735a26c37ed04d78c692ec70617640da0adcfbea1b94986699d79bcdc952eeee62bce25da11a2e55b03a5b8c4c5360176596b8afb0685eda03883dbbdb01e416bf608a40c68df34ea88ca11f86df8c46806bb94acad38b336876f8a358c4c22c597ce3c69f3d804c0b43fba70a1c87d4b15526a4e59754788a342a2fa5cce82064821d32c4498e2cdcb0d78fdfe2fad1d99ce2c8fc4c12afa7865d51be4ca1adf0b33f46b9d07469efdcc61b6ff2225e9b64ccefa115e2a90729fd9ceabd7264b438a27f0c74078298b0dd7e05bf548e78623992320477be4930309ee6eebd2ef79dd972da6e05b906e4018d66835ffb5a07a741b365aaaf5394cee7459f30b510c7e651043436cb6c1e7f5f75c4f6d5ee4443b42ffc410ba7a232547886161cd8633759321f77aff0589bf961a0bb96689b521bd957207a1a5b4816e71ff0dbf5b6dc920ed5d580d56c661b0403dd4841c8c3201ebadbd195541080c49086cbbb86a8d8cc20d36f173c2bd7b673eda062caef211379a9bf759ad41b0a8f944150e89f0ffcfd8f6fe5e9559297648eb43b5fa0d365680efdde566488b71f7d078ec5987ecb063ffd1c4cfa6a06c424135b2a826f1d6d08c6c40db6718e4757c89954f3b0086740ee16d2375106eba295d0941f9d49a884d47d8bc7a5b7cbf08e8d38e83347b1ecba60530d692bef9edeae215a3dc02610c0b003b8cd8d3b0434e8b8f47a09e195d911a50c2db91acd9c68a9de3cfb956522c8a00b050d2ec9282de6112b40989e0ae92cdde1f5a152c6ba58a1e5a6532890a0eebab0fe5bb949c7cd60fa3ec84771c5b9405f03ccb5e208625e513ebdacb9b824b984efa2321cc6b0268f4dfbad391b2b76d04a036f61cc283951971296961e3c53d4855c234ac58a6f9096f423a281df1fe01d47c79343dbd314f757471705bbe1226c2c97af26c3ad7df5fa61b55eabecabfc8ec3e306e461364e39246f20f0dade0620d6cbd69d4c794e07c0b9f62493c95eef6af201afbe736e4bb2d105224805f7d91769776bd71822e46cae4e68dade87484c3b6321b7d31541a753973bbbe0710b94868beb496238033365ac8969b04a8f85323eb0e2eff8dae8d815373734d015653b8e94e085f5b8bef26de31320632da87a385b25c8587e9b0b7b9f50321d76955ebfc6728c75afc0960845cf0e1b3bdc1a276db77d1dad5ef39001fd023234c203be29f9506987499c0a4a4105bd9f57c643b65de2de66872ddab73b87b11c1ebf77f85ffa19f25dd81be39ef051f6392e04ad1afd4534b5ab3f4d4aabf78fc596b4fadb6a86b21c2094371f2415dfa5587ad3403e62c5481c927919821ce2c86585c54ac1b69016912121cc5e4c28c6c70746b1865858227f1b6251b16018cb637f991784a85ee946c7a2a29a8e546abfe1b2c2c1171cf4df753ae44b41f223feb4b1471d5c434cbc3597df5c3ce576ecf960dc242b032fd0e78e77efe873fedb6e70bd3bbdb50a0aeb96babb8b9b323f7485bdb9d53e8b3860903d1e51bb6df2832cc808fb51fc07adebd3ee51afe2c1608ff70b4ca38143e07a50a4b79ff885fd09bcde1040a9d73b88076df0a7f60f80cf8c498a36df1f1d5c2f8a5fef61b537fdad0d05c9c4f50945bdd28dcdfdb0ee7aeac941e6e4b9ce3854b76873e738cbed062afaac722533fd53fb0ee4e39562b3aa2e83e1a832ab65f4bb1a86cbb675599e9920facf277c97bc57df5d36967a2e75ac3583823a52802765cb75ab318f8da27aa86c5a1b41bf50c4a7b6a385883d40d53978d1748ec85b6c3e671a4b485dbf22c58ae4a114eac6e83e1f71db737231bee51ffa5d96314c7a66d67242596c77cb65f32e51f1ea6b61e4bc1ec395c5b24e1026bdb92c2807d95e523ec6087f1651d877a4b4397f604f5a364cc49d15e70e712a718e8c4b35b06027fcbb96ac8869089ac0236fc42a5042eaef949c1683293f0397497ec63623d206e9ecbe5170a96671c2cfaefaf2e88fc30ff47f6dc6bfc914b7780343cd1ed5b1a152b0b45abb6ee34f19d2665825a8434f19c3cca023a775d828c026532774ef7a8a187bdd0e5fb6f4e1ce3d71c01934b29e1da139c856fda8843b69c70e1d94d07c472847164a51988acabfb1199b8f9615d648c1795d9188733d17444d020ce71420e5f7b8294649ba2104fbf9a09b20a85f104b43224d9fb69faf5a61f41a90189e65e0376f24a1bb2c98f64a956dfa347260c22a96b01601962defd6e3faabab5289c403b390e93e3df8b123af1c68e4250bce5c162c35f127a12bba02269f3637e3cc91ee3bef10adddad105fd2b28be4d8b721c7dcc5b0923d98c9e5968d334ca50994a7d073bfa821cb0d94d644e49f6ed345bd041605e898d6f64d7289d447879ef7b82e0109f5dc2ab02614d4a1353097166f6f634e90ad0f9d53215644d22116d4f457440f8e3925c8007500cd5bfead5d88b0b8ab8d2c4d8f5c9bc1bb43bd70c372f40fdaa60b7d80c2497e13260dc37ba23190b140b66cfda3478b7f0e9d4d61b7e21f46492b8b4ba383f8a75a3b7703db9e6e21378968864093cea93edfee3c2d034e4bcde35fa751054b8e56a225e022da25c8495cc1650ae011325f3900bf415f2ef8b0d195ea7f1319a3b064f0fbe95d7cd4a92574ff33740c9a99b84baa265c88d7dec6aeeb06b0043a2e9b1cd503a1e10a70262a0f77d600445b254e2e0578cd8e7d1dfdf68bc97e8a77cac38152cafe84455907d123860a635a913c1d7dd29e95bc601ed52766a107ba73a2d81283400b7480cc6cae1b5926dfec5c934f136ae9f3e135a6ac2158fa525874f6a50a22afa939a848e217b4776e68683449b6cba1a86967e18c17ae528ef08a3a00621d2a8fd3ef883f840fac1310d8a33db0fe7fea792d0960c1fbdf6bc868927037ff3febe104c52acd04405fc8bf22400f71ac629399c69300144a057d1c48f9d65413d67f880022e3ef6284dd7784d4ff18566f880490719c714f13d8e2f86865620050e80abf345fa396c788ab1f470863d75bb04dec472709f1306adaa3f4f67ffed8e91a0128ba6d8c18bde7a71a0e9772e68d1061cc73cd51dd8e1bec9eacb28273509d6841c06bc9f4156da7985afe7482cdf7a1d512d4f94df0ea953550abe01386fe39f48a245fed72dab43cb9c41b6d7e9ec8df9764258a114f2e29f410c52b15385177409c0cbb038841fc96d1d5927d06f38f8d6e3240fee5deeba74405013820011851fa456c4827bdaa553ca468ad50d32c8a59362cb86cd56bae5d7ba343138f234030421809243da4a84b1c22f8caa3781eaf42e21386d242da27ecc1c0638f4f808aa52f6d12ddb2f229474727a4df58bd999aec7267fbbc509693c68bd83b6d23638e32af36e761f2553b7a8ebdf9eebedc60924efb2430c24cc4d7762e7f8b3f60f4f4442e60f0fc71b376363e2047e7841a7a0a71322ed00bf978a84eb78abbb2b61dbdfae0a033f577b3401c8ca73c1ed8fdbcccaf256c2f2f6d96b957c330d987f127de0926178df9c7bbcc6b50560aa2c987c88f184eace686cf92268b5ea64307f208859adb5dbc294c19d5606d28eb214c52287a6b1029073d520f8775dd997df4b905c6f854915f110e5b37dfec6d087dc9a47525ec665fa81e2df0e2eb73bb92dba7f8294812816efaa44d8622bac55bc340c9a4b28b4b84304a195dba557ec86c7c31177c868d3a2713d309f482dd9c80a98ad91e391b84bfbcd73be09aa512991027edfe15005fcf01f68eb70c0e5e6102084d1c7d4920b72084dddd640b34e42877ae51575b8061d65b8112679fc2be0eefd991e0393ac6636bc907e89a052429644247ca1f747fbd19b4b01a5cee957985b18b3ed961984b11fd17c160c7da93a57914e43ed52c8ff0af19acc47e769d17e81c042257203a48228ffd2a27ec594873d53f2635040877ebec82974cfe2683578ae42b639aae498da26de40a083b532338a21e55bcc3571a8e03c07cccfd7a002d1c47337547def11e6d1feedb4f26dfd3fc64b14492988609f328096bacb3d17a013a95c381f948659244a3556a52055295a62517fa802748efe6c0bd87cb2984bf478a3317f020adc9cd67e9a1788e9aea29009a2b31525eb27150384a1236dbcd5112ed90496d9518c5e0146f1b53283d3a731012c98b792d99bcc78c552a432bb42a347e67aca2da7b8e88a21a8cc5b196a59646047485ce361b56e487edb39f8f4976ab16a7fd6d42bad036261496927f6b5a37b421b7a59056776abdd7ceaa2fc0ac6039b1e22d2ca8f9b42c1c005be2c93198a82cfb52324f7d7a0519dbaa3c3b388d79c71f745bc1c2acdad28124e3a9b4f6371c62ffe24c4b9a39c0101fb1c3d8f26a413053bee589bdb1463f9148607ad4bdea117e392e3b9ad48ecd1ee1c6f6a841f708f7de7f12a3fec22653eca6136f8e50653c7b84f441aa171484408de7434ac3296fa74ac230929e37f7e6548a91cfa6c496ea86db6dfd331782460368621e53fd0fa7f879eb8e23fa237cd891f785e316c0fcb3a0b0419f60e406c3e3498af8dfc0a637ca0a0ff8aa0a8f2b718d9f8ccd219402902e207c8d2a9884844e310c99288a7680854f103520d944b9ea8771ea12087b637b7db32369f476125e9d869191f74e277917a823ef5693bc128fe4c1f9210f1571fe46def3192e85ccf7b7ad6d22e45585c57bcb93809fcb1727afab0d26f8fd4f0c0b82ab53b87596bf913b98c839db9e1cb01f447a53d98753d7a24b19a396bf7f3d29cd37757b5ecf90da455136f7f916703d39bb83ca40be66d7ebd78e2260f5580ab114605380ad10b04279cdf1f3d292964b36a120bb4a956cc739ca3cee5886a2958184c3de77a3d1b772ddc472720668ad76759c15e25204e3e6a8eb3dbc89703d24c791ea980721ac7d149d28418b52f6feecf68a98a025b2567220fc0f7c6333901ac94737093c0534bdb7689eb6bcac334a31c5abba000179a4c00dfd07c7b23300c96961e3fab3b2c14f254c1828c2a146d898fd497e403d426eaa1e850c2a653a58f00c81c0f131e0e65869079b2688d9c01adeeff5cfab1840bde832d6bf88847fd638650ab76fb17c208ba232bcfb0b941729ae992c78ad94488f647e1a718f075cfed2fea2af613204d577f05323f31c80cd3352856ac4a3187195e0545b28b64b5da0b4bffc047bf1496d4c312223dd379f6c08d75c0cd1f4daea1fefbc7a43be5162e6528e2e581a73f7cb306de4642db9bd2126c175e2db99464f545b096a00d23790ce02662aa800e44450c021c504f9244850524d04fac9281ac7c573b061469e5e4962df2637fb11a4c3dc15253d77d26d32faec54abcf6203f64ed2506e9bef3f6e6d278518d69bf4a74dc53e55f3c547f1df07ee3293431e3bfbdf29114aed51d2b39fcca47205818b7436940bb32eed1b86b5047c41afdc1be59235bc6fa9dd2c5ca4449f9682cb8aa102c8a2bcaa56b89b9ddbc1c2bdeddc482d5ce5e4a572e1b6bc9a020d84905402004870fa66e5fc657a63f660cd239927ba3db50deec0f4153133c3efdf79873c460c9852127f072f91633c44139e2e57904af81f0e7f82080d4ea857894309fe1389b09b3463d428c12531777d6c7b239ea8211b1b5112bc27763b637421b40a5909906d8bae894505d0be455e0cf9f95045aac962b26cc533c7828a36a6d44dd49f312aa48535d842817adbc180c1b7e9c62d71e1db048e03f6152a03e63d76aa1c6a8e3ce33b8e9ca18b8538502f730abd2df79df06b460fa8fb80a3d2011b753631c4f163e6fc94a12a769c15e19ea1b6b71a3cb37dc372f8311713dc8deda4e537a5d950b5193501b7f3b41150271ee82cd28ee9b06fa29c8ed7686139970704815bb1836b5a3e0c2cb24afc804a2612bd6760ef6a15ad7a9654f794d8a6b3e526ce016287774c072871bbd04d7ea840cd471dee76c09a8d06404141650da2bf142e0f4bc8fac0bb4c75e809ee680be7603616038844570eb3daebc76ec02d02a40e1c2b0c19d3962def590a85cb7f41399df733c444703811e4fd4b4e4a7dab6d9293f025fd35de5f2bed64fde525275b11398d0635b450a5c39a439e57dbad3fc946065b352923087b50d2cfe5909002e3696c3e74215d881852db2c58c30bea5db0808a760dcfa50ac441cc73902e887b9e4c6c62b31789cb272083c4b2f20eeeff79209b5a694bcb287abe212f8cc2b5c2e4baadace792c8106fd6e2092695c33ef087b48814d7305d5d05efee1374080431dc9a697292969b33bc1838c2127a74c5b6a2278e71ced592ef27bf5f21bbd1a08218af9f9eb1d3e423cb9e0c66829194c50555ec74a6b8a841626674417945ab2918a0050a8c212e14cff82d9f7ddc96b12b848600d7078799081ae7a7c0d41a2734c1dd09235ad09ac4f2089606ad33fa3c8ad3701f280c75322a4291da44abe70b013356c81915d2508ef7fc62e09d82623568f0775cfab54d3c3cd009163b57947c2add1078c59de798117c9d2c8630d5084d0519298ddaa4370b5616d680032fd98ac9933a8e4261f3f47e620d1c07cca8e0683812283e95c85c438529c6f76bc803a845ecfa432dd747834bbd87104a383726fb323acaaf539515f0f13b943ce1fd078a8fa094fc94c0d13db8edc37efae730671e5b8814a137ee942e2e1bcb894bbbf4d34e8fdac6cead22c7fdcdbafed71b195b0f08ff9e9b3e3bdeeb9b0182667836d9be8a2bfc4fed2efb1bd639f893bd472b7ddec0be3660794f2cd6839289f5b76c2824b7ee3d4e73a84d2bec44d7e14a202bb82adec8bd6aaccc5d63244746969cf43b82f7ed00aba0b0dad8feb133140afd282dca54b0a27fa8d337d07d82beb9126dc4e77e8e8a5b1c893862405f615256f1f5295fb3d9a8e36c5a8c9b2a8ffbde1f3e127f6bf77ccb1c3a004fab61212a87c409b0886f1e0318c4d6aef90818ed1b149df188aec29b4cc4b400947e151a00ed5e4dfce385cc506002a19358ae2755a6266e2c863ddd1e06b8d08a0b6f22d7245729caa67ffbc3083ccf68320ba059270f70bbfeede1504c575fda3624635967c008bfe7a53380d15c98cd4d5fba0dc55af29e45d29484362c435e60be0254f8af0afa628ee24d05efe9a21c58fde0a3ee088f18323dade28299934b8a64b3b0e51c6a8491b8ce814b305a52608c668d85b5b2c0a4cd07533fc9f3a3342050cf87b2b858f27a870518c8e20d35033d91dd5cf4ac55136f62fab9cb9e3a5ac1a84b58ccb6135f130446500ee6ee8d74690d7afd2fcfc6f53ba86050bbead1bc21dd96379ea26dd416b9f1dce62ee9e8ceddd0cbf8847a79ede441ae38c07d7f07a74a60533c9f85bc42956551b256e6d7ea0efa2af4ea14c10cf57db2587cdbf946551d23ba7b66481a2218b2ac08a6c4ce9335e39f48a6969b281c38428b104eb1f7299a90bc4fd106360870ed5dab309a15250a93b3abcc013638dcc538c62b2039763b1c5c7b11d81002744d4c87e13e3dfaff7f546ab850b7880572af273ad838303962b18d8170c6e66839dd84e982ed8aaa9e38081be3feef16098efb7f33e42e930121aa3185394bf72330c5d3f5ed567b59626eb00ce4bf6eb6883f64978f3357f462cfb3f0974f0eed77f2678ad43697e6416880b147db4473aaa8046239059ce88350ed172f8dedb6190ed36459b1a5a5963e2c259db5ba23835eeedde26babbe2b0c408b4841e84741b34d988454c1a54b060c6b75613ac7d41a597d557b23412f29893d999285a123232f59fa39417750f458ee0e13877881db3f8c86017683d83d668d545d2a426b25da043da04d111ed69becb26f64dabe4aba0a05c77fb6b8ed1a185f5d66120a68e683bf025581b3ea432a486ffe18cf418ed0547bede9262e2bfcdb7efa44241dc1786252a61d05142d99ffac09cc69665925a3c53cc737a0a10ebc6c6a0ab28e1005aed7ad9b224b5edce4b555a531000e4e73dd6c25418f69144036643e54ffa96276685ad4015978f438bb72929b1b79d2cf8924c53123093fdb11920c9d723fb57a12b83e0b48a642102e91f4098ed2a6ff7b3da52833145373882e62f65d9961ea4dc1e07b35ef53d9dfaadcc1823f0755b57fff7a35473a5c80dc258df0b9fb64c3aa57806bb235061791857627d039862bb1be12ea05dbece4c13ddb866bbf49516349a8ff5c3c91fc9a6eaf01e086b1c76693024e845d99affecc51fe9ba64f9062918c051e8eb922f2f4c53103a268a3f3d17d28a3c2a69daec03e06419e01b511d80135cc22b2eaf72f87dc5b5b3847cab0a2b42dd785e7f4115d86247aa73e2628ba7afc207d80f401ba003e561c7e4cfa8477b8e5beeb8834713a99d78502939ca872a874ea5a2565861fcddca64ff36da1385844865712be0a087bc80b60c0c8280315b4a584160b8629b33098bb234ab8f5d85566974d9beb27c53f42614606aae68faf4d4a05a5aa9bb31cde5ce4319840e89c81845765df7fdb4691a692f1d6c22cb7324ceee2d0b9890054c53dffb666851e542e60fbe2e1cd75b8417cb06d58b81315a4603964ceb81162593ffd02667041a6c15574bbeb2ac6bd9e18f54007b3850bb53a952d749ec93ae1ab37f169c0765e47b09e2ca2642e8f5604bea77df5786197cccdfb3f1ae5192e2c90b4e4267dc110a664bc44cac50ac0565d761af39d6590579f5e7e5f95b7f0b5496ba9828f7f08d2afdf9af445ca0401d6e874aabd5ac8b660797cf8e6d29078195c948e3b5609936befeca336590d0b0854015315b7b874b6e3750c00cfe865806ad3af55f8de10270688a79890207e7462feff65a0a45589f38caeb8378f8cd7d962c70b0004db4fc61d53ed1b492ace4da75757ab9b90e61bb3912e9490eb0d35b03bf50eba842069ef1c3acfce0ec6ae60e78851bc09ef2a2ab81053059482ec3d04aabecf1a28ac85fa4332f697525b1e05f0779ce36a1f2d990adbce0d58fe178153d34e28aec1a8b2ab128627a509d7cc99af48376618a7d870c2eb1d373c548239cca0333e20dd0028480107124c3df253782280b8521a0259abfe07fcc0ac0c6200c3f99d5528972bee4ef4e5ca15ee5c936738275ab4321dd7494fc3314f3b781c09e8dba7ddf8d14a81791547c72b2aa22ba69a807d7ce8130dceb12b2cb4cf824d91db24d379964238e758a97016a787536c72d81070d2bb97b065e0c62116472c74196ad17e1be0b8a3268a871400baafb6d77ecb3f43bab7a701b05682e7a3f68fc2c95e87abaa8a9b292eacdc671017d1d0f23b3e0c6b9e38c0d304ae01ab9b9a806e8689db34baf9000d724df28006e89b093565cdb983434441acb1340e6e8085d6470c0d2862aabf4be86ef262b467dac336ec4d05aa2f9b3f74ba3de5ae532e4a7e7cc89706533f3487da22ee53663f4dc8f3cde006a04fa2115f90d1280f089378a03823e9581ea039a59b7af43ba7542249f5e6cf3246152acf683a8c8d96e02ae9cda8e01eb0474aaf6ae45fe8802647f4471c78cc228d606c5209a77f7050487a0c0154ed2fedb2b693685cf5b5f80ac750034919624a2322e9a2ee40a30fe570a52d11062ccaf2972012e225cb26e2a0416438b79ddc7728f0f92c93767159e88dd8aba6e2c975f4f0fe82897e8e74a9244fb4a1956e7cf52b8b4d977b3de77d2179c6d7ce05b39ac29419baf68c05d179fc6e260797c867a2a9aa08911deaf9cc5436397fc966014b4f486601f68f43c3eb7dfe611c10291d8e7909adf1ce098c73f72e6e7778a087f5ba8b2129859af9534a8c8073854aed7648977becbd5b0d7f0cbcadcc98f849556c3960856c3d4805ea03001a6bb47db2811ab7f63e4314b5f30baf18a50d7092b660b0179cda4669a168c21fe782a75a3f8a12005c430eece7b0578f5e5fa03cf487389b1af757d7b0fa9535e83e76adae7352199bce022c9874ce34fdcb1a15216e2add176611dd73a42743bd4cb020c832d3cadf6a49586450ca785577dab9c5b28afc76aa4de9ba5d3dd632564324f0e171dbc37af1411a3907639af4fdb88c0a4d5740f3d510174532a24f7d03a0ab88e307a3e8a9757975f0fb7446e4a2868fcea7a08cdd4cc21dd6b719ab08b652f802624644f1bb78d058b8586e139c050af86bb80b69349fe69d3a9a6e5e630df68775ba6c16c69b4b14b2ca99d0404712d4c85d351b47c4b8392e1c34f72c96b8505ed43b1db6c3e67c68cca9ae778e63c76f37f7d9b43e71bb35c532f4310cb362eccd46ecf2b2f275249b369400dab73b6b92cc27dc99888bfa864e4d8c22bee9cde7b96004da58f4bc8113a7a3128b80da593b776edf6b0b1bb963fd9eba6cc550c6bf9a5e09c1627068b57ed155099fcd38c967190628292a7308c32ec6c6987533e20b21bc8910fc4f2cbf1d02e363173eadb14665c8f6c607f700df8a14d580e10803ab2a7aa0f261fa8f34a2e7590203c968c42f4c175fb928f182de4e115d02fa6f02d864fdab34cac14468222aefe69482dd3724de11638f28a41108018699207979cb80c44745510c385cc208ecc5ec23b80f9d923f043189570983011dc819c4f5f0bd842713c16de90d4bbc902621c6510440e091338678fc94fbd0ddb7dd88de5b1fa377c204b3f87172656207d0b834a7b5ddedc786f9decce2c2407d6729f3f94f00c6f382d916cf493a7dbf365315bdaacc2c0f4b17528c4914749ea0c25582decf3623758a8aa23d840c1138b81e0230eaea73bd70a0d8a01bc536d56d5c31df3e2dce73ac7709cca98410bcec6fd0f604565c4553edf6c8631f53b062f13913055fc5fdfadba0a8a8a9bed8eb30147b69cc4c1c3a37d00155badf471bca459aca37b716666921c7c805d1e7261a589528c11518b81efe318ab0b57550a7a1fc17db8a6196f96cadf4472366479d988ff011ce6673e082ae5a19b68773a71d147fa2d2b4a368ac65ad0a814bde8149ceb12842fa267b8bf6a36f7f51eb54200e1b2951ba5d29edc7af743c4771c44d3b23ab9ba0f68b572304333dc0235669e08d895b8678104ec9b3a3c0c6f9f4bafb2010fcf28d871f7224c92f98ddd8c8abd5939d58706acd603f889591c3089f52c6c343f80a25257b70112715a17461c5bbc91b5d253b0f67bb334acf3d89ecd125a7b2b48e537d4af50912db669cae32ced28f6de12fb45367abf10c341e2d139f034522ade1092255a267025cc061f16276b23bed137c4259979e60efb8d47583b0273799c422502e9d9648e676718383df86899b3334fb45e520a1db0ad905a58b1d5d9370e5915c0d5a80d3efd40c31f83b38e70b9ba5498c1a9ef8e215785acc86f7c0b8b44fadbd375bd5bcd1935ed7cb28d59fcd21b498fdec7e2b77583c33fbbc0921f2082811fbc773ae6896917b7a31df6779747c74ca268b59a8468da69db1c5fe4a46480e57600dff5008cd92fbfa569ef9be951b947434d06e7741ecc4f4c0cfff17599d594c7e4fc7a4522a956f5256d16e1b4e9be017b505843d348cfce719a7729e26fab000330b6f492379da4f9ddf84573f7ff6aa5fb837e6877136437e25539bd8c989ba74f3e5da54c29cd3417700d57f2f13ec5d21e2a76256d20a55cb344da1f8437fba90270d21cd7e7b519d2524398a3df57943a5f563caafb8fa48bbad18f07ba60bfbfd5b6ba56b8a5f98466545ad7f0da6235e045f0fad61260fe099e86717cee1e43e7364ade447a50dd504d2f3f10850ff0c66a56c80e2dba4b8afcc02b8f7d57b5b6c0b0dfbbfffcd4fef4783613aa487cb631d200b896cdf86f009cda4a23ead056585ebf1b8956ebb165386a0038dafb1ff3d3009a855158235ba8ce20f2facc54159ab82b1635e03f73b976caba30b7ce49bb5950da3fdb61c2013b16fb9e902b05136d982ae1e4054a1358acfa956cdca4e2c72b2695586d013870d4f3949cd80ede6ad87a09d9a7da908804529da26663b010f43ab3a3a093e70048513e967669847e121a35f92d8b11a3045dd6c6ed0513996cb1a6da0c97b843298d0c9da2e6f9e50d5710ce672a07a62bf66fa2442ca01af480e9bd55c70e7b29a74ae3e7cc9ce63d5bd488fac512a82ac21dc5de502ff5366a27563dc99eb3a441cd68a45399b4e1844d510d4f83d88fb7fab1284729aee5cc19f165a4db69ae6feebe89d2324445292797fb3a4f7849512919447b744687ffe188248c44f5b7530c2798513094982b049c81afd12d37f7977041009ebce7da9f3db4f381cdb01b4a57fb16678004ccf0989a013b22ebd208daa07fc05ae2157d73facc1f54fe7ca2ea4e9ae4bdd28e239039a525a9968418985948f15a027744602e7ef51782cc3ff30dd913f547de0281a940419809f2e3952b51c09d821e3344147d495e7a742b4e939bd8374036a4e2c0318c5eb675f8bb7c8a8014b641c2a6b23455f1f2e6ec1305b65bdd90e072169b685b80025dfcbea4a2f235c6a34e2bba1788af8ee1876a700320840cfc214d10b5d7e817bdaa3af418d1aae5ca396330f7c8fafff071191f7ecacb77deeb088cc361d22103c6808fd2251c81cf8a603db92e14b6526366cb1612109fd6e8ce7e098c68748841f4ebc75d26f8d3ac0af6364f64b66ee6653b305f34fe25e82881e3887dd7a931058c3026e7e15447c410827b3e047180c49790754fce91da6617b68ec11b91a90b4bf0dc8b1ea069ca882a71805b8ecb542c18ce35e1f1bb82ddd866dd42a7cadd58b57af88aba00cdd135f6b5ce3ab9d0d919099d93414ece8141bf26a9c607a0939f8a9a9483ce93512062b94ce04fe45c4300e5820945dad0e8f1f25ed34dd7a70ba3c973dfae6bbe6b5979e560cb4183c931c1ca5183429017fbe9b70186445746bbab1fc50216269f754491255b66757476ff5c5b20a2d9e0ffdf87e9b9572648e40abff446664bb9bc814740e9e0e710d308c1851a47a7aef5d4820986f21815e7c88225f245ffc78417a0cb9b75286477d48a62e48ae05b9b792c589852a25a23c852a313c852ea796173f6441a64394a747bd77fab195a7f7531031fc169753783afde924fee9744a39613c91a326b3856f6a69f92d05f32c3e8451a15e5c54e3c3a8502faa914c5d5c542d8f8aa922de9285cba3503d50ef820a4fa1680ac337a1fea442fde934fef72d2e220964fa303cd2a47cf91732553b2ba8262ea877115bd29104d3f035ea747a51959ac178badfe22755aa762da9198ca8b0b3529efe6432ad534adf508e34f172c0b2b3e2cd34183f80de7710f848ef721ab88d7f3fed9450fa27e13a6dfb1e1cd91d75479a266aaa54b7719c1167c475a2a6eabacef38cbc4fd454dff77d1f6864646464d4a58c4a4625a39251c9a86454322a19954cdae9b7efa19129e8488d3372c7f07a0b7372b49c6dcbe1382ea7ebbacef33ccfcb19cb9b939484054b8a06e17803262e2da5f14f2a32f5665e7229b9b838470515a0830ea2fe14a24c54a0a888a2445111862614ea5dfe8422534c1c991d99a14ce38ba40945ea5b868ff26084648a891125bee9736ca8f04d21ca84528561380b5133d30c85323dea439409850adff42813ea439483a0a79c308acf815450811ac799e8d2f22c5422ea5d5ac8f434a6e32c27c5c428fe894cbd5929cec6198a0a2a4ee36c361e99cd50214a2b51b31cd48be2a354292666e12c4c31319badb33d3b322bfd1d9c3151927a41138ca64842f566ac228e35e39e06da87dad5b46ddb388ee3baaeeb3acff33ccffb58dd872d4949a046a66000b2ba0f4bda2d53d06d9c7b0eed07476e9aea8ea1fcaa1086ae89b7f45fa8fddd464ee3c01e5bf97d0ff7e165c97b0ef5a7878008e307d49f9e07459e78c40ee943f0fbd20fd347765e22532d18bd0e24bfef589d12caed89e0927cdac684b04a67e57225cef41cc875396637bd20c77124c775473857683849385942274229aa108a51d9ad60d47e056377d449d12185556ee7054f49ea15951cab88e3384ebb57236fc769efb134d6925ba32929bbe78a4a71a6c974291578b34e05e34c8957a4d97020934e04e1f69d95ee1e992e1982d309751ac1987658c2a3f0a8c3121e75589c8447479a8df836489a8d979e152116cde6949474c589c4b942c449c2c982931442114e09ab4cd16cb8efaa6836dfdf5217564985a0d4954ad06199a26456e464bc1d17baa4ee0a25b3229c2c52948453ac68366938a5dc7e1642111e85527458c2239c2c385760c1c9027e2a139996c81479c5d9b7892308b7f766f7c68c23084230458231f59a784d361be2932dc6f620283e69921281f8447c222a21426142b3f19ad06ca46836e1942950802c25acd96da2c96cffa9442762e744b3d16ea9139d6836e905c1fd52574a9960fcde45118c17fcedbd243c279e0934cf7be241612db9354f20c10ab18966937a4d4423cd26159b944d9278a2d96c2f26213ed16cfc09244c29b7179548bd26e213d6129612d09b782952ac124611862edefb7d49becfa1e1245db13b1a47323d0df18ac049c2c992c36bf9eebd97f33cef7e9f8b9454084620f03bf05fb3b924e875583a2bac253a2e6827958ba80aef293c729aedc3534e4aca51b9755e11e57637bfd14a2f3cf21b1046116337054f3b2b25f8dd156497a4c96c0fbee9419acdc5a8a43a4a2a2f12f2eb521729a50b60138b52201881beef4aaf2b47cde63fc6f35e84712e9c69321e498437632da1a4f44a7763cdbeed5375cf9a7d5df77dd76daa52e97edf072a612d61157daaee722293924b31293def725c477a4c9cc6fb1c5a4a07a3f7a9c76423bbf717262526629884a0c9132750923081124d70b228d18431158bc42a289c249c2b709270b2e498a150384938597092bccc31c349c2c9e29b8dd3bda4f7625472555e8c7092bc57bd3cd962702faa5ea0949c58058a31f59a88559460a20927a038a249139c2c1d962e6956c4248649e90200b73004b3929be288f245e94f4881a50caf9802c10874bf2b4b23eebd293be89af09cd06c3e294aee53548ca96854724838220a2965d88948b8d38fad14939046b652cbd1c02f81647ab5d310f0964af7963ec706aac4d20d35d5a7ea3e53f761193e6bc65ac2ea7e84e5167e228b59c9a52c66da29c7f3fe92a1aaf3d3cc698030a6de6956722f4aa155517256941c174aeeaf2839139429c8bd07050909cabc9763238d6c0e32199b941cf7c21484d1058542a13e170f857271214b8fe2bcee847a94f89c8b8bcbb394b8a0bcbc2f2e2ef7a25e145146b6979677974fc134bda86ffccf45fc96f17226d6925b83622d612de19698e0092630195beeb7b4dc6f19719270ae18c116af6b19c796716c19c796716cf91c5b4b8b0bf7d2a2a4e5a5fb171630eff2f28d228c6f315e5c3e6e6cc1c9829305274b17c3b5689f164edbb66da8ae7b8db504a5695743b5681a0b253e6a0725d2c8a6a1b6d3492592ac25ac25e56c8a1448e50da53f210513a66ddbb61615101165a97b17c9ae3b757f123d9c2c49909c4e8f7345e94f2d62498593247ecbe9249e5aa474df827a70b3e1f2a896ed5d542d2d248adcba548b2fa66a77849305270b4e960e09a57d4ca07e9b69df1dc1ed3dcd73f00341ffcff3bc6ff38e70effb3ebffe1eb935edf33ceff49a0a4611e3296df9440ef5df8b2adf627ce4e944a65f0ba97647a8d3b7c530919bc3c91233963810054c982d01cb12a62cc1a89c51fa134b50026ea077fdbbdfe7dae6dae6dae6dae67fbda471da73475cd3fbbbc904a388d1f34013a97a3310fc62c6af88104a5992320337d02465a6a134328561447714fe063e4ad5439512f525904c4b6a77d4d242a6257223d3cda544b53b2a3f9389e37a98d2e164c1c9829325841272de0794c0052540a184264a1b4a7f42094828414949a3f4278eaa9461cbc7697714c1fbdaa6759a46a6b724fa47c230c29b7dbf79b33255bd99c944829a8c93aa372bbf2fbdf75b89dc584b584bb6d9175aee0e8c4293e8cd3c255e9177c4e7df8b2ad37712bd7bbfbff7433d1165275e259f912d8726a2fe3e6b49e93512887bd48752bd99773f0cb5244888e04acf52d27d89db01122550e829577aaf71af91db115e490c45d6928ba34bb5322c358f2bb7ae2351da879ae79130c2bfffb1662c251787efa028dd9aefa0483cf568192296dcb7dceffe2b99c8eebfcb3d6bb603a350d3b410f53d4bc9277a4c349b93aaeb3a2fe5389235d364baf71ec0437480f700208ee486dc97221c506ecf5a727178decc9b79dd18caf4a58872a399b196b096dc9aee4bcf9a6936be83a212f43ccf632d39dd910b49a558b2780fca1347529e382a7a2209474f2401ca1349b002b6402abb0f3b2b9d151d968e0baa7b7ad47df0f42daaed4715f7db188257849a0aa8f444941ec8814abe105542e164f9d2ad6dad7c7916e447a65aa9a15064ba39efe553eff3bc171248fbd27bff814990dc2bbebf2ee5889727a2fcb49b72e5e9bdfb9e9213f8723a69f7f4e066a3e5bfd7fed34820ef5ffefbf4347e0f55ca8f4c4fff7d2f9feaaa420d15a40492298cd2df3f7dff3d4e12ce1517c7f72039fbbe2fdd5aea5266e3a9078b976f390d51a5b7f4be3fb1f8b48729a74de5dbf72c4e8f3af5f01e0824d37bf2529708a827ef7b16f77192424de59fa675a5f7dfcbe971aeb835df7ba71ee09bfea4c2494ab9b2eb92341b9710064c28bbbfef028a60e9c10b8aa7074b648a93e5e2f85c3ccffb8fe33699f630a5fc9e04f2c2bf5edac3947bfa712271b27447304a2ffef72e1128bb4f4318941df8dfe6cafb240c2346f5f4df873f8adf2292e9e64af03f317589c009fcef71b25c1ce2878f93a4d97819be77ca0967a5a92b4ba0caf33cae2c81244e965bf391600b24d3065383f0fec6714c9a745d77559ee779aefabeeffb521dd8521dd83629db142b589adc9bba25c8a39be2349bffb66d3398a0d436284640a246a9fd8d52e39cbc405bc29f308211caedbad65d29352a78090aa9262f84f0020b8b67e17d485cca1595a64fd2e148ac4f372ba56f47321a1170d8a248b72a4e9ae4f8747382f3e9f6a4874f3728463b7cba49c171846405cb8d4fb5241b9f6e339b4fb7221dde6b7caa41319292c3a7da14199f6a556a3ed5569fde249a4fb5d9cca75a91c6e486cd05f0e99552c3a7774a15027c7a8f2ed2b56234233fbd45aa4f2f93fff43609c0a7d7090d7fa1cce0489ede283d95714947f7be7bd36f493a40165992e998030e6ea33de764cb41e2903d903c90e9ef40e2208f36f0864cc1b100643ac6dc20d3181b64fa36a40e64c7649b02d6205370a441a663cc0c328df91cc8f4656490a94c0dc9045c912938d290e9183343a6318f0399becc0d642a13839c6d36b4269ed16643932143a632646c2053992700997ecc00c834a60652002448922938aac8748c79328df90090e9d3400280f4b06c36b6a2efc96643c60c642a4346e65364fa312fc83426864cc7d205098e2e643ac68c641a437e4f58904f6420591c93cd8692255a1798d79e3b426bf11390e1b5e722e0386448e28afc667c925404a362f1a3112258bc7f8b0f4ba79c162c58b02055a3aaf316557c4fb81957c2a8acc0e2b2952c54ee342d48d7645c545e6a650b5515e57d1954ed1ecb9a747382c3a8be45e5c2824cc10bf97d287f27595c91b6a5dcbbe52d6f6c3638cec90d274e3827475b0ced41701c6362fe65647420388e3131ff4ca66c31b407c1718c89f99791f18cb618dacf9acc9acc9acc9acc9a80638c6624c3c3f2c9902123f31f13338e1e9622101cc798987f1999ef8906464f39272577546a973bfa3424ad63526a1a134f7b8265eba67458ae1137e38c3ca352eb3ca3adf429da49a54a69604cc727272f1d4b2594479afecbb185aacda4aa91822008aac0f2fbc2fd9072d8be184e18454fe4a61041f07ed774c30d051cc7711cc71521952928459c4204b96ddbb66d1b398ee33c4eca6643143991133991e3c42944502c8193f22546c6bd062983c80b268c373419edef3674c44979c9b4f45e0ac65b76338426f0075368a5dc54e98b93fbe9cb94710629399c9439361b9e14cf9322e55eee4a605cb9a212e8bec8bd2765b3f1f2048462546a2f52a4a49e958dc5912f4c18b74f5fa6949af602967e90063bb8fbc9fd03c2d5e1c2c7db7277197cbc4edc7da36a4c7fcfc63de365b97b0b1fef8cbbbff8788386f2b9a2afe22016965b1e74bb3a59588d998362bc46657ecdeaeff0d9f5a72a635ffa63b87bc95d3bdd3dc6c7cbe976b5613e9ebb5fd7bc65db36ff8affcc6b2bdbb9fd954537de6b86653506437fa3b8ddffb3a27b6358cbfa3cd63c779fc1471f7277171f1d09771762559f3607b14fb45113c7b0a9db9599835a42b8ad315b5d63b2aa8a6b885adcba50d5ddbb8d73770d47c9116420831e251752948841831f20d19902073c3e43a8410551cd915149e146151642e00a0c4dac10a246c004a51b640c11e45085c50b09acb698de51034e5a0481128046f8a48044e85266b3d2b78f0a4e84e066c3f41ee38997250e4184f61e39d3fe86e183e157e2bea429e13efcab1a02fcab81e22701554b7058db97544a8a2e6bfb2538ac1fb8179f67fbcb32995e14bf64faa2cb2a2909df04824ac2214a7f41728825784a7f7f08d36ba308e37b9c1268fb141069ef894a4c5ff4acd27b2a25e22f7956e9af48ceb6bf7f49254310b17d899c6d4a963c4bfced848071369bcd58db8baa2188d8de44ceb6d7ee89677b4d2ca98a2eabf4a0aae8b2c037a9942cc16195bee8b2ae0ac60fdc8b8f80edc3f7544ac02092b0c4fb43945e3584c9e4a99c55527d7f593020301af131941f09940222ed5340c43df71b8c1fb8d79e87d3d1de53e96cff5d92ebc920843143828408edb727e23ef7489640a2a4e8591d4fe9bb27027cd33b9225cfea482444786f228b9eb57d892402248b9e7589f0ee77e4f6fe4478770d5362488de12e59c208ce124e49d1fd2531e38521811d4270b3a13debaa8a963082536e459c92228e5cc208ce92258ce094409b9f8cb894720b224c650981bae734324d529249b66b80d08d6c9d0a2a71401125102fb5d7b82b038ee3b8bff702dde77e53a5e07f1f8648d2fb250f4990b6b747d26d1d92d20492e9f71bc8a9803a12a8fb2465aa3d08049246b6927b8edbb68ddb388ee3b6ad454b351484aefda6a98c845e7ae7de2383b4f73c4e6524a604d2482f4da4112465e96f894cb94f41d20892f22381b607f2f79652d39ad8a094f7431074f7387f4e5bc22adabc238ef09a2431a61e94fb29279b0d6fa6c5f0d734d6acdc349577840845025250524552d93d4e12ce15d7ddf3489c2ca0e324e16429258157949bd755a1c978b30f8cda5fef8eb6ce63c2813d4cd96cdc77cd4918468cdd7755745294ce31e1062a6a68325accac0c3b31c7b6dd80c2784bb147b9d15085e3388e4baa526a7b074d46fb985a04109c024e01a7805364ca1b3bf81561382b73686028b51aaa800104bbefbad2a735985431492fae30a63554f15e862633138698a4307c610c6128b5f6965f4c5252a9fd7df942a97d5743952a1b594315b0862aa5f620088220784315f1891300b895a4986653224da6fb7ac2e856d4fbd704d7c94de296be590e2d69e8c68d1b2fb53b721bdf83ef6ec30341174e4a910a2a289b30be48711a2da6c9a9c9685718d398265c4f1305dc920aa5f649394a45a41729dfcc696edcb871e3c68d1b00c0c26d250a3029bfedfeabd438f051641a2bb5231728b5234c7c54316e9fde243499214de626d1030910cda9184516a712adfc75726f8812916832420881124293c15296a22af52baca47c00b92521b618f74bdf95ba2f956a50de2f9534ada4912900dfbf5053c1f8a1bd97df6bda89c756d6a0c95c5667054b795f88a2f2fe6956defb31a941fa31493561bc1f13a7b9afc9682726261254610595842a5aee15abbc3c5832c9e00293a3c52da96450b9802dc616f7b680690103a585f8bdcc664c5e9c5c3125c578a1a0c413a168321b2956d1645abc6f366478df6cb878292823cc6f1f8a50361b97e59ee74c68329b51777d436955349b3405cb162f832a1dcb16efa24a63ca160faad22f5b90a967546e0fa36ab9d213a168369aa6bd68b4dd2946ed7ea24c0b1898d22907fcdbc28369a14a4718923b959c264c753499ed15a004b21bc29863eb896936daf6e79d6d2d3e85295ba882745be8d7fb928b4fc2ba31efe27962de858b8fb91ac9f3e2633e864412f32f5c7c8c8b2a5c1c693652b840d26c60fce655f1327b51a2d914410961a8bc6fd1e261546912af458b169f638351f9d064b61650ca4d7b51f252d482040243fbd4bf78172a181a09e307171ff3455e7cea63de454af54215a3e2dec5d16623e65b5a5a7e8b5179465b8ced5da81ce9a5e865e682d1f427138b17cfc573f1de05a6c5c3b4e8baee28d582f1bef77d2d3ec7d602e63f98f061542209a65fbafc8b2a8d295dc8b43b1a5b5e66e9cb11a2aa4df1adfb7bfaa1951e10cd86b3622e115c12982d260baaeb292674e4104b987e88ee51ef9143787fe2f1fe74f52d4dcf69362e4c6a096352da6da6cd6422b9effb3813c77d2a24275670dcdb786d3138948fa70f37adf426d2b7181bc903f3a624ac30a4ce85cfc53c07c5cb7b471ed2a9288c51897f3f87e61d852f9f9e8abc234f0a0f2924d5a3938e92d29b3e4c3161047594c0bc0b18728825784c0ff343944818db97bec5b770410e6122616c248c1f5a7ce98bb4208bc0bce94d0ff330a4086564c1e2bd231a8c9630ba3c0d46459b473a997648e511d761291243195469925286df4e39327c286a9ee71da55a306e20286eaa500c1f0c5f930969404a81766150ffb148bda3a3544415b5708f525181646b321ce759f192349b23cd46b3517a8f5152f188624c69e84e391e0d489d88d4a5341645f774d35b549e8a58149d8a6808ff87567e1fb228d24e22928824562162998948482252c95d6d2c39a7d14e25a7d154de91278566b33d926705969726c3a99a0cf7dc555232da627025524c69614cef518a8a313d158164e71df9a66ddaa66d3420793420899f764928d318a4f68a504aee43902cc2989e8a4e454db83f3d014f4994dca9a8e47e53899b7669400a953096dec4fd1152e97d1ad364b847a2c9702f42d96278474418531a908e4aee69a0a10a1ab068361af70198690f6e342095dc832aef4e41bc4f359204cbcf35e7501f784f391bc7a522f7aa66c3595c0d485a92ee9b01059705c38811697b2b563cf566be6de0066ee0066a1e4a75528561e8daa987f72edfa9bcef8a5c0655088230ef9b0d1620887482f143cb9fbe480b29c31741892d546992b2c57fa79c161f8657d3b4ef384e0cc5e754690f462d4a2f030c8b170e49a73bda621025159dbc990bc6fbde7f62681a552ddd9fba2351657af035cf4d5b890a610fc6747c2dd584b14c1961fc11867b19ddef6022614a998f4c6f94369ba7e25c0ba9a3310dab74553aa4d0289472c9b4439a727ffb242cef6ecfc3fd462259e23e92ed39d2379ef0b9e74824dc876412d6970ac3b8bdcb1317265c94708112568942b3f1c2ae2b5d93c974946ac1e8dddb8311184a1146a1c984475d87d4b910a125b95cd781d06bf0299be653c454138478f9f00184db524818533195842b8c9d4ad49294a92b8cdc839f84652a912089e44b592155658c99a1ca98be102f1f407c709b6fdc7bf9dc6bff5c0def019af657b82b57b82b57ae7057ae5cb9c25db972e5ca15eeca952b57ae5ce1ae5cb972e5ca952bdc872da92be015f00a7805bc025e01af805740eec3d2e5aede48ffae90b292b292b292b292b292b292b292b292b292b292b292b26285155650410a55484de1ba6b4545b31452672585c49ab1966cded66da0f6dba66ddab66ddba6793208356d4bbaa2a67d9af67d5e769b1776dbb66ddbb6e598699aa66ddabf6006a631385970b274370627cb2954a5dce8f29d4b0b87e2ae0bc9715cc7054d66fb2ec97b9369f332ed210727cbadd1baaebb9f38c59863166ede154fbba29451fa134780a27c0217ba2334710a622aefbda721ac255d8ef6b19688ac196a23d35b9e7c30952792b5e4d4a3f39868329b878426b3bdd724c7166acf9d72584bb67bef6f292946569126a3fd3ddddfc4db59b171a8f70f97f8c81c33134a75527d25b8fd5752dd8f7426e056444b0fad245945dafbb3604c0300450c80dfb856a69618ef73a5d7a0dd2aa494dde9e34aa95418460e8a66d344c8c47f264d14bba3540bc6702c4befbd8693a5a44a7b30f2a0283f55a7a990bc1b4a295d00dc164574924a5d31a6deec08cdc6bfe8081109502c536fc66489d1b950a6de11de6c2b72279a91a3400343e8a618f7042327a35d6d7b2da61cbf0e2c5933d692920abcefa9b607841efadd92941f48aa522d18bbd7726cdb739a8c15655745e99ba60496727b27a5aaf427907045195ef1c31299c36b494a29f17dcb461ab9630855a9168ca6d778941e34a9ba2f7d2a8ff4108b5605cfbbda0c4f8a8e60d2594142065f7002254cc13082359b12fa66a3a491308ce84c1c67e5c9a7796298f284a2279c90f404921439664270e124ace26274aba4619530742b6195b04a67650a2e469d15172317a3208c61151723228cdc7761950e8b8bd1e55217230f89f27e8a0863da6109ab38e16840850a152a54a850a142850a152a54a850a142850a152a54a850a142850a152a54a850a142850a152ab317e75277e6a228ce9ce6def672cf7a99fdf610f860fc00fef63ca095cdcabd1ad743d33670e3c61e5cc971dc87ea3a2e0863fa32e331e3542fb322f1e5ce949895daa78a30a62928a2b1b67b7f8b998120744d0c2d5e83cf5229183d272628b5279b125d4ec7a9b8188c5b0a0ca3a6bdf6da6b0f05091e5c71764f3a4a2000810ec9c4c35d86fb4e099ae907f718dc7726ce643291dd514aca785ffbeda65e11a4372b4d45789177c4f6a95704a895a0894c4ba407925ed19663d35efb254e38e18413dbd6a409c7714e745dd739e19d524e38e184134ea49c70424b39e184134e38e144a94bba3548e5207583940d52384835f179a06f31b66f23491f448912254a942851a2448912254a942851a2448912254a942851a2448912254a942851a2448912258ad7f0a16a1c4f2aa016973fa95e5e24c50f5fc8b4b30203238a24e9df79dd763a9d36d3e965383d0bcde67462f1a798074f4164f83f3de7e24f64da821cc34785a12afc934a0cc37f717a195a1ef5a1aae55d54e991269e0fe29f4818e19f7e8411fe4b48f218c917323dd2a444b97c8ecde5135ffe44a6a2da596979182e248f3b86b2850c4f61cb8bcf9d80c07ccb731f92308cf042558bcb879f180ab92d64f844cf159badbde6bda2ad1c84b39acd951562e6d80eaaf39a7b0cf10099f9a5236b9a96b569c2fdc746c7b70ce63a37f7eb9c4d8cfba94392aab2666bd4410a77d72a8a773e5b39fb987cd4618a3bebb3ea00c5ddb3b9b26b0b23a1c376774d948174d66d5ef38f02bccd402d2bd4f2f16175c89137d641c6b50ea2ebd78a8e35b8e03ed690c289d4580cd780026f7ce29d6135983882aa49b87bae7184afacbb873ed61872d7f258a306d6644db6a571c95e34ae1a3dd610ddfd6c6d56d5230d2fd09062cb622bfa27665dee86473862040425d347194c68fcb0325716d56bde43f959214e04948b842b3cf071460e275af15e739b7d7cd0f3f1e720fc3a735e67885a169661cfee15c5ac9a5953a3af2dfbec5ab1e9ee43b83b1277e78818a03dc081f6c68f373e5f13612115ff9935fad2f8f5f96457dc93652a2e727393cf1fb301ee7e84c8dd7170f71be4c8818bc98058567523dc9dc7c7158f935dd1765fa05146005cb72ba276afb92686613552ce160bad42ec8e33ebb566e544b73447ce4d809300a724c69d9b3927c4a123cd0c942937c845c0998d3c8e7365cddcc6fe645f3bc32e07b4ed26619cd9c1fd64cd1616ae20c51426709d196d778d192b1934ae9326a3edce81bb17f111871ddc756bb3924173aeb8150af2330bb16dcbba4e14ebe4cc6365d195d631d4c43118ea83aa3118ce42bf65ae9fe9e959d93fb3c6ae8fc164aec7bff34f66cf1976e757df134337bb9fd52a7e9d513516c3df6634bfb4ecb3d067734575deac90efe9e9791c3170781c6d4683e4cce36f781c3e679fd6caa25ae679f818ba5b3bab3862c4d02d63aad86cf1f0bfb3dab23cbea6e673cbae3fe8ccd6b2b6a7a7e7579f5ffb67454f996aaee813b97e26460cdd8f0ac9fc1154d310a92d5651d82a6b69f6cab63e2a86b57ac519f6fbc7af5499a96213abb29fbde6958da1fbf1796215bff076b1cfeeefc9d9a7a5e215f5e95163e85665b0d7e86bf4c5f33d3ddfa29a655dcfee2763e87e15cb768b06919fb72c0663cd67f7eb1e15af6c4f9b5135b6515506fb9c7dbe7cdd6634ef681e221f74b3424602686145e4faecfa159b7bc5688b83f48ae2bdb23ace9d3567af7967af79e76cb1500c888d612dcbe3c4ac13b35414f3d03a9ba66cc7dd5f3e120000a552c974e6bd5158fe047c7ef1944a2593bbc37c1c00084aa59269b5b3aa964a25538c1a1abdd71c946bb4c99e342b8a61352cba5a69933d6b6a6e746cef97cc0274021ec66e2dd3512a954c3c2b1d4487102bfa3f2b7ae6d7e8ef6ff36be75710bfcab9f551511c847f0fe515bfceaa4bc69ecf6ab5fc554fcfd9b2666ebfcd41f9f5fbb0dfd3f3180dfa339f4fa4aa58b66218517eed57d1bdca3e86612bfa4478ebd7f9e794b9fe8730eccf7ce66c0ee5dff8b5b589c2f04a746a956df7ca9a2f33f55afe6acd41e8cfe35fd1bd66fdd9f52c8a85f06bf4738b7e96b5fb35fa2acbba5ea3affdeb0faa9f68a32d8bb6319ad70ffa7afce79a755eb3c6e6afe89fafd1c79157f637fe15fdc9aed7ebb7ac4f9665d90fbfd2b0bcd1d781860e3ad8fc8a5ffbdbbc716e7f9e5d5f886d7f5e67984c6bfc39885ff5f47c0fd0901efcd9f55bcb609f5b9d3736d15ff36bff8c8a6546f2e716bfce267aaee8b342fbc7afd8df1aab5bb7a8cfb9e29ff95951186b66d7fbb0dfe6f3695676fdfce2f91590c9aaae9a6f73ecf3ca7e763d91bab2bf62f377def827b73f3ff87fa5cd47ff5cf30ecb03338538316b289feb8f13b3360a4381b439684810e343e1fe33b167f7f3d044b029806df39127727d7e76ff8ec1f2de98c746639bf1376e82e810824627880e21fe6745b5ce26dbea68390070dfaa04a009f7d5cf8ac6626c7b3e91bad7aca2fa57bfa2fff908fb373d84fc1042a40c62cb86c460a610eb0fad7f7869eca3cdabd010a2ecc310bde6a01cadeafcd218c86481681f3f3451f6610f6521accffa43e316ebe08d77e8336f8d81e8fc8375fc0ad794e45be8dc1df59106d49a814cec5ad19789613479e3add915b77b2827b7b28dd71f744755b10ead737b9e656ec96e6e4cb4cd6bfe41f58ab67b28e7cc2b100050dd6ff8088099369f6810dbc6f24be84f9ccf273a716e55fc2d58de9a488f330cb9b71926d32b6bfecf8a9ab233b7bfa27fe6150813fd61816c60061ee30c337ff312f023dc03308e7c28d3e056866134e1ee27aae2a0f5874ab575fe41cf5771d68f89c8ca15f5d1e1ee2deefe2385e3ee44ae3fb3eef9417d565647cf7fd6326c66d77f7eedf7617fb738f61a7d9a9abd6618118eb570ea74f76f599f67d78740124fd0edca85aa3431983954a36bf4ceaa2acb3223b19696a93298cca479110577a7c1c7174f8c2f96bc48e2ded3f39f7f70cb6efdfb75d0f7f43ccd3ae45731f2dfb05f3f28ec867f99e84b48e675fb83c2746eb16ae6f3df02465870e28127b439886d65130d6af3caa2e79a635aa6636c7bc2643ab7a7cabe746e5f9b089bf96c6d136db7ce6d4bc5b2130d42cf156d0d61582b0633875a79e3332673f7216d46db2c33222488aa62736b16d6dab8d5a23223b97577202b0ad46a0d61583e82ea16ac95d1186ae6b6cde79a75cba2442d464dd427afd9a562183a84617bc5423908fd697d30c04661a8dad239fbb8fb8f9d814c74ebd61086b9bb8f136d11b55966c4dd7bf0c8617d56b4d5b6ec8ab65614d6b22986b5f2c6ad9961786dc1dc7d4786e1d5dd75b83babcc81b3a25bf700d4b2423baf6d96edfcd3e2dde655886d3390c9aaee0e63f3d0da68eaee3ba470dc14c0dd3546d5d24463ac89c6589e9d77f2c906d139f3d87927ab2e9c372b445d8100325920f9076b9d83ec9655b16e7310ba02a1651bef1d55c53adbccb1a19cb595b168b82c510177592205eeb24413c6e02e4bd0b8cb123ceeb2c4eaee2fdc5532a4dcfdc7e0eeee8e00773f808f2f45ee2e845b1aab1939ac64d0ac426ceb7a69fc7a39f7bf34f6e12e0047f9f8e82285ce26aaead6cf8ab6355966249f2d3307b9bb8dbbeb50c3dd69f4b8fb0c77cfc1dd653871f71a775fb93b8dbbcfb83b0e2e0a185d7462b8bb0c6883bb13c0bd0617d1dd4917775789eefebec39f284caf4014c92eb435b54bb61383a158d531849e6b8ec5ca9d77daac9e2bf69177b44c8db5b29d7395f964222b8be610c9583f2b7aaaac8fbc31bad1f5c7cf8a5fe6500e9173cda6ce3b86b438ab3a7e5654b3aa8e33ef5836cd526799914c64cdb1216a5e57fcd2219485c460ad8f6d629eac5736a63311a236cb7ec86ae6c9416836339133efc8416dd699c8ce5a9741c8daadd71faa8a75cc088299428c3eb6807fb3753e7fcb585415eeeeee9e8f281ea06c30a292d83ff33730b6f5f999bf69b38f5e71bb5ba8edee42b83d5f1b1542216044f1605f423fb3b3d6b8954fddaedabc63f8c42d2273cd6d2b334f17382171f7bfd9f825f43368abb56253003e9e7cb8c902c16891f507cd6d0e62553e9e60dc757e565475a13ba48fa2137c068df38759eee87655a36f6e5a76cbcc3c945b3073a825beb8d9bfd9d5d4ed8a06660ed5c0cca11a1a39e01932ceb3c665c3746197ab460e26a691033e0b40d3e616cc1c5a5954b7396865cdd6cd0dcc1c7277d0c7d07477183e863efc6fce1505fa99bf59d11fb4653f07a11b0bd1e58ae2970e2d7473b35b361693e95686e1d6476bacea78991bc734baa3cd3af964cd7cba7b0cdc7dfb689a80bb0fc047530b77ff7c342971ddae6a6acc56d7e81c846e1486b6b11a9b1b3568641b3ad4d0c0363564c8c03eae9c69cca851c387266fdc6eed92e91abd652c90995f66cd467f4cd8dd6bf0d1c483afcebccd1cd3b9d5e87f8eb1a6fe15fd96dd660efac7efc3fe6bffac59dde1317350ab0584b3d0cd8debe6469bb9c899cf55c6b280700e42336940dcbde563c908dff8070dc259a6e2a0efc92b2ba4e7f1afecf7c49089e1566895dddcac2cba63437afeccaa8acdeb0285f2da0131c4aa8099191e7ae0e1630c9da8ccf6c9406e6ef6eb0725d20ed93e19c8ffffaa8006fcd089b6f2cf995b40266ef7bfcc8d615be0c10eba1828e1a1079fac760fe06187dfab8c351f875fb3dadaaf1fb46d9d2abb29b101e78a7845d8fa7c1e78f818326b565766ab83c8f81e02e0ce1581858fdee8f964d727da315876f71b7ceca470ff9b7cb2e667d5855f26ceed2afb33b779aff96f54bcb679afe8048cc8e1697cd4ac9f071e7ebf7ed0fc5ae59f331f71f704f8d8bdeb76954f76adc9b9ddae1abdb2e836b3aab2664d01700e3635b00d1b1b9bb300336ce8a0838d1b3a601a260d1a2e97cb358306c63af8cc982123fbd8a831b796b53507e0f9a1a3d42975fc0e3f74a27f736615c53e6dd61abbbb8a612d2b249f6c909f15ddb8cdb12126aa312b6f15fbc83074afb2b61573b56cdbe6645576b241f2cf89b6ac33af42437c58b6d5d127ce3e6e6e72cb9aadcd9e18486563ad98ebec61073317e9e1866c073317994143b64dc1faac3f708b75346e71ac1573e913671f3368c85616dda86663adbc5b3606435b7b28af2f8d7d9c386f9d59adadb3eba5b10fdc629d97c6afd689f34b0890c9aa2c22ac66226d76c1646b0bc86455578b08abb96d6d1ff88ae297d96af389f3ab1573edbc73fed879e7445b3bebad512044a5cee8465bf6b565d8dd61dcfd06ee1e848f9abb6e57a78aca5c325633563368589f15a5c92a36750d8d1937cc5c00d7598002ccb031cd9c030d8c35572ed96ba33f44edd6e84f6e6bd89f169b352a3659b5a6d5b29a33af3fa8d6f837bbfe6b639d1ece1c8ba1edd637308d8502270652d91813c624b6bb17b941dc97f1033cf0356b9d5beace993746db8d6399c80a44ded12c90ad653bce2cc49e6b8e07bae00199bbbb0700f063080759877090150cdd3afff99b15f2abadd1a19c63ecc772067a76cb58c958cd087af6cc302ce457e7fa33837756f65f8ddde4f00c11f22b2275657b7ab4ac87dd4fd4e213d53f2babe29e9ed733dac787cd276e338c3565ac5ff5f4c8d8030cf1df430f3be08d77fc8ccecff4b0c3cd8d8f9a777680a1af9d180ef23cfcff37e0bf657dfeb38ac660acc9ae3d3d3d392f805fbd0ffb2dbbb51a83e1a1fc7aa3abec35d1464f5466045d7fe420542d6339fbf0532407e71d47142852c200247adc1dff174162e6a5f1ce0017dc574ec880d1ca454506869490811b3b8880012872103a628089fbead49a0d1818805f00e9025114d1c40bbcdcfd7e17b8271326849818c0bd880f311c24e79687f51688a208208ea01a26cb3a0855712b963390aa4ae0010ca88013cdb6f9485671569fddaf378a9aa88f8f4cff996f101480420576c8a0c18019442b7e9d612d7e1dddaecc1c56335634686828f014c871576e87509fa7f94cf340267efdcd901d43788836aaa25b46013ff3fdc1fdeab8df21ee57c8ed81011c5481e2f671b07b65df0593b5e6e3d7f9a565b116bb57b475060571f7e137ad53455fe6cdc9fdf4710245ba5db532daee71023c196db709c3b9ddfa33da6ef70bc4876b004fdd1f3dc02924f072f7197fc3c7b0d93a5536864d1fb7876e573482223013c1f3c0c3b73273eb892103c4b634ba8aa11f498cd6cf68190220f91919a20cd42272b55a2bad756e7f563407a68809cbb663118fbb13d9c42201bccc8d613a3a678b4d8dee287778180180252458ad2cbaa29f5b167673d39380cf79e7573d3defc36a6c7ef9abbcb2333bfef36bb3eb8afee4cf42e78a7dd8c7dfb23e1bbfb44c28f6aa8a6efdb9c56dc642af1384888f5fc584fc4a9bafcf1c84f59f2bc6ac7abad82cf4a7c6421ab76a6e61ac7ee9185ef5bb30aae2ad316c2636d36a54a3b1cf3ef877d6e8ebb70dabceecf91b7d99689b61f915a3313fbf76dba2b087e58da267de0be8e949404f86b13b3de88c3e5b5668fdf12b55d3e8bc718cfdb5fc55fe67f7caa26d0ee257196d77cb6ead6dd8686caf16d0a351dd669d9e04ecacd967d79789f3fbb0d94543a4aeacb9b189eeacffe626a6ffe65799355fa3276b2e2067c7101d37372d55cd420958404f3e59f3a6270159e86768d81fd9eb1c84feac2cab5f97bf3ad58cfacce8c73f5e0f21629d79a32ef4c5b63a3f288b6579cacd0a39b32a3bf3cbdcd9ac1056c8b9027124094798f09e9e473970e40167d63737f8e6c608222b687a8c4440b8ab707a744bcdebce3f3dffb3a2aa7a73330e61c073087a76b7b2f3738c68bf5e7dda1c939d3f5aab9eadf3c66a1156a7cd41193d55964585f467a1bdd1b6c52bfa2f139b416ddec9419015aaac731bfbecd25ac62053b7ce4c84d53607b13d9b65db560ffef3b34b668495cefa7f6567702b2b573fabfcab35ebc71a056a59a12752d5d732bde6356bf6657e2b630ff02bdde2580cffc9b6b11b6ed85828b37ea5630fc3d9f9e74477fc2ac3d01d83cd395e809ee29e35deacba83011d5bfdcdab56f6cc2cddf337da869ed151a7901180bb60808fed11eeba55b1c9baab158840cbe5b0ca8146c57bcdb0956dd1a035974016826c8409ff1b22438810c979954a351a618daca3111e2340fcd59aa9f99b33b735baa5e2cdee9f698d46649e071e788869c1377ae6d890d80eb11c62628c96ccffcd1eca337ff3af5fd1af408b1582bd59bfb06ac17dd583b22a116e65b895e11587712db9b9cadafd335ba32adbe623ac4683da7cae2886e5364b7a92b5d853a65b673e829a78cd195033e09eaea8b64183460ea826d243b9855b196edb1ca0213944328cddc92b2b84e772c00451a0b89fd9080ed585b70c06435f63a1df28ea839e2cec739b855ec568bbdf077d198b06e536f63eecc7d8fd1bff99373a94856e055ce7adba50add9d813a9ab86b5f87310ebc29f5ffb473e5921ac1d196d79fe5cf1cd8d0b266b75dc22c63409ddae5a76e3366f34fdd959b7328c3ae1ee51f030a07f7bb83b912a2386b786a15bb72b53a3b01a53a3b0211f7fa2d059a6355af332f326da688b6335bf71fb59e3acc61ec799dbfcf383b63b07a17acd3ef97cfc6334fe18b9ff4d57e567a6cd1bf5a9726bc2198c3e3b2d6bde15cdccc5e1769703b08b84665f16585914c97842e125dbe36756d4e75799cfb342aed089f30b3ffbf3b20bbbddc65b88f347b30ac778e31deef706f71bc3fdcab85f1bdc2f01dcef00dc6f0dee5700ee9774bf2af7fbee3700ee9706f70b00f73b83fb85e17e53eef785fb8d71bf2edc6f0bf72b83fb8571bf2cdcef8bfb7571bfa3fb6d71bf28f77b72bfa2fb0d6fc9fd82eef773bf9ebb737e3b0feac11b9fada0d6cebac7dd870b0a5fe556f6b3a2ac0e1d2e27fed2788713e71ba3ab0677af71c0ed460c8315c53ff835508b612f73cdae5f65edcb58a2c7c1f33dfc10868d18070ce3ee3b13fd76f71bf4f4f4e8e899e989c2d8c384bbea7c225595f9f448c0ff89dafd2cdba335b6acf8dfe836ef0cc32d8efdcf8a0ab1aacafa9cbfe6ecd221c2d89ab96b15a37bcd322359ff9ab3d0ceaeefe164d136ebc06430744583ecf05be3acfe6ecd151d420b66648014e6ca9e2ea21d83e5955d5f0761808cfbe3f06c9b8facecaadbd5cebad5e6cd06d9e4b09241f34343b4512093058237ded166d4e78776b5f0c63bc605c8c0172084bbaf8832100edf73fec831221ea2ec43cfe717510622ca40b8a7b5b23d2dceaaf97ae79f97d9e2ac9fa8c52bead2e80bb1faf5ef58063273d0f93eec8f5f691bf28ac2dabc5dafaaa850d67add43992803e93f33facaaeff59d1df6b3ed1375118bbb266fe9f152552797ef5aba04c947d38d5986c8726ca3eb042842000bb6fb6bd69b5840cf9b14324a7482ba747496408cf4eab7573bd50640777185e1fbfc6af37f113b53928c7061f0f204547cd4119488ce5d1f907c76238bf6e786007f0e1ee30b6e571a238b60221c4aa6a59c401604621a8e0ee3aeb769585f05e6b1c434fddae749bd1b6c5ae207482600531630ce285d99af99b9ff91b8dfecc46cfdceac84098e7a5b10fa28daa78b76c90221b20ca40adfcd3ca62dec2cae19b15f694ed1021124490ed83be7676c04c2174c04c21748672702b2b712b2b7774943a9af5597ff0ec5071772140e1ee41707720b8c62dd61977b0f81d77a470bf23c0c10a23224fdc200aee7e5d94c65d0320602082bb6b2e9407360b50c8c01577bf4f48a0021d16369862c3dd2f0e06123084490440d084bbdf0954210031da6e0716b8bb062407270a13b061025e70f74d060b54b902079a34510477f73ca081a41bc54d961cee7eabe8c205905cc10128d87077ee033d5f075e4148a970f71b856b460f1440012353b87bcb257bb53451f661678bf10377bf3ef8180c6f73ab814814ee3ec58765dbd8e7d7deacaaff716e9f10c2dd7de01e43c6dd5f14e023111eddaed41a1a413566ab77840cf9c1a3430810066809510641db33b77276e87655f3d2f8b5ca6e6e7ed0f933ef98ac6d591d278a7574ce3cda1508bc775414f388c570cba33310e6c9bacd27d19951a213ef72084c88152dd9dadaaa0cfbe4d36c591fadf7b9661d4407ee3caa8a7500915b998a621ee79a77cecc23ab2ebc02915517fa63b3427610ed182ca7655fe68a3391cdae2bd6a14fcc62859c422c90cd0ad93948e671ae3296c766856820d61fe79af5cd0dcc1c025201204533343f5af848f241031f50b4157b28b0c1a3f18258fdac6c2bb4338cddf99576c9f4cb5c71cbf298a0319969b2ef83feceba75662da3617be9ceee035c132e6c21864c4ba32ba20cd4fa5951d5360620b81c1b1c70f70d801f902d635f1b15dae8b3fa67f2c9ae796b9901dc5d03ee8e4406dc1d034c94187392dc5973cd9f5bd627bb84b21011e434b1a2ea8a7e76158932e69cab96f529af02dc8fb0a30aa118323b805415ebc8e68a55cc627dd61f412c2c1339cf52885016ca42744040478985e49faf94d1068bbb5f8089bb8af5467f5aacbe2ad3b9fd6dc38acd673f8661ad366f15f5590199d8d53e919a3746cf0c83a13d3dafd7fc0a027f86b1e6b31b8679fc6a9bb96d5973013d4338c8cd4d027ed559fdecca27faabad31905ffdc738f3cf106d7495c160ac2973e621d4e767502d9479b02ccf2ac4ae402b1028accd3a9b15a2571c846579caf3c7c6a8c96a5db242cedc66229b15d266a23607b1a6467704819942641fbcd38511a70aa7f1f1e3a5b18f18fad22d58de3f88dabde660209a28fb70ee984103881d2f8d7dec35b758276bfcd239b3eac21add21c40af9e19389643586b63c2ccf49947d3871f6611a096204b6c35cb14ceb1f2ade2c8f5016c2eaece493d58137de1183b53e7446f30fd6d94359c8f9a3cdabd09a8168552d35de78c789b30f1d5bb33e34508b616dde388818acf5f1d2d887caaa3bfad471c660a6107b078813ddc163ebccda1a0339f311f45c59968e7ca26d0cb6e35c65ac8e96d5d1661d7395b1eb5ae693d5b1b268919f153dd7aca3b36aae40ecc81bef9d9f155d715056331115b328917ce2f62706c488430338547701fcc6ff2f9457fd42797d58d61a4760c44173662338842883dcdcd4b0917d7eedf555d5862cf45af6da387486a1fb89320cebbcd9a0cf421f635935bf368e9d77dabcd922e69ab7468772de2b6eb1ece7354af3ecfa1afd0702a4f4611f264b6f6e4cd407dd5a66001c3b57dcf630734cb786f60fcfcd8dbeb9a1a1a9d135bacd3f78c5b0ed83be746bbc8103ff8d9f28033d51066aeda1bcded0c08ded7ff31b7f8c19a20c24b375c0dd5d3eda1843ab6583055975e136afaa2c68d5799fb945753e5b3beb73655d2d2273679d77a6c9a8144e71a6a120644aa1991148000000f3130020381c148a05c3e1984c51d3a898071400098d9e5e7c481a6759960429650c82841802000000200020321824000f5fdb4d80ed1d498335ec2374a082a494675331829f5cf9bf2adcdc521d9c7f0d6deceaf4f6da23385b634579c1d1552115c5896d3108e7d34c6a204edcd54d9ff3e9b5139c2fe01fd733b7a7f4666f17ea3dcc9f00fc62e05e4c987f8a200b5f6bcb91ea0ce3fe2a3e33d323b7d5b22fe8e36076d0d7a95c0993202e045e5c3d018223d3d49de801bf44cb66e384930b606587676c99ef900992ce2ccf255680262dec8e395bcaa0070588dca909f8d17685bca8dbf1cf21300f72256b73b521e0ea2ade6661f6bc9fc317b25eca3a866c0104c77d721fda1a811efeead0a74797d8117efbe0f08b0f7a2c45e318e41f2ec188a6aa568927504a6ff812f4cc4893aa567f210d8d38d9575ce4084df9cf19ba59ea14f1a49933b1e04c70ee0479a5f5b02f7cf3e05c2a0a6fe27035776a2b54a83f11a49af8dba9349b97a00d3ccd904f02e41fb46a42af4a3d710c8990ca6611003dcba3495da20e88cb763c60bcf2a08380eb9dda712256f3d8f9fb934bbf5cfcbfcb06fab7e34d59a75c65781767f15dd73e6579af65985487ed724a3f55055cbaff39708c18e76a1c0d0620727267c6ec5535961266560ab723d5f949505868aa89a1511c3dddfa0e68ba4d20f60faff1b5c77b87d3b30afd636037f9350cbcc97c72eb75ee6c5c2207a91d9148ee6ec82063c4a3aa3784ea7240ea1770aa8d6606eedaecd77ec4f92dbc26852a680c0facdfc03658f82ffe6f126de0f9198539f03cd91f1c501ec81b059b85f17a4b56af75f4e3f5186e5c95b404f4fd50c41195a779cf9f48d12b85f4b89cedf55ae3fe4d7c940c10831ba783d445b699f98b75c7944f8126ecbb297d22eba7b2fe727d0818a3d77c0878abae1ffb1470699d8713bf3f1cb5de8255b5ddf4ec493aeb0e98e780683fcc84af2cf7fc2b74dec3c239c91cd996326f28796d12dc3f0926f9e1b75f0de14aeeeb697867a0152704e37e47162a9778a6ba5eb45dacefddcd504d6df83e03ef2fac9d5cda1c701314c906f73e53467ab509e645af3cfe40046e09d75309d8a607e86802f201c69e80ee210e9bead3e053b9fe0d873eb8aaf9f4e3f6bc306a86f0faaefaf17e78956869b28e5974d71fb55db98cef36a60f64b59e552ca9d012b75a3cd80875b6db17dcef45fd20beaaaed757c5a50d7f9fdf00c17fc0d187e5819576e7fd8e9065da10c6da06364143768d4a512f01b69ba9ca0417ece56b4b8180163c003f05fe837bf273912e31f1fa9e9cff81ccf764c1baba1193470782fa2f23bf5fe00b98f0d3a36a95633c4befa9ab15e0f133f7a3e15cff2bce22564067d1920ec8be97c51765e38d5dfbdaf4344a09f411dff37cfe5521d8cf137d21d488c49785964b9edcf0d37ed82b82c5b080675a8b0036cf6d2b93f4be8fa3471cfe970a3f453687bf2ef90ab9ba32660fe95f45d62adceeaae1d0a4c6fa1cdf6805c34ccb4eb27c81367dc91a5e0d99c9e2fbbd328a272d979803095703fa4f4002c509e977c4a241e798d276f303f274c0f8b10c0ea999761b0b9a86830107f96e54fd727a3ee65cdc058527473de67b8c66223ccfd18aafff5ed1d29e9f89c1881822184661583a7164ba2158c609c7e95a3853b6993e89bdc95b21f82b80d2d435d5a2397b4d2fc0dcfbf25217106d8953d8e8aa0cbcc0351abad6f78e91e16b35610e01713b3086057a814c90d7067f917b84c7540e846ba918152af9f565f13dd2e80ec5a4207c78992cb9036b5a9dbb41205a363f24ef76e9428f5ae174f46e6acbff2b5083022e728830d0bd1b8c5444f9c8221b231a3ed024b08a46e26a658b48a39c6171891ee84849b435c618d3af3e73cc7e3471a030916224e81a73098f477127d7f6569cf86e0f046520f4f338796ff955dbdc57749a076a8ade6480911030e8575f337c40d711f40507502456aa9d95e2f008650ad4b591dcd5e82f89bd80e0b1473ba3efc4ca7580cc26070bdd810951e6e872e8f8182911d51d3960691fa0b0ff797c637305ae0020a50cd78e6a7a68399b2da11d42fa0d2d7971f5ca0da5c94c210eb4c6bf2de95a178a5e7ff5c563fbeb6c3614e921775dcb2183c8fde8ecdd4d2dc5017571afdd5446d3f7fb853d2f42f18560fb6d5dd7b19ef4f78036f8ef5ddb9ceb0f41ac9ac27fda7e83850f1ef135e78923daf37add2acaa4900bb36e725e614e648dde9a29ba27587d2bfdeb222482bbab3358f7608c0bf9c9f82a9a2ff05113a005b9c43103494be74daea3f76ebc2fa2bfa84e99e39e8543cbb5541d3e637ddc62864441cafb1365e27968108f8f200c4415c99ca1582c59ca4cf2b793366736f63506f1723636e7a3000f20d2e489b9265fbdd6c5f4323b0e985bee96213ffec04dfb552cf8fff9bb5df9a7fd3b46805dc67f096f62818d8b963f1a9ce5ae81851b9ad70faab9d3c4e457ac35df32e32932f21f4043c9639daa35e390e9d0a80ff4d1b0e1cfb09df991c145ce06f79e4d0deac1ea9ca71c35ea994d9bd06317d659818c81f3f5b49d14e260a4c345aabae914ef9c4c6310311830da02382bf2431ad089cc2a8cec813b81278fde3067ce1edf1fe9e9e6f62deae55450b58fa761374ec204733021a3b12d3f149b9528b6db579beb26829c3c165846b3f59621fa25679b6e08c68cbf15a66013b84c14b99cc852193f89fd30d9405c7eb8515f6566c9bd806dd75275979360694abdb1a772f4fa3ccb4eebab690617dcf168d568bd99e9afb4105a6d2eb9592577c674aed04ef5db500d4c0715048901e98f9a8b291f73c5dbd3a52363cb07e3fb498aca0c45721a2b6d4144fc12e4352392c5cc538d3599c9edd1b4a2d0a2909423138e18ff734999bbd72a4d562cd7d30d37a5317992f65cdb3f5e45b41a3f2ce1cebd0bd6e0b154aab8822efdd6e769a841dad7c7610382e33af931a17934fe5b5bad365e228b6e4b79fc7889a8683a14277776d7a9adaffe5977de29a15e058bc08a5b853a8ed30951a3e34ae74ce8be7204382c4b569acd80e66a57ddae7a1df995ebbc08e9e81193255db7a31782a61e2ad33102960843a5232186a9ead77ddf59e48bf19596b7d1bbafb5e35287f0a23dcca253e21c56381c902df279b794d93a2a1606336f875464714b712588956ff21ed46ca05506c234ca941f78720ffdc2fdf02f3e0ce6f7340d531db20437b968e2802137a376325c67c6405bc643227d50f5679747b45465a77f675932bd9e6ac99c1470df0617d013533e97ade559ebd1892baa5f9f89af02453e485c990e6870d9978683f809e53e252a48ebb97c28420926f9df03472f02bcc6919e06f554ca2d139923337043564dca7aa53e6bd3807b82703e190ca8ab4def80f0eaef429401672cf2bc152a468b6e37aea53e241c698d6476b5bbe000b62da1fa72cb10704bd4733cd4a4c961f856df5e420d183b9ddd5ae527e32b71f97b60c6933731f6ba74776151cf80d70d7f08ed4cb6a962365fdc6fe34bf96ccc9bacbdf5e238f61dbe0fdb0cd6da3eb985c0f13a823e8a0fde770d10e0cfccfb466ce4966bd45145d44127146fc60e1a48dee00508f140e88719247c5c83cecb9d9d80a30cea65746eb9a9d424d1a3fdd2827860d7701ebf561830a4bf69d706df7999d202f796bca3b87bf7cd6be7bf1d0fb97cc05dd0f447b62cec30eb9bb162e97ecdcdd7c51c1ec6e77e493dbe22859b26b5864b6c9b56b131582acf9623833ecb3aefb01419d5e25d7291238ba3ca7cd1f57071501ebcafc7cec8fca65002ef683c7d8b78b80d7c7d58fac92ccd42870fa2a7c3051700534f913842bce840dad88dd6805c7ce4fb15ad0e495febd31f5d3998ae1620358fb1c2a31bf801074f89a14ac5a0c165d9067e15c87873670b1dd6d4e13dfd645508751b2710a1fb0cc141c8641e7a6fed8f47471a4d3143c930d881c80828f88b31bf9b93ac63fa8e7eb7da2c1bfe181b8a3ab0b86986cd2a1e005a4288313c9316fa4ce4c3664cd97eff698c2aa096ffb0211bb5585902ea7af50d2d77effb1f83f5068e0c4302aaa2d91d721b5e92b29d6777fdff2dbbb1cc193e83bc7a331bab3e51acffd3e7c74c66a6ceb8a300b6eeec69c27dcfb0177e23d0086a08161057b0f13cc4cc40298724fc619eee32c4d5b8e740cb17228bf32d41bf78d0a8532ddcbf577f428ac518a7af7797c56a5d1463311a95480f0e0454599c22704215b7a8702b60e256d9815e30e3411f64699ae0df12c98abd454f22ff1743d61c029463a6eae4d5ac1721bea2e43470643b3bd3e87501a8e1ab7205d97ebaa377ed7b986eda1fd4335427d0e3fe7742ae1c173a135b8d5855f2c75c55b8f6c55b0e153173b868af31e078f638ee4724bd3382b49e5817e63e6e7f720d814447d0690c908ccad05cc1565fad8d43d23ca33141bc847415539d1a9b6ac6464a3055e37f3c94ff581c590ecebf6e9e7c6527a54c4f4d2d4dcffd2988dc47c1ba1d8e3b8c1933a1499ae0204fb88c73f90b57baa53f84d570955e17080b6e0243e4fc3420a84cec9b2578bf505d9f57652fddfdd0638d2ea7fdaabb364fe7aa81b94bbb11eaa932652ae216c98b45811b5c32d3d3314c051ca38c9d8ae833c301e5b785f96739e5fd4f58c494535c0ccdde43e2f5bb744ea4bf2d75fa678022913d88a7e702f673708954ece3a30449689cffc787a9743a4c6fe960ea2426e5fed2d6f5b59246e545b9b7c02835119fead4764837f8a0b1b2f3ce37da7983807e1e229ca3925a66e65b6581bcd3fd9d68431c1add3326029506a15dabf1e7cc1d6094a0a7d342543cfe80ed60000dbd06a67a0bfcd419b82834dbe9cc1da501cdb5451e2dbfad9b62bb52bba9c3d18488ae387cd3af870c4f98dd0d408699db6e98f5de88e33640cc771d678d7b49f9006ede6c6e4cbb38fec79b3827ea11c4e8bce64a32ba80a34baf1d9936acefa10aed5b757a4859bb3fd21462a06dfb709dc1e61e29ef4d20ff738c366b791b52994866142608a6dd898ba6052df408010520fc025d60fbebe6e7e53b3cf6a5edf756a9d5dfd9736f1c55f9998d5f4a8ae2a29cc1bf493fcce8ac607bed53d8b515ddd255bf01105e71102d282f76430d9da8377ae605316dc61e8b5cdb38800a7f86370975603025176e4c37b1d46c5d39a50f092a27eadafb1d924882950d05a08f4d0aad96f9988e0f7000304f2bfc66498ae84c5236e000e0756b948b97a81c4a8d36e9ef2c02993a156e278be9ec5bf6c0456380963ef488af447f2e11b913859b1bb55b5420f08a20b39db753cfc04efd9d447a4acdb04a2453633f3f834cb7512226f87d30c1bdc0036285e06b7f80a1064c3f490fdf7652e9974fc7daeb9480ea50f80f0fa0997b16ca6066cc5ffa2f69f235d8cc7584552869bf03840bae7f3ad6bdab2439449ac5f51e37ddabbab3b68300ecd2e603c6d535acbb1c7c38c0c8e32f29b476801852fbe27b33bfb8bd8574dcce0792277ff16557e5eedbf636a6cd4e279377edbfcffd53a8d0aa6860df816579772e0e5ff9865f0f2b0148463960514c57790b91e9d2ab8872b2570fcd26ee7f19ec18a1f38077c7526ab826f819b251be005f4bad7cff9e17b0e90d60468e97b7fcec6a76bffb614916970abbbf6296558ff73e2ba42488f6d2382f02881c7cf1d9961621a24f4a16cb3a464e61a2759dcefcdf58d08634215314a3528141db7272486975b81b1f9c0cf80d4140cbd00ca67a46953c4bf89e9801897cbad4d3f37548498e74f2f77a3cced37be6fc449b44fdbf0d70c0cfecf357c43e6383b951891f7e1591acaf00ad6bc4eebde549007c1661880d4fd59dbbfff499caf2016f2e40b39e5f3f091ae833a29b53e189bdc7a1788f70dfae4edb93558a92a6e2ac5fad5c13d89d7ff282d63cebfde45a37cf369c548026a5516971d3889c76bbc6f660fc7737b63962e51fb280094ce3de514b681f94e5393d6e5268a8d8d17621cb187548a31c29f0c60df9a8564be095d60c4e19253c8eddbbd9b75192f73edccabb0bb4b60d53ca464102b34e6baf556377ca8e50c00d6f9a85c49e0e6054b59b2a1f50c6da6432223af5f08bf5e7fa76947f5234dfd6ff508a9d9588fb5f313064bba13b7ebaeacf3136a5c3f6962932606ab9d1213f6b72a999fcc8df8d6679ea7d4baef57fb41571559a953f8dc0c7649a311fc23286bbf7ed04f340ff5b9210b1660f841d317c951a35f2feaae5a1b3ee5ac778a92b7e52dba05e13f628fec2db24015d69f4ef9122ea0414ee3ceadab094bfc328611bc148863b711429b69e96bc6e8268b03e1e9ccbdc6db60e7174b6fbfd6461f6043fadbc852cdeed336fd0874428149ad7fad96aea8320edd30b58ba3f017cc83bbf20ac184b8bf4d7ec0d9c21709ad7241c5ea4f7c38681b75d381264fdff3a9680fa4ff43143989fa4712a9efde521d1466de75fe3c93a9dcbf4bf66ee44c696dc356441fe56a781bca7d177c5fb167a95bed6484c6ed1e140fb67ce3f3e0c4e463b5f81f0ccc3ac40ab241fbd2cb3dd75b2e1f0f3d0f76ae1673b2300d403def4a0f78735047bd456532380305725fa950af82dbe6d6cef7c8e9c35ba9b6e05f4f43dc2dda67c449a619a4449260929d1d03efa8a93e17606576da2cfa9f5a6ebb7883f33760f30918549ae0b7167d5acb2d71f78ae7c0d57e62e11e8986c6c149c21aebc609e2d6db0ec25584d1811cf3522bdd85b97dba88f062e71d6f45d807c2182dca898fa1369ede5d1bbf7ff80aa73a8ffa475c29d2992fa2b93dea7511dd40310bd8481ed38c8b9980dfb69a5c6182bf6aad0bdd308b2fe3abfa2649eef3a8d5366a254383234c0f46ff6e3c745a049da803b6e96a070e595118206489429debf85164c8667583c354ad09684c664dee26615d1b47cbcad1e69d48882eb69d411a97f0fe0b67853c97b65ad631dd669e8b5bce0b92e6f73601a76269be5ed8bd12a9fe67282af6ccb4ecdf5d3f2d45cf45d5eac7b7845464aa2a96b3bfda99f564b20427b3d95ba77ceb6ca62f4ab2129b9f3e536d11704ba270291fd152b5195bf5f9ab94855f1d03162e13cdce9cc54d2358aa797d039a6156716bb23d82b3959beeeb85bcffd1702804c7ab4ffda90e44f8f7c0c1ab4658ae21f31dd48ab2e13d8fdc37d4416e5e985b1d57ef20ded2aeaab1e152f9d1f2e86810bd7260dcde40516c012c66bbeb2b541be7cf4e46f415f587a75a1d14186f6d05761f0eca5791452de590e4b736f4be58e2305d5d64f3f9d44c7b5cafee62c1e6d78bc7fc4d4b70be38633094409332a0c543e10a408086477467be7c9eb185cc1aecac1d249cb1df4cc0a62cde505ca221e0eb5941e3124a6239766b0b0ef0d58d59b56b659b9cc359c83bab785d8ef9af4e556e42744a78ee9cd0c462fd7c058c24c15c6b78c46c708814350626589739e0ee217c8804ecb8bbfc268f54bc38d446dacd266445b0e7b8cc495437e98dcb1ae656c6ebffb1e5c60d4fa690867e089333c20b7e4428e8d62d0f5ba24442c2518f00376775dfb07fc2751d45ce14c3156857519b8d384751b5a415c79826446e4c3c9400da685b19a11dbd3d07ad7560843ae5ff694de590c9e338a28d5008c09d0af99f391bca6149fc8c9b7f3c935e0fc45a33709481c5760d66c1f2c5da587f7236b151b3171b513ed782f807efe4d86c074cc7faed4b93a6af3570da017ba5d84abf57f85f49ba5668ee4dddd338dcbe35363b581185afef719f606d2bfc638b0fd5d1e3eb449773f314f717ac3897848aba9ff932fa99c18ce02ae95870629e4ba47c0fcdb99f086381318bd43273ae775daa92a66b4df4529c3e39e12dc7f4a9a01aaac9a8e5b998c807993d208d6446c54e709562a8f8cfdfe6ac716c682f4bb420f01399ff6fe65b281707ac775f1f85acc741f1566fe3041708ba027dfe1cb64f293a91b0cbeb72dd7ffd7eab8125dcd70553b5f29b1d4ecfa8c3d61cdcf17848c02115c4cfa960ffb757848124165f4c8ff84afd4cad3190f7d820fc20cf8db71208cc7a6c756c4a166eb86b543e526d8a3ab2703d1637b42cde1739c02b300262298cf4125d33093496bfbdd73f8331d69bfaa246ce33d22c8ebe56ffbea0d6a9198cb8b9062866f8db9df00c18e4354ef82a16dc40fc1b919f1440e26dc890a9a0935b4c7fc87cfb66f8646dc4d915f84b6cb24e53973b6cfd91ea15ff40f806bfa57443114a46329abfedcebdcf99bb750198cabe313ed3785c2f44de81df26cbe675767f8a65161d09ca7f34e53e39c0458a08f478cc05a1e52ce2679204607027a350de2463dfb8436089a93d48f7d3e150e343c3fb2878289977337152cb1b593dfe06839d423258cd99c9ba6f59d1e63b84e633aceaa579f5a264fd4ec1a08c4e7449cb2a669fd9ac9f33ebf979264f7d887f4a76be24ae31fabafc587d94cbc80b50ebe71f484b16e3390318cf5ef82d2ac55f55e9a46f5af149593c84a0921b545ea0e9c0ea56ed1f46847818975779fb79c4dc45836c0074775bf6bc41d12572fd3c2fe089af00f0730f17474799851fce547d9caa84a0465f02bf1c04e9ca4cb9ab15bfaa5495f3f2843e7cf862ef7249af66bfe2f75cbeeab9d9d2ece0dadf63aa5aaa29bf61dd01a790c70852cbc1d8ad59c5cb8fd9f8630b20f94f686042438f56da927659e8cffae12a981c153c18ae53f664bc80a5f3abb64aff878240bc22861744f8877887168eb156558923959c672dba43c01e8f26757a89c0f46301ec99515906add0a22a356c1d733a731be8a49c1911997bbcd9c471d56997e694bf6958512b0066b4e5e9f96026de44edaad85cec3ffbceb17f255471ee314606786856a02ee923898925d413efdfae652250495c706b2e76e328927d0fb035837cc48aea3220fee5c23210abe0b7d436fb6320604e9a2fa6bbd3ea458481f76e8d43dbc4e7fba8b284ddb7865788f12af90d3c64a86d97c50e338abce0291b9c42e11fc4caef43f0068731cb07f8a94012ec00b1218070ce829c5a8926f588c5cb5fbc044948115e81a93c489bec04db3153e8bef928f89dcb727185679073ee205a880ce9481074109dc08cf7bffe7930f3d5f205a04adf06b9b5cb8e17d42db23861d698132c12760dceb1d4d17b1919a9c1db1df65f9c2d4d57ca08d44d7d78c68a0baa95e3b6daeca5a200b9111fa97b186efcf306e35a34359808c6e56da46a8126de8d3c00d80e4d9bd4613e938441ca079f014122dde3ce75dcf11c47b0ff3392013e0eb84fe3af231e71af85868a972f35c7f1d814780cf5b92601239cbca0d4fb7cb5132474c12008f38edb948789f408bd80c91c29903848d3293b4bbae4d555a51b35f913a65614cf4e1f8f08fb1e8c06d9c8757e12a6d072ea961652e0516c4859282fb53b58dee4f11f9523774848f261288ce707a447d786504efebf899e6e3251da0ccc456e7f6546fc23b5e90e5097aa78bde5cb12ae06a3724340ca3598d3022d64980f7ba5a1cfc59ed1e7e8b72bfbf9f0657c687c4693fbfd0d8e7ee3c50fc4b8136cf76351b357876ee719cdcabcfd3cc307d21441c0b104779817202d43601177497831e29e571a3a6091aa93f55772776a31d83cce3dfc39eded9787a66add058d171e6dd37ad34d46edad2a2103de057a908f1ab0e61ad4afa3681a3c4fce47d541f944d0ecb3b9382a53fe7ddfab6cf281124dc12377ff94ba36b588ba1adfd6a2c8154967d9cfb81025bef6b4f831609204ddf94eaa1d005322ac2f59ee3028752cb601b538adeb0618fa57d7fe2f75e60255596f4d2d97fcb0feddd48e5b2fbff7fbbe5ee005f9122faabc051cdd790d785501e34edf57fded32422d4ad6bbb1f0c4f9940aa22e5729786cc0f1faabc09126f6f390c7eb5bc9fc815cff3f121a3bf249c38fe635de69b8a80ce509fdbec126d8c1b4f0e9064e66b801e16526659f776597affc03df4f1e9e29ee7ab8ea7ba2b74daa96e253d9dccb8779a216369eb83215cdf365bb2f964c54166db7fc2b966c2a7c0549a3a9ec895adadc11245fbc881681e40dba5a4e606c541efbf0e26d9f8f3fef661e4873bd77385f1d9ac141681eab582c72c70c53b1c831cc5771ba535c3fbc18fdafd18e15f2cd45bad2e1ccad897c107570f6c37ded853df9961cf07da319b9e93ed3eacb9842e58785ea1d884c19d5ec7a5f1e835e4389576b4d30a3561f9f058e01c7b19bcbd1ac4b73fe9716bb3866161a47694b1682144454ab92ad74c70287d60fb24f700dd80cd4eb999c1a5189a0a8c9a1155f2bfd80acdd7c78519c2d713ea0d6107e7b2438038a47ebcbfc6ec9427303bd3ec8ce99b30245895d2e95aea6ee26817becea9892f5d7f5bc501abb35c0013d997563d83aa450c9c626af21d54594b067175061c73be5732393eae3a93eb63411d11b286350747e356852a6390a58e66b3d7e428bc83d0aa2e3354d5d7896549b489a0cacf6731bfb9ee1ff593636019ffd4c5e16143bb7a67aa40f2b855a65db0bf5428607f4176aa947aff299518207a6d48c6c78974cc532a215f8dc011b85cfaa4f730fa478741ff785c2f003c7bb38915fda1334cf77a7e50ba2111644ed4605969eff4d62cb1df77a5f7803ce37689cc9f83cd2131bcf5ef6db7be3311292bb17fe4e1fea47a73fc37c57db88fd2db0dbd7f96b415f9b94154f1ffdc67c79ae714ca7ef5083c431b52ddb5eec87f0d1310071f6666c993c8a00ff0f8c79386e961a4b8bf71d7fbf201800b181d9eddd7174b24daec7eaee78f27b9b1ef797d52279df8d61c6010287dfa88e6b83112addf80e0bfaf5042a55c28cf7f3f62d943af402b295165431aa4e8acf37f039367d1d04e22cc59a77da1f7aef61e4d4302f47fdade652c02c849ae5a79c9912b5ef5d6ba7ed277fee0b2e7468003644a4f0f3a1f8ee2bd4950dcea0aad5c8badb398f287d31c183ffcc6123c4f4facfb5f5aff47695e8cb7aded69e4abed75f30a5a5e4bf5a62bd7f73a82ae70947dd7fa0f4666243fb9ffb5f0492ff8b1f7d6e9c968d55458c8e05c03ca5ba493a4ef33e6e2f86832d58bb33ea23ba0eb0bc314e4963391b33d91406253dc5002e832fd15c22d8117c15cd992ab075b5c008f3a4dc69d0c5328772d1e9297f04c1162077ecfe80aff7631758276c3f5ad7e8fd74a314f13644f20d109b0e660eab10db993d776c747ceb6dc439a5a472af2dd9ed9ffd903d22b4acc067a9c826fe9bd253233403b97a5b76ca1dd2f499c32403e80dc48564bbc21f8fb4b5c85d756ed417a1648784c2a8068288c9c8ac82d828b5913885aa5e4465a06e79c18b0468b665f2dec35c901c455dc388bc220b0188936a6c5f900cc29e950af6e3d55e700189727798f8a5ce28720c4002648c346405deebcdb1b4f5e6625ac8144f6de83524a93b7b754bb5da8b52f1249920c17a0b7b02032f789dedb42c39442ae69ed3099aaaff4f658d5908ec31bc80a08715650e856e8ee8596ed81d116ccc76ff3c827a053688c910d4d354fbb258b2c32266ff7d99e7ae288969f19a53ffc3e0afa243c3c566aacaae82f05ca071b7d373eda531a21b8000641e0a506e7def633f8215250c96f449fbef89c5ae48c6df8621844302bd03cd925bb4d21a1e47dcee625189e2ae0ee7b77033433304134c73eeb43d23a5665cfc860b13c480ec6e9f81f055b1dfe0596c2376a885e7055d58d48a658ede82db333d7d8fa950b9fbcaff7ddb7c8248937fd68a6caab06e45dac4d7f2336a853f5ba5a7eaeded880c92f7aa30fd6a38faf63fecf0d22fc2aae954cb4b2146a6a68ed1c4d3b8939474be3cd0360336226363867b98294783e544570de18705aad3dc03f04f860010616059bbe5cc83cec0d4136bcdadea0b5190b8ffe5ba1a3407a8e2e73fc566e8688c1fb441925598ec5bdc224f9b01afc25379e5a69229001059c78a0e01f0b2c1441ae8e15621015fd6a38806e43631f5fe10a936a7e0e375f9c1050eb47d6a2ae0c639eec9eb77b5c90b77232c252dd65a7f144a81e8759e4803be7d186520d54f93a8bccce39859ba22cbac0dbd8b2ca2092ea9069a5dbcc479e1ff5d19efc3832690e58927ec470a7301a23cbc2d814213b83d0c913a4ddb1cd2416de5f89fb64d6005fe487ebbe0ea7fb29f49bf0def951dd70232b18fd8bfd829cfdd516d3fe2a9498a3b1d8ce80f54b3f434e5d55d215e9d6a0190cb4ed0e1aa063703a73c84144f892d716986afb6d2255be8316957ec9985bd640bfc35807df3d75ecf0bf29fdf2779a15713955a5487ed966350d763383a6ce22e7ac7f5c31e99f6a8418239aada6b77778334cf497e6e6897c4a54861955eba137f58b25931002ec6c92002c8ef38a5ff3a4230c32294b77fea36e14b4af1d6bc7ce35c8f86fe2eae41d9d5f66c0b130ede186115fe31b9bd3f1f678553a8bee1afe0d59d7a9faebc1cb524047d0d558ef60f7e2a5e73d41c162b0956bc95339de8957191746e20e623db56b1a02b213c320297cb8075c74a81b128b40f85cea43bb98844cbf3ba1d3e59bec936d43eaaafacf89983357ff69d792a34ad531133dc9cfd83e567a8e17eda687fd1ae98aca665ac4ab394063b7a759d7aff550e0522427df25c4e113363a94e03cec79db053db22719c7e6320c224c397d00cf53886749c4b1ded3b8a7795643cc56bd4e0d369d6fdc5f0d7f53c5d1342c2594d259c488eb150d484739e0391eb02239a7b241cff440931fcf6d2373e0d62b8639efd916b561e19e6aebeb53a8fffe046abaf7ba597887be1df42384ab88a1b9c04b8104c96cfc5424d4e8da379752412e3b5d81db51dee0a29a54e3d0cb2408410bd2818e24a0d3c9b8f615478732208de4170d650c75cff8d1ec92a8638936aeaeb9c1860ee415bc6647f0899c202d1049590259aabb38eedb895451860b24888a025124f833c71f5485701d6710c3251cc46461a54d28ff2ee177919049d9117eff43e8333954a0b1fc192e79727768805a839e0b78d695ff182d443a40fa9d15355e0788629e62992117c876975d39a273865607edcaecd34d69f9a685c2e1233a1d50cae233b4bab9080dcb04edf74df7bc4e620ac2b6ee77c6ca7eea9097d21cdcad42083c8642ea23060c21bbbdc24aa1f3f7452f220ba5b3dc8a07b0f1262ef6e12a0b382a705d1d8f4d12d981ba4212d2e36ae9d7754492098d8e9cd0535ed4b9e757a81b1af449904ea37da9a7479ce44b96e690d4656ed9c3f88df3ba620bbbbb87c90da8597ca1e8c55a38513232063250814838b82b406d4d995abed7e1f7282c42d2a2d0b24361dad46039f8f70142808043dcf71502bdc30cd16f566ab9384a8ee55c52155f7f77c3a36ce3e9851280f3e490681734cc37c05341853c8caafe6693e099c212c961a1954a4172b1b213aa3ddb4847ff8b64b7593c34fc9b11454841bf841379d7bb34e78641d0702be6dd81c87aa3942a08b423de3883320528480d935763128bc1e41433ea4e2e8e08d93ccbe551ce9d8c2a3255a2f4e9990f58abe2b854875755575517907045448cbcb18f267b0a1c3439ecf7f3adc80ed46c322005d4fd13ec915fbc3ec4b4cdc401873b63ce95c43d533e3cdcfb01c58d1c5161d9605f182f0a296ef6c0f74fe6e3dc1f82f6d514c2e4c5a8382b5ac8ad5d59e2d66f5bf91c262f6f6d50568e69f1d0be8e9f96677916325e11a447e20189322733e6bd416700258288c2162ac921e64182644ca85ce9c96e447e23123de12bc5efa6c15333304e73f4d4881363352651eb701db2d1d5a43b5db34aae4380c5b9f3b39eb49754ff1713a89832800c1fa6b19dfcd267c3714fe715bf496e4fe4b205be43ecbd2755991152e0b7756b0b2bc0415bc3529c7651f5e86c2a4f14d61248e148c11610b50bcdbdf80c6369ae3913ddbae816ad765632295a292d39149597c4d28a2bb5f0f8ca72c8eb07f92590e90c08c4ccf78eea5186a3ad41347f42899db9cded067108ea34f982850b06d4146071b6a01a8c1aef0323b1aba6d174ba27ac571344ba506d6e1b4fa16c024e5ff47f58dacb02c1462fd44521ffa40eff55d5a13a50a72a3fbf73896edab85f491c95e5207028423c46b0136e2a047b10832d3c0e609a9e7b711941c7ec2786992ab05b8e2cb0f4c87fd08ed57e2d6afde43116d603530bb9db05155e879c2e471ba3fb996800f44a3caa5b3a8315463ea7d650f2408440ad1de69036e12e12c7d9eea93a4fbf94f720bffc584076f77efdcc717c95fd5f1ee7518365e5a00876bbb8c4f044a5ae414e7b0c984ee22a9b8807c3e064979f157a1047ce2e0a4650accee980086aad0596050b33780d084f1bc4544577daaa1f9a5b075004421562ab649b6d0eba6346d805cfebee00a548d6fa223c04aabf4f810af1e805dbe2d7b0a90ad99917b2edfcbb215e1d48198259c8c4d6cfa90e955f9265df416b41d660d769a4ac3d98320f4c372ffdf77349a900a16d063d03cf10bd8bc85634d18ad53955aa8e190981b245c78116c667a2e95a2ef348dc83a3435f5a1751951839e35e786ca92d4e1a8d40580a17ae2834c73b098adc5f7b71e00a31fba3e41fdeec63d96e498ad1f4ec421946420a96e1cc87cf1fc413e9aac0623f9c1d558364f4836b3ae8c5038ed498f3d515b93c9464bae1fe7066d7a8b097d8dbee21c8cbdc100a5fd32f42323ab2155000f01313deef9274f8c0c542b72029f29ca167fb31cd10bbba646047665639c0a1bada37f7067ac619d7632459817cfcc75bacf41196970b1ba2ffb5fdc924a367fadbc897831608ce742f7a1818c4b6ce056b3e95fb900c20b0d3b9a1298e536bd29926f9c43354af292542d203f5fc6bea3fbf4225ed41807b102a6e059eb3a70d4a037ab1a0b58672beafacda03e186ef40ca9fedf8fb7fc67ec077eb8b895fa8d377dbb8e4557fcaba007985494871cddcbe2204ba15174846003c952e0ac0e9a92c9bb875c2ada6d309f1663c86eb47428809c2648e980f4a2b34d0ef20ba52a49cf8d4148791a3c31b40f82f744c3881a81718ee35e8cc700b83e0d0761643021d87d1eb40cbfc390dd497e4db6690b0d904075ae8840a7b585ac408eb30b8a03240697a345559b90905ac96da67dd7709107d3259cfd546e9405306d931cbbc3468dba5040c1b19ac91a1958b5438f0339f0622ee9c709636e77880a0a4cdb3d9dabebaf02db1882d7c9081a0f23ab2017857d5cbbb576a549d27bc084e13792fd2a0a0688090b389495f93f6cafe8f1cdad9132c3e7be6f3c8d5fa90c9eca9b0eb80ebb2ea8c063c592443cad155ee2bff7e1a1c5101429cfb866c7e0ec810c694b82b1aee4c8b1b9a49eb94b747a994fb43b07015e05070f32af5e64b2b92997d2d73779d11a549dcf995c738956f0ea266240e32101b74d722bba93b835b44ebe5ec89eb3b56c0c72a67c19409c3e4f75c0230d3a140c0720b92ea24fcb891ea2e9e74366c27e6d173cbccd55d892b2b39229a784077d985207b794b84f306b369eaf4e33e21c81a906afa0bce296959e07f738932c0dff865af2a34514ae9eec67c7cf7d47f340e876275c60f00246953f763d593dd40a8e926389bb923db16475e660a60402ce993affa0c4dcc5677cc95b184a7620e1f8cd9b8d914567df71f9097b89902819c5cc5195754d2731047acc2cb71e1735ce107c0794006aa322edfcb8cb9543269e1ae6068e2140377cde5ed41544283ced6d8a8ca8ee5b67717d116e9d67ac5238049c5a17bc3760f3a731e96604f4703a7303eb25674664a2085eb7c03ee616046295f077e736b7f8e6f81054ead2850f149b0983759619a1d184c67003519e0babb4b232b489fe90bc3f17591fe329751f5e2ce9c53612d02eac9f788bf0b8b97b5585154efc79ef3b9b53ba4a2bbfcac9e16fe616cdb77e00ea28a048461d8b2a03a79f8d6875eff454ce3f623442c5c0e09303bbe648bc831b6887fcbca6487ebd86e09873b3817b302b6c011948a72a7d9e8836faf50316144060e01cd7d30d7e4d8580d51b9274e32a284b45b33267178488e6df27461f3277ec71920ef96770b743b79e784131e5ee012507d57d9f876cbc12b48e69e50d3b2a8ade9b757ba7810749c87319d666a09c30a113d5098ae54a419af09b4687c29ed5b923dd036f86702f9b41dd290cc7ca8875cdc5957ae2559100d2fe2cc75d61cc50b53f4d75a8ef1a4c8eaa830c7b58478c21b66fef48131493ace203c3cefa52240ac7f7cc904928d6597b7dc57745d4cd57f28d27ab6b15caf0e2700293c2a6f28bfc291f1df4fdfef1c9118c30b1c4d4c4b27e8d5c27833cbb6e32dc6670e99e2895ab90d68608fd4a68929601cdc89ad19a861e8b9aeec7d9dfc6d041d6e01167f9671fa531fa84c938c599cc7330b88528ed3a151cebf4408b6038ea92670a37c97638801f4bfa2b88d3549c95aeded6c7a5bad839bef1e7bec00e63d4803b54e382966b743db3e3162ff24706bda27d4c68d50c5bffbbd21cb54300f4a574ef9e8988b9e287351c8fb6521ab7cc45bb2208b77021e8f4fc60bb327cea44280f9db217d2ce0be614fd13f8c459480e122df0e985d66f344bef116939168f2c5875422092663f58085d9b7d29ee1d6a3c950e339da9d10df9614231559106720854a7a861d092193999b105181d52e0dbcd0a43eb1626a9fd6d000bd0089708c4095d1caf1906c8366aebd48bb0ceb9a61c82f66155e68591c3b310eaffbaac20522efe065e51d398bd2a32839623a2bbc9b68d5149008012c54e044f00190129d2d827d123ddbc047ef943ed70b1c67eef436d7dda00e705f6336fca1e09a286842c8ff837ff196ff1d813bdce0c725a55e0eaf66cd2d0b85a9e6475bb3b124a8c95b0f7a65d32c46833bea4c7a46bf9d497a74022c5943be34ba16e057370b16c96bacd6124df6ac60c404440f5b5c90ea30ff05e767d318c8045098a092ce3387e91801f23462efa5bbebf4ffc6f481d289775b5dafc1540c9030bfa09353425c02ee8d041ab3c005acd9ded1230ae1c76f71345ab57162352b384562498a9c10cfe48820104edb3c23f71f6b0b334ed8e90f22108b87d089cb5528d1a7df71d355cc7b02f83e996302fdca0da4e227762308a2de7f3e1d8fb12d09ee696bc52a1be617cf8950f0b25cfd73726867f76ed1fa85c00551c9a8289508827694d8c29336b6146618d30e8b68782aca7296f62317e81d1b2f39edc4632ee2381e315f2eb708774fbf9db40727696908cbd4fc6ef6bde50c28f5356b2dbff9a9275fb8186ebea4b00f5da227a126cb389b655f280fc34e9c6f0cb14726d0c5679be4f54cad8a603d6d740f8bfd21347c993cc70bc683a65339943b1def9da6a5393db8b9b709f282f35ba0cb60856a78c470d1d3361b8c294fa5802a69575083f303f30847fc2466c0c3bb09eb93cd03878ba15e408dc1a79ec3e9c55e871112116ee7f2193860c30d981597e0a0899b38f9431f7607d2ba161959348cdb7311adc4d32329fd52f1769c446a0c24bb74a47401ee3f9bb81c4a3abfc9801e0b62b852169898cd06a59a6a60c006cb98174dd8dc46fe9e41dbb4e355e88cb23c9050df1abec53f4f3a6e9e44ec42b1a58c5c5e40bf3311b49abc252dfe7c6156d81d00bf33b617b2e6d897df03f7b2a840a76b004db096a50d0994d78898565167c4cc1f1120b5007346049f6f6adb3f597fde3a6322be5c8d09ea21bf45799d94f973bcb4251476cc167950a141585bf9519ad3f00e8a81abdb1c7973e9b7a47cc721583d0cf139f905390ff1c4b0e53a605ac5fc18233ea53cee34e0445e117c69ffd0b2b80be2792807503b31f92201cd5d5c9aaca8117155412e81b9d1545d71d010fa2313862a7e444cfd06baa6a39a9060466b9c95ee65b291e7526379990e92b168c8230e2d3fc491b6d875faca3db0745e9e0d7b47a5a7f08d19e49c88d28476a738da81ebc65ebac6120ee8963d4ad7ec55d942a98cb8106d8af77f4aa6b0bb27fbf872cc9b9e1ef5d0d03eb879f1aa9586f2e1cd0bae5e71681f6f7c72c48343fff8c6270ff08fe0915101e7b1d085c26ea7c4121b7a0f05172464f2582e6df7910c8667a2d1547c41d379ee69391946d57be6c0c44c5f686ff98357d64176b1fe7271e9b2b61521b46b44980ea29146bb61ee3852d9bccdec1b914d1d0cfa571b9f96e3c85ae95aa9da53b9ade82471cf74ed4b2f8c9c20e551e91fb79c1b42f5fadc1099a50ae6f3ff6c1c7b21181bff61ef8124352e181cbbbb3348f829f63a60af052b5304e805ac57a82b27b117f4aeaf90f2ca52f520fdf5dfd02205b1bdbad6b3512fa379d75062c3ca9bf5f2e0a285b0de8872a0742386ce28b254f9b14fad4fd7b973ec5184c6184d49c4ef9ff2c0263a1ac81b785c066596e5fb4be17239cfb9d8c473857cb67f01cfe2e6e90cc1232d71b4ed0fb7d752beb3b96fe8fb3fbff92149c96bc0d03d3347487595dcc9ccc3e3bf33d9f78e2ed482f12ed0d71303fa6c5b5c3fde6967b6611bee81587a556968a716cd9f6d7bd65dfd4847e15e45a2c30346d6dfbb7612af180e29acb69a0ad0af32c19c3e73d8e666eddec9b3346d2c8ee295ef4dc6d8b84924ffcc6a90c16c5b35105e36e63167caac703aa19d85f0f1023dc9ace016b7e29d7aa3b765a82876a1d54f7c64946707b1434be4b1213639126325ae37e7878b6154d626684a89bd6c04caf4b743f1a1f032562126f896c4cd5113ed50c2f8205746c93ca1831fb29fb65daba77d6e60c578999e67773e2f36bb7520becd9682de8c77fd0c50e3709ccedb91ac5437aa4a4c66647a1060a637278198493205aea87b5cd25320158cb327f59be519d16dcdb87b90000851b023cef47a32c04d8cfb84445605755d4370b2dbf0ef6becce8e8fcb32b76e7116c9579c4209706d28453424b265fd784048a053b57f66af1288b7dae7b02f7effa53b469b3360202e1953ac9ba24178c007c86920366c854f3fdfa1e2dfb4c03a23a8debc51bf91a17421a2b5b03bb935649ac5c87ddc5a58219745a408df8ac2c506a089856e0de3401f3f88bd9aaca8bf1e832e88717cb178f6ea33a0a32a5605bf52be30b345f5f240b4f7e57d20913c73861302087f50ff5ed660266446d5e41b44174ad19370bd0b0ec0ed1c2678afe045143869b901625a00c5a18f08b937ca6884dc3fc21e9f3112472c459b8d221b09f3c16644410460de291a00d59f8703e8eadf53cf9edc30634144829eb1a1c89c052e210d640a10099313dbff57c6ac71d38e5e78414b4d6d1bdc8352479193e58d0c31e878ab6cda6f780924811fcc623b34368127d3f29045a092ad26030be953457f5f9757a347f945295321c385f88211ab68bbe6c0943b578135e4cbee458b94f7ea4588455f1cdc3773fa25697ec7ce0f4fb14b2bba4b2df3727a181b560d2d7a03851767144a1dc160ca7fd7707d0bd05e83898a662fd24420ca334f9d41bff9f0b4148cf32ba2c34fb877e7f5c705da0a177816b08d53bef3c2dd10a2c422b6c2ae33c50456b6317548cd380cd650bb76400ade26fed398f34d3c980a295cb1f94483eb41cbacc5d17d65a6e30455253833b6ad6107cd4197acdc1a31c26a0eca85b69abd6fa17296a5d584abc044ddb3de8fae493bc7a7ee348e895acf594106e2f5a391eb548a703e4f7eb48c57571148b4a5e5eb45758ca5c5bdf452bea7280c615a923ce8d50b359108d95ea456ca9979d7016f28cb997224172f02cefda1b2904f20a894d05baf1f1da53428685bb2e032cc52928d686236053fc37d15a9fbeee9103e4f788242207ed0b431fa191c2ec143a8ef221c582461209bf0c75b3258e17501d9be94a1d065247ba5a3c513e976aac0ce34115c40ffc32a73a8374dc9d7f721db9027a0d3ef916df56e7c9a89441177d40affb09cfc2fefb78a6a4036cb7d052a92b2b321c203aeb11a187dca472edf3db96ca181906086a5714cd8be657e34d160295d1af67c1446e42f53cb74bec6a5ba48521032fb7b301cbfa9dbf923786e25cc6285f0aa42b0414c3eb7ab7840a2b7428814f15ebc9f7fb90c89f187ddb24676a1f39e696dd5ab0402c42e8c556d751476f418eea741456d95d00973804784b5ff66f35a8644b23191887a49a0d007c608fc7286753a394335034243e9a382f398ccb3cd1b4b234538a910fa40b60ff203b882a2ef09cab9e735a00f20f2865183348b0c63927ed8b868992900a6f0b2c915fb7cc36983370cc9c438234b3f2c5a8fc6e5c6f7e480600fcea35e88a3a5f7905c20740bfa9ce2324ba976f157566998e1999a28a248b570d689e15bff377009025af129fb2738b3691386ac763e43e15b619aba87f7e0c0d2ad20a7683fa4ee2a797f53ce2e677900f99cc2de81fe2c7512e0c1105230be119d2014ad96789bc1f4c34e909d1367d61db62cd694f7a632255d5469a42e48dbe382e979334a92cc6b5cf381e7254304ddd01a339970239260225913e1cc9e070254234e6ed3001964af4d6312d3323cc541830ff484028853936458215d9d0495ace0cd8c839eff24813778a9d2c9d44bf614c0362acc083bded3d197ac8b43deaf14976bfd29d1cd8b8ccfcc199e35199324f5bbbe8f7c8533e812770ab1796fef65ce2538c5f3d48d7ef87af96485844afb80f3ca1a62da3063204fcc51d482201cac808857aa01d8719aeb1d62a7ae9a02c836316534c1a693704ff4942246b41057fac0f6a2a5decffd0485017b38f9478c913f6a178bcaeb0439050b7fc6209e99bcd77f247220e02152337c363d4e2e8ccc99e6d2ea87c6fcc8e3b88852a9491bb2680d4c90a741a5a6d538353b7259c4d88a649f70472c5524856e78febb783fa253a3e8ca0c516a563417e513accb99038f8b979074993c367442082c92478b3d3b73bfacd88d3369ee358b7b292c1f088df26fba781b6c30518a59d750fd6c61594f4e25ee315f4d989b009c315933c85c92fd1a43c59b4a611fdfd81011d56f672546b0c6e19e144b318290eab06edf32f04a152e175941d2f7fc0a14a9befbed3d683740f13c578b33755b9075c96afcda994df1d640c105da4bfd6c240e0f6d2c0e8f6d7422fd363a69000fbaafb2503db4c189fadae4b4813cb2c909f4b7e994813cb0d169090ce8a77c5cd12f0b5c37161c35722c5d6693e8068125b72e3e7e5206d72a800105248c306cac5c4d12b33605c19ca156dbb9712897e5d031eccda80883248dbe789bd172cad5ec455991e920df6dd36d1072459af11825bc3556a4d8c3ce3b233994037b2001af6326baf10931110b7fd0cde79895449734c30d286bfc9293711d98d395bd2565941c12295320c548088200a63240cd8a448ee292516620acba4203e1322d9bfb906a3aab6f500bd968dc857e621dce7b4a84572438386814ba3f82f09d001cc01ed1bca6d6ce0a261e54f2379d70fe58e249994cbbd407d237567a2547516a90ea129c957d1bf92968455b6281e4c90630e50046460c0a50785422bc92eb82209e692948e53f7ecdf3251e92649e9078482d42dc07d3a197e43770f2e83ab298b9197fa5471a807aa3091d8c16f79c14114cfdb039eb41b55444546d9ec8d3cdc81eb20a100697b85f9132f09df3201bb0ff67149f67ef0489dcdb1fd9049588bd55622e3af66aedaa33dabe0a2e95cbf51a8d4669c3a1207af939471efff3964606cde3f774828cdc7b5b7e1d05b96dc824760a6f890a7a3260832ea1336eea0b8aa94590131a0e60f620b38d8f91db3726e0cbb48bf9cf9c6cd440661ba377789c4bfb7b194cc4ea6c2cdf0b484cc8ec509010e9a5e7e6ed1100f1f847d3b39e900a24441fe38aecf63be9d25b1522b0a95f575ce62dc13e08502834d64fb5d2f752a1d1ed2707f1b27cb74ed486bd38b378850d55cd13ed40740ae452d5f16f40b0a0a8bbf1a13421e51877ebdff4dcbbc38eea3e6bc3aba695d8df025ce6adf0e4f6c79828c4f129d1a9003101b167b8a80aad7a0e909e0e127a2dfc4ed12d276d763aae7e933f938a2a6ef374f8915fb42fe41c8b57323f1d5e418839d60b2f74c968d3f49d0e25cb7b07020e58ac32c6e2a9a85ba7234e0391be1b55d5ddb84120678b5d2a2a63e09f504859c07ac07766823ec793c01c5aae38c812c88c098842f67133d8f9dcbd07e16622be3963e9b520f2d77afd95f089de07513a35f64c358f7c2ade8d140b0d39d8f3d5eb56f552d82a1a9e90d47beb7beb5ba3b939a85e766e945664d225e74f20f46aed96732d0dda8923978afb3266a9471fd5663d50cacb152f822347690b8977d47856917fd466beb30abea5b4c69ff5db576531fdb31e7998f29c34deaf050dad5c776824efdb1916b44a1fa5b589185569ebdcca6989422e4eabae12a9570327a15381bbc3ed530aba7c6a6cba40f2afb1a9023bd86e0a41edb8e626ccb786a605eca8e626caafa6260adcb1a6a6323004c34b0dca1e5927ff1c64bfeb23bc3442e90a8411fd184890631bc3561203eb4d82a249d8bcb000bb4ebc3bcf082451e344302dc3436c3bed7ffeb868a9cbc4ed8c1da0d2129315153c51d535fe23f69aab77fda06320eddcc6a8222c0a14c1c33110d0954484f554d8f7aaf1869b49d96b308b3422b94049619c0548e17bae0335d88015214618f8ee020149a32802ba376e781cc5a3d3ecc68e3ca0f901c275d62c4cbeb6a5b94e3696b86de410043ba7255e370110a1913a0602e22e9222a73c7459aa0a5d2c58ce0d0e429c6e8083c6a8f09fce1c8b8c0b4470e46d760cd4ce4cabe674422db84b9f547025302ce77e86515afd1e57c371a9aacc1d9c8b454808e70b2f0a84500ae323275ae22d044d94ce7c1c3f93c9b398a7a91bc65ea00c4a08aa602fb89c03c74051df1d7d8dab9797632031c45f8835c7616c1a299f86b80e5f5ed32ce64607ebffb14ecd00021d34da300439284042db2dd5a30c82ca4d070bf2d33b53802c9a305c24fa27007af031106cac81aaae575f7780b7335cbf37c6d202b33119dbbd892e5ba493f2943e5b09dc7ba64fa17efcd8e7846a46eb960f20e0416fa7fe0b62027834b225a7813a561046faf29916ac2f5e9a6337ac083e40f460edce31505a3594ed20a8e1190ef1a530e8ee37de55805b849bdf69c360fe05011fc955d43b51962af15cb036406acef1f41a1e038d9652c38a26bec66a414dec49dd9ce4ce80c8300a6cbf50ddf02773fade748f40b44282058edbab96b34676096e961e57c0c30ba807d36f0b08041c038dfb85cc0ee34d205560442950f611fad096c9b385e8d50db509e0780ca4f45f6af1016fc54293d17c1e2f16de09a72da2b0e652f2baf323b7c7c40c90964f7bac7b67eeafbd0e533f4cd41a3e5f9cb2cd642d604a430bc75e7ec8598c60a6d3e98d3b7b0549e7cdf2c2c4f6308d09d4546e60cf749eaa4633eb26867e439668d13fc089a1bab028d8d0773a2408790ec244eb86f7ce25868a3a27c8bb0b72ca50186183082cd115be5d3d8381937f808f6e7aff3c315434bc893b9486e6914d9a92230a9c182aaece7357b1e596dbd47914baa8b0d32db1d4a6419c870aba2288c3b41d0fd8063c552c5fd7112182fb17b4b37744a13e4717ac999f8af9cd301318b73cce78d9bcba467ae837a334f6bc299ba88c24139c35981e8c8306361c18c6e2dbc09cdba894984f39d0d6ccc21fa1cd3476b029242e19a9bf15869183186ea1a8a522c29fef3c6e9059b3f4e8dee981c1cb6c5083ec52266425fa0577b6a0810898848890b8360aa132c7753484f0ad43c290256be509737f849c5b76982d470e088fd246156c2522383c6c4d7e8239483007624c22a270ae82eb8218a90263219db5dc3194717daf02c3efee24e87263473230ea289d29e567c0c66560087531450fab14d0ce81b1e96f672bf8832a0c3d191058c40d12bd57a252632df97253ed1108e62c784d4f4d1f87c0020e0e0493387ef05a6213aea3f1a00f40307af7fd3b9ddb0a4044046a82dd4830270c42402498f2612d982b0cc1da36e0850dbef145e10b10579f035a99ea7de32d99add64a874c61918b6bdfd8de94e857e49432ef67aacf9ae5f1da9b620aa6ad0129fc0277834983154cd7fd6bed8e28880c835d6a057324840eb77bd81dd1b97e94711ff43268d74726f9e62449bb9c1c32184bf9c759ed6f5dc686249b05959582660da6ec55d35f211048bc151720d66084555bd9f71ccc392f1ae6604ab34f1087eb08a00e894b7330ad5642ac67eb79007a303e248b722d2449ab8692be222608238159f55e68995a54f89c344dd765174afd096eeb4ffe47bd7ff5de3f169bf04f78cfca52b160837f12147e6c7906b3aa43edeab03ef863766e7f52f9c37673e1611f4e555747214872a9b98324661e6edd66173843a2b5374a3905eae23a3ee2b4c610734e19c8977572441816069879da801eda14028d78cca8a423554baa56aa565a6829d07d258a8678e87911ad016d2484769746b6a66c8d64d7862021c3a0222af6436898f709aef107937542c9b8adaf44e0eaa0308c5654f426896d5d6d429421b7bb45d82a898dac4f0bd9ba0281807a925ce1221e73afcd2a82f82c83a025db80d9062e35db5641699e4515343351db7e90364d913fb3dbf106584dc43606b89860306e6505f14889c5f63e5461eeb82748a99dbd80ea265a3b6516e2339bb5202cd5a22f3aba00575cfcab9f0b49683dcd33856c9539455fff934d36ec6bb4ae8352c48d7d7e9d684222ac0a264ea6d2a11531060f4b469f97232fba96658115609cf00013ac2e37091ef09d8a52a725b935328982a21449b0727f7e2c0ac6b9337e1f233f8f78a7142223c499eee2640239074487f613c4922b8aaccb72662435dc032f3b10ea020df71389572e7b75deeb8a3d0b02b6f224af124746e4ded03165212969ab3e4e4f86293f18e95c2bdaa9078a6da7e918a6ec219fe213251a106bf60bafb7ba2a4d37387b3f7daa2086d52e45d5cd90e7bbda870cec0eba8c2a22793be7104cda7b559f94f55d362fa9c6f275303a8fa44b1fb65429c59379246e5108a57bd8a033f1b538e029c33a58a0e4f5e04e8b9fe0565c809d6fcd725945cecdcd148e61fc3dee7840cdb9b1bafd9e4fc7704845c824138b09c5d838a9a5b343574b88ebd5925254ecac2b92ac074ab4574cf7a1a2f9e5354a1bba22e81f504e56abeaa8e7cede36688d92e58de39f69d8885b206b9419d26dcd850eed079b88bbc788e8eaab70676984f1b0a9fbcb7e843309ff98e3cc09fc92b460e2bd019b311466d07c0c52139cffae2668cc496d2e53af08870c131a4cabb99ffbf681d8f9bb09c3df34de9521a609f553d3dc0253effb9709172904219ad894299f9f70cf5401c27109fc255b72f5ffe08bdda04d367e7e5523ad2e12f04233c48ca52d290fe870cab62cc15d29366174f7bcdd3c3ecb359f44f6661ae6c24776e18915270d0ac4991bc712e31c64a05ca44739d2427e01f33027860def737fac94e02cd455c90f5b8073e25a888658a38cb185967dc8cb19bf513bc796c1cb6ea70f0b663f963ed00dcfbfa67c329488f75d506ad62decce209a96ab711029474f43a006fea4c44846f87968fa78759df056382fbf0343e8ae463074a769b6e0158daf4f5001e199dd12c82f69b91eee05f8c712b37fa328ffa3c268969bc6d6b522735e3ed3893cadcc228e256f94aa4c759afa90a06595fa9afd1039ed9a4c99ca2d150c863a1a9e50e944f5fb030719e264e0375bb45dc7f94fc356881b50c371a98ec0f12e7c0d11fb94824a10515c1b655405eee52448275b56797de082e488331b241004b91148d6404f208a1eb11b39a34fc4b8bcf5bb70b2ff8237b4c4d44f686cdfe512aea5fe740439a8b41a5687aa51c22250793af7b4c87e1434bc0757ca492d1ccf470221e7fe2f50674e7a9bba5f6f09ca131845efeb9789e2647823c86f5294e9be4e842401db80fab85d9a96884cde889ebb4d451ca284cb1dd63c27732075d0aab60ebc90e513cf2824481b1ba78d5ac44ac673327980b04da3c49df1bc80a368bd8a6ca10187db91933b3a88087030fb668bc43ae181bf7981557eca39e5b9f12da4568ac727fc916957fd1460c1901dab821616c77c941e4e6a92a3278bae2d846865d0b63d386c743034f7847bd52cc913a9d75388b4162852511e2589eca4f050bf29631ac8126042fd372c867edca0c614acb63c9e3ad494017084610397aedf4a4c8322a79506866b706c56879cae07500b7fc05812c07b6444f3d90e5a58277b6e6bd657641802c2d57f231aa3103c3001a88ba4f9c33eaf3277ce66110e77352b1acd9d5f90b2075c765e9cd0e4d752cbbd0ef505900c6be3de5b72f2455ac4efbc8cb68d6639086dd5f76c806249551dd3a541681eea978231174988735f6a0265e82d02054d1ddd71c9b32e82509d0d5299d4605e100f24451132ed56764e03a5dbc382ff7eb5150c302f3a974b8f2f686ee64a3cb187f7f2e8fab6efd9afedf310594eea869566e0b55634461b96c8ce56764bf17c93c52c2614329588ce7ebe6b0a921c604fc7b8aae4e2f82641aaa1400d96a8d67b4822783a0f0d2880ce233b9614888360e6b7318b587b1b0cb035c6ceca30ee82e1691f28a97100fd567f2ffff1a99d88c1a73c56751b1fbc674110dfc47e13a443c02cbcdddeb0271174bbe4454918d2fc5f51840806c79ba24c2775614f98710ef25061e171de02be5feb318d8ed4fd528aaadd90c5806c0d37e83565a41ebeb51f134251c9e97f953cf9db0d69fce5251c53d7aca79b9b4de74fccb12c21677b8706db190316654af4c2a49c5825987bbd3a4474ecb7f794683f2dea42317274761e17cef41b4e2d97b01b6feb63b8a19f7b81c64eb629dc644665d8c4247319e8e41eb8766c59990ecf025de014e4f669642fd16b76bc62434ffd39583b7b1f251ab8054d8b519aeb08130dcd18527b1146e72bcefc9584db087eb4a0d7a602a149a3063adcd96b4dd2316be558e6b1c388b8b0ec51bbdb2b7b19846356e317a781318ca8d21c947c9f29f13ab913813d86a5e586d7024d2431ab38c02e79c3dea0bd35c03451dcb867b940b5d3057f7bb0d88c07fdc740c47d1be77eaef61c04014a39eeaa8d4995b157e03cb70e9f67d72380bfec883ddb2390a75cbf4a201034c1208af35b520b6268658f5620a5530de58ad9223b2ca9b4c21e4a5511ac5ee57e4ff080d11097a1417c6128172135cb548985e56141ec6a501dd929961f2c0532377a0dec6c0be39460619056c2035a30befa371fb7fe2f92faba9563e99779fea6602ffd4712df2c26f25c6a6691376e2a45b48a10b15d3364455efa15fb91be5e91f8192d8b579544cff94d2ad183852e9a8c9730b97cb73b425f9b0977dfcf0adeecb7bc7fc4bfbf3755e744e5fe00cee9905e30bae8a880f5877e20ad0d92f1a4d210e33ff0fa0110bc0606de641f795c91e2458a12c7c10c2b206844cbe0b0fae23118e4f50df0396afa822bb0cc4cf5e5632b6a07634d4387989d8df2ce47da66864b10f8a680c44d318a54dbb027a77d064d48353508e6063ca10e63a4792883571d77e6e451f2d762261c05c0cc11bbcffd50f591917440d4f42c8e459e6dc5087a86a3f0b980f1fae9f3cd8403b3203dd0c913d709a76433018ad4cb6e0d469a0c2e0f1acc95b2a4a828074c13ff94a6505250dc2d26dd6b51963dbeb48f90143d08d0a64a4e4d4e1b0cffdeb9b3cb9a448faeae376d72703c1e61f11411f7a2546f821ea00bfa4e9f168724b2965b92d2c656d2961099d4b800d25b405c2b7443c908b1358131b994b86b787291f5607df0bf9bb60ae592f0f2a3c37df479016c1067e8b53db9d7ca8f2ca0134929eee6b27c68ab0a7b636858bb83e2ef3e7c676702116b5c26ce4a7b30c4b048fb76d2641b8a17b193abc57abf4a6a2347749946a1d1842ddf53cc64b97b068eea540ce71e6d1b4d66b175f11811d35e8835c5a4f624fdfcd992996e2bfe8fecc52febfd82e3c961de7904d69a4d5f385d68846b7141f4ead948420a02ad7ef38850059d8f0d0b59d705e40c3ed97d69e8d5cdfb49f0acb58d480b3729e1052bdd60c060702fcd2a1894095a4b6e8554ae41a85bdfbcd71749987c8192f4bc48924e86bc2b6660c6a2f8480e220d23bf02d36bd874ab751d4139fba0fcdacd89581d847dbc57ce9bea9f0d73bafadefa0469c75b5a321741db0933f95b477cbd9cc7df3a329b3fafd88b770bb880b92ba411272521aade407195b4f7c522017c1e4620b38328eb48a107c5e2668347d201577b07e2dd84c549737b228b958875c4608e73d5741e5b15ac23fbcab7fb9e31962cae8ef477dc26f7b0b2f8df3b23417185659aefc99fe02e4560bc289be058ffc87b180bc298ea48fd07e0f21a09dad4e7fe21e7a4c36bc483c496d4417eeac88cf524a1eb112b8fe2dc4d1fc1588b8485289b98428745e802f1ba62ccb7ae648ce1c659d3495a1d135168437a02434e18b30d17eeacd708635d446032c6489c8a08605c90a4c8032b52acf2cc7d31c58ae014fc108003848864e0302ffeac5dbea058ef6f992f962ff6cdbc4eaee9e5aeb13a52f20ccd9ba0682bb243afdbbb4351de22194ddf5b0fd37579d5228966f0c48d4862cac7c840a9d674339e7ca06f6a6171d099cf9389256b754d18ca81d09a2ac2ec38585a6323a3cbc6eb74765bf806b2498e08740566ee9b966b0f4984d36cc8a5719d9cbbce6d8972fd8500c17dce1e689d11a681bd25db4976c9c85c2e79885485a1134de0cc2de1a3fe96f962b962dd18cbd42372660c58aedf3f2dcd1286473b7edb1dc6384c735d8e443d621c3e1e919c8ddc730af197a74776bc886cd66bb0154cfd3a3e2752679fe9119a31434863673c4d314346a057313f442ca7951e39b05d8ee9415d4325242b374817f4344a7a040f343abe33362de5407a644492f4d699801eff98a77bc0011f4f2befbc0632540a74b9536964b8bee8918731c1336b4812d90203550edae4c5900e6c6e91e2a30209bc169a8edaaeeeb5acc6449ec71414fa1d3d5a2b60db5ea73e8f7491e7c7e614274e3484ba25d6f7c8fb8515f2e0fa0fbb5cf369e263031fb51de34730164af6dcb9bc2f31349732c02f71db0a5ada6807796c6e7c04be7deb578822fc10dafacdbfe5c01d171e7f447d8eaa0ab653a55690464677a6112dbfa355cf6c931ab242ac3dc745e3fbb73bf9d5e382718503c490a51e291685dae3394322a554683da9febe5a413474e4726559a4b348ec03ac0b0c5e78ae70c8acba418e07109879c0fa978d75487e93298201b95ab222fb84c7bace0b4838701eb793eaff51214f1eb72a29e9e57d351da303a60474367e640ef41ce266e5d8de39a44496a521da243f2f9b44e42698192c4bd2d590fc4765a50124a2a95b193c864c228084fbb1eba1722e36b3b75ba39626c876e3f4e563386cba11b8a0050092f0770728fd982695d13bfe3ff288890ffa9cf275bfaf17ebf0f5b84e5ffdeae4353ee6adff482be1a63906557732c5f5fa1d817e16b70c69910cf11f6139f30469cb8e0c4e52caafb4dd99c8a6a53f64b345331619f866e29ed3cbc59d3845773bb9e9b216893ea32cbeb81ce2c2429c2312c961d3aa98e9a6fb18e9e8186b1a871f74b332834dafb51b5de7ba290d4a68c81d8e7a01ea792a07297572aa942b9ec7fb81146132b3cae5969810c2a2288e895a30c8a5a52d5ec8c05ed8d3d548be281be7f21e505f080778d59a32318c978f70d362d19aa751543e5aedc648b50983805ebe2872ae2e85ac6cccc7c1e8196751d97d1b23db1f9e8d3b11befe53fa053d5ec009713a985c58e9d580064ea7f46422394b482923a32a1525a4f61f4dbb97fdb732ae8684c90459164dee9f0425370fe77b45526512485d917c7fb25064b32adb35eaf3b1445cb7b0af1898d1707650e5178d766acef24e7fb322318c27e320ca9a4ec68a6424edc2552446362ae72de1441fd1414c00d0a5327a7b6c14de67cb77edf4d6a9c28b4b9128f67d728f0dbedf03d8ceda9a1a2541cc0fa2a320be3919bdb34f954e12f45449528561c9b78578232384d7da37f6961968ec10a60caef73fd6b43030a0ec34d5f0a3d667ab3fa7284b1e2185b8db4acf4ce69677b1da104e4ffe868fb18f258ee3ad91bf26705bd6da2ab205228359af28c8a4e001b48a86dac4fe77169210a713aae090e8b67901c583d83fda57e6ff1a1bec6eaea96eea9ad6f39597f0e964e2540c57d3c993c5f17fa11b8a873e708fd93ff32796604079cb23eae52a7efcbf3ec4cef7c2b432729aeb5c3513e63f1a000bb58bc6e0d7fe79da5df84bc6988675ed621aa1b3afcf7f75edd64355ab2566dd2c4e7c8714b715d18be74e18b789d654f23ceeacd6b9668a84889c88b697f464d440c1f0d5d203ea31f3ba9cd518468a1024aa4b4a7861c8af7ed772e829172271400aa940aebe0b28d190024b9519e4bc20849d33e0606a0c410f7b8a7ba9fe9e3b32eb44970ebd727eea38fea6c76a6eecbd7e918936feea9dfa54a1c1d8cad725d5c5dfe19332d54fc34515202729ac116825d4adcb4d4f22e5afec0fbb7299c10ef02635d585ddf568c105187f717bfe19c9a572c81ebcd2a64d91c347605cbb8bbfc7f6709f82222684a6f1b7621793e8e15af7d922cad4790873296ee18dc8f4db6112aa80c43e377ec7b1e06840819c20f35ebbd550c8d13c5d448c3a3c2f074c0b19d224814e53516559314c9345e05a2a7b81083549655ddd0e2b110c460d782dc70c5ba10a0579397f7f03e92f34a6a3d94b76d0342188803ffa530e75e4451569003fdbcfa59a28d359c745dea3f6095d36b14595c1a9a7ea7d648ee2748e0f8aae3fa5b66204689bb30bb69ca164b748ced786d6dfa5a50d4ae7c1adc62a5007131bd97a32dede7bd1013667ac004189927b2520b17e6ab7da839915b01b527464565b70f86b011b64fda27d522413235c18732fa67c3a36187cb707766b9326cfe87d7e69df9b4f927f1b7b6ee0dd9599f5e6fb1c074deaf43acac52dfb0d271fb6625d2f4ab26e083aefd5a5efff755b22a69509707162726ae00a14ac5b3a9b69c6253a6fe2b4037b1b72d52732bf1b235dc60ff10825221463ea692752672a9e0e38248eaef49c952fae979bf4400dade605290f34b5683b24a027ac3680b83defcb81721e1bfb55ed795ff28836ff01857d2f3d047ae313a0cee0f5f1890845f686195e5b41a70e0cf927d60959a5b0f19237aedaa870ba7a6513567f067c5f77d21210e821dbeff172f7ab94d203ee5cf50202cbc561481b04baa4724437e79e56fecb36882aaed701426cb05e5b343fef082e58e8ec742e21824d893272d8604c1fe60600329aec88ae417d7b40f87545e8c2f056a7741f7cc849c714fb03e89a68637c9d3517656aed8f3ac0ba326b1a6af28626632261006ca9739c4e05382b5b244eaef039cd926db3295a843be1a4c6e06f3a2abf435bc73793f53c48f6ab054195e49370b1bef360634efb47187b24875da534028767d230a9b8240c8bc91dd9bc21e84a68472cc91b5e02099fc17ea71abc1df686ee4af38beed96b71237cba80c1b72c1df8f369762b3cc59057da00544b7699286f19fc2296b0c9ec13526a6055faa46334b09c38c4dc4dba07b26c9ddee9f164d90da8d6d7f13dd9136ce0db577623cc638721dd115ca8c2a020531caae18285899d0bcc1b8794d8c464eed82fb274016d460a32186a138331be5b9801c1b3f0dc772c7288bd6de2310593b9b12c7badaa8afaa330c110ece2f5e319b1e0b8a32dde0c4372edcdfa7cf667c6b8188e949d9b4bbc3a0d06a89aac99054bdef65568ac11018c2df0db2e5feb5b05901f422f146f912bd9c9b4d472bb0152433216b657a49dbcf53f086eb99816d9075318dd95502fd3779657ed8d1f458b01fbc6a4678b39b5e3e1e1452e4a90d1db4b0e8cc1a5aa092f729e5d515ceb97bbd285c661655c0829cfce9ffca3cfec671451fbfe33c44534b995ab03dca30f13665598e5d7ce105cd8f3dc9b93d9fe03fd1837973f627222398d0c3e14f3fed8d75b70fbe492bbbed8aa1ffed11f9fcf9167c110743741adfcb8c583a4d731c76282feb8ad91f0248e1ead0932710f374fb3cba6d03d35f9bebe2e7312cd1b43f1ea31c340b48578eb77b65c25c6797601f0788a9f68f974817b9a2131553b36b805bb931f7d2da9c6c8f56e817540c945ca109e8a2992671da006bcc090b249e43ed9538e694ae9210eccbbae3143dcb3442f1599aa8ea7ad6d735a3f9b4e0fcc81c62cb1b23794fcbfba44ccfc7c269e57525a99b61b079b91c5426e10716919bfab4c0076659d9c3fb5c2e917f0910643e383a22383fceb81b9cec6c5e4de6a31c8a8e05123c28f01aef3f2c097256e265b418c98cdc575c396165e806305fee058a29b46236ab66ad78c3f82c444104b302a04886560f8172e9261428704662cff2eb4246662dab335bdf6735c2608b3b2f6649172c00d1ce448cc68027e8e0311adaccd12d1caead81c4a4331fec1e3ea9aa98abbcd1e886a8201a1aeac0d087565edad817711a6a01501d5913eef3d60c1f4978f531bb2ac9707d39d8256e2ef0e9512bbec987e211fe822f5730a7a657d4cd7d3c4dde98cd485b83b47a5474eae72fdb7ce445724a2ca30057cf038bdecf62335060ab4c7cb2a9cf1882a359cbb5c7ed1d430e244df68487ab837f83a5072b31ce55c31b85ff1174ca45c92249fe3e13c078184fba4cf0e8364482bb55680e3f066bf356492b55103596f5b337a0f05254dd67f1353f96abc4d2a1de2a4bfb08c02814f32d283f478b01de623ee8d6747279e3fe606a6d81b497fb19a633f91cf16008e963878631f3d60dac06790e801040d3883831e5c093000387a083260f024df79c0bac013281ff9f7a7f2244fadae3422a7ffe2874afd80f780fd935b37981801a89130449484a01fea07906a3e11cadfcae0dca2e7e6f968b48e34e2a74fa7d90e3091f7e18ce1babcf5c3179de1b2eaca6a406addb69a1ec1040456a54fb540a8b874e385f976c42d5d3b91364323f2b37d3b0aacf8b63f7ee885373eac73c27115ab45cfe92fae47dd0fc039bf6ee87802ad1a7f9ce97ff01e33b3bb3bfe28e459eefa7ff48e5fda22f1b3c1bd4e83790a09d2e5076cd9ff3c5860ba6cc549231b0720410e969b9fd1f8f0be037435c81316bd93da17ce1eb9a8ef2951a9252711529a0f993ab2ed8e24194947d46787e7a582919b944da8670f62ce8f9f92a3432d1900ffe3aaab1aea8c09ea7413dc0e4fb7609bfa44f25e935530d8b7f928ac24254b39faf903c2206980cecc1b1c13bd679549a3d06174d27657cdb020d229503b822da2a799a698393b26a3d7a2690bf06ad3aa3e26ee21f84341049598f894cffeac4e38ba7422d72f17e3b030f4208e331481c9743b48ce4dd70f82aedf2c525048850c6a0e81ac4819a9200d9fd276214f2853e55045834d1781328f8bea346b477e31f97471b2b8cc3f41e409f440ff602c8923f337a2938cd806944c448182ae8f7ddbecf5ceb77740c8f9ece4ed2a3ddab711891014a09a6b2410088a7e79317be2c78b836797e864e397cb4e2eee8f3ed11ab3d971ec27552b78555053288db27596da06d39dee5ab497078c0f5a827c043e478fa97bf22700cba544d01b0a2315724207c3252259fb3546a585903336799764847a0000b26b805317a276ca2ceb891289a396f209de9f57070ea2071a935583c55a0fb47c9b0d95104ae6cad326c3f40866ea36d73d3fd651bf7f2ba189d9409572a9451d11b26be8d573b967d3ed1565105d7e008187388d448821583fca8bacb5f15ce7a0ed46f0ab0354f46790995c2939c9a48abfdf9211e50f324b8bb4186f893415e036e834412a18d12800d6e8812f930da3e8957ef13a9368ec1c0db6e7c8fbee851da23caa1fe560174a3d016549c8ac8b94e760ec8e978e0172a60a0b9d0e208b605edd2defc029fdd85d50ea53240827ece61d8613435524692056ee4398e070a21cd502601d26eb638a0cebd6c5b5d0e303e821aa405c21924ab93c1d9badb33d0bc0d1066893ef34bca250de8a3c45b1d50c55f4e39f83bbdf3eddcc3a4a9eab3751a93bab4661927c11daa4e7765d993e489785e1281a6c09dbeef30c6e5a00e9fe50858e577ebae07f7dba17263f3cb6c70bfbdb81fa26d817149973ae4781d47b78bbad680729fb411dda398445b1b83c0222b628d1a8ed24d4a70954fd147312c5cad981d4e94c6c38f9dda86e904164c36268f6f4fb267237609e53f36abd15a101b23e09576db946ec461c52a634c90f305aa31888094e43b42b803a603af4f15fb303882d43ed464fea417e457833b37a2365e652e96abbfc2a66b776387e3dd7f783107e40c45897677402cbadedfae68fc677905b4ff9d694e14d939af7b41df850e8d787f2d29695f4d22bbc8dbafa9bad9f57b0beda0d9ac6e95e58064afd8fc5bbf08d121cd487b5281154613eb04d84f4e83860904d73d2a67a28e41313729a145aa8dc3ff4b4d62cec0c46628054c1d524ef048dbc070fad7f3929f0e095e2ebfa744c87fff189aa970b0e48dfe0553acfc99931259dbcf4ed47e83c7981340b54864353f53ef366f1ca36414d57d5c9b2d506c77389dcc35ad87079c6d751fcbcb6f96e0248feb967b73cb6dedab11421edd478dfafc9d5ed6c02c6aebb5b998d1b01aa0084e0913058ada67de1506c351bef04890a4430cf2b33c09cfd3cc6801071391dca0c81d09ff78059fcd0301943785f0da9ac8ad1accccb28d912e6d097e204fffa7ba4ad04aeef046a10521050abb80dc4ebf0d7a801ae803be0163831eec07bf02bbcc21eac32558849cea8f1d57cd9be6fb4fd7f1b3333336b69a0e910cf3dd6a13806a10f9f2889322993ec0bc4a0dd3f34e76036738c07fed168a74b115312560f6216eaac54b23629a6846ab2cc3945318754934d35a3f861d129bfc94f45430789a60b538d16a241d58865469375164d36a7a986f4fe1399634e1211091061b26a95a52923b5900f9120a258ee4087911dba98a0de21f50161c972483b64938749d2818e4c8711d8f87185448706cc39a7988962d9857c088ae2f6bc1fefc77f12f2610e75e490811c8072980107367008639a5019f9d5a068b2d2280af9b005aec471b5d2590d0af599465f82fa8b3754e08624483704818421570cf948436cc8d0c84a40ca3290a1b21e6cf001c9862c6c186d30d55004520d2398d3a4229124c9c43013c9f0039f6436a11622834651283ba17ac8027549b29244e91e0435ea24e4c353287a5ef82423339b2c33b136ea146a7e42653659e64531b1b2a110d43626ef938d7e259640cdbff21b79c9da2a16d0a86d4ca38842958444908869f47e5c820a49352c51439569fa79f2248a8ec9f39d69120d2ba0a1a4010911844440888e901966a863061dcca0801934197420c30164483271c83253498e1f4964f4244bfb31abf1acb1a4c932b1acf124a83f0da9533f03417d0ab50745d1c6c764e2a5cf4a2572f4ad50ef951f57a5ee7509fe6426bf4b3528cf1a4b54167a2ca3f7270f6e9d8d5f19facca7ca1892a0450c108821c93481baebccc6b42a65a4ce5a60c9f59092207390828880140403417e4c3880aa326532659ea90cb550c6125b3a749d29c8eb2b2814113e2419c445279cfc54de8f3ff44788e33f79fffbf781a7130e939b2111094edf29cd94b292e7f590258e3837dc4f3991be14b1c010635a7d29f285385a3ee80528e6349524112c1fd8f24d5ea8e2474d64925ea09926b1c705d285ec820aa416c420b52005a9051c1682c08214241650b344fa552bfcb29a92cc6ac451932c9a2c4bb2937d7efbac80c60a1920adb0c40a3ba4157020c141028344428274492328cc39bd4e6527c96c50a8ac84fa4f4a288fecda47d949321b530d0dd9bf53cb0735f1bc1f5496939950355527b33171ffbf2f3d6942912d1f84f2f901f5288ea34f913a25fe7027451a3159693fe69046d99c73864f4673aae0c69c5a2887a4421773ce99a382933927d0c8ca5268c20f298530a689933ad53ffeb17c4829ac262905164840dc98739a50a3388a3a28259e0404042420dd84d3f2417763e1b47c931beea780e0b7470fa5e59bf82e72d20f05fce0804cd28f22241f3820f95800c947696a8d1e4b8dc7f29161467e19caf3c4efb422513419aac687e6092af3c4f2146a8ecafe10ca07e593f190917c60332020cd8c2cceb1882a3f8ebe87ac8c6cf9a0cd2245314a8b4a56a44891222610d43593347333e72c3f9527c9c8419279424609999124230449c6448aa123338120ebfbb1a4f2d5ccc65483aad1a17982aa990d4a0795f9b1460725f64cc89362ae9873a57d2a238964ad9ceda9f890628a803227aaa6d23c416536261fa4794831347396de871403825f1318114c538e27750a8a27756aa584e424392af1bc1f9cbef373eaa0487ac163ce498495cc14248a39a4972a7cc8227b68757fc5cae8c5ce2289b092f9d40fe96535857c3824220141cd717e6efe13277d27255403928b1a2e43d3d4f2416267d5a0847c88a2c9fa4e4e49725181d47282d9c2915a7ccc399b4c92880cd3e8c3efdb985a24ab6b5f922149c449224b12cd30e70c3249221109cb83841d81848d41c2c639716e321c4c94d212ad190fa0e6282cdfe83b95ccc6544393214992f9f0496663ca6c4c3595e64966837393e1603a9d32bf51a5284629439f2a5939bd64ad52a46682f333e47b7885da2b56cb7ac82c22481714f389e7a978950d5b263899459a08204d3ae7244bffe598ca504ca5f628630f720f2157ac64253de0d0c38c5219fa1c3d1e30d22375c54a89082ba6951ecb2cd43e7a1489c213a2908139e7152b19ff4aad124b960ea3108028c4e401826942d564a5be93955024169d62f51e5020a8755099ce230079e840c10e285431e7152ba5505c6db11c351428d9431f648e9ea084279831b2c44c2c33d5139278c2ce136609b26a7c388a9b262bbf2f966198337242144ea8e2849826709d954a90d5024bf26b420b4c808309633021354b6278f241ba4465a9f2d34cc06109692c61689a4a9698759f2a47312bf54c03859fd64b38a2043aae58c94a52e45989082b26926465e44ad417500294690241cd4725fce841129e98f38a95d290ef2552a7422d44127e126ee6152b254b0b21c18d11126680042490407304378e708023d05922c28ac96fa02d8eab8cfc428f65f465a8c3d1631181329eccd47d8b6489a3070a752f59624b2c575dfb91c6d43dd91a7b0b0cb3adc4087918218c79c54a49af3222ac986a502a5ea587de07e5cb2fd2f82546608011accc79c54ad6bf8c082ba61e6a23d080871e787471c54a564ab293652522ac88a416d2a99c111e554cdd977e5c811b8f203f77e031a76914ff1d66984677ace69c367ebc23235924f9d99104fd324776e8c08e25e69c2d2d94896189d442dd8f7658b1e387a9a68fab21ffd114618d220ccdc9f52a886b30d3e44aa7460f2533953540e157b2c491d44245c8e61c3dc92282195e358a65fff4ea87085098825499289644404d133922428c2914b314f90965ad1a10d4345928248eea08a3e5533f750c4d2275d039e7e8b17014ea67348426783e840ccc39fdce7270704050eb781e0ed9f2c9489dfddc588d85738383534373a3e255ca4ff539a172332422c1217dc7b9191291d081003a78a0e385209011041f04228210030436e69ca6225e2592453cd802492de48b94625884fc40d627c91c93ef5a258641baf4a992fc7446961f0ad5636a81a01e450fae541b0544aa505f98632a652550671e08f8076cfc80033fb032f528fe394030879569d220a84bdd93de7f1f00c1075598c896d659109933e714628e7a80821e88a007bc073a2339dc90630c39aec80113c716710c11471438f280630b38a0c0f143a7f8c85af1e85469853ab3f1a5a8f2a3ff59d6d22b952f6d4c2dcd9399bcff2d5e7a1b93d5429d657e0f65399987426536269a5a0555536332d164251a5b05056afead5a6506821f95cc9479a892f41d95d998429d7d71a5b92988fcc6acd421f9a1502dbdcac472c5ca4a28952fbf93f71f95a148dfb52a233fef979854220f20c003cd83283c98d34466a8ccc6d47d273f1e9d2ad91753402c714c95202896434a767081d10ea6ec60c9e88d244c9317ea590b14cb0c95d998481e9d2a592bd4a306f26346b2460f148a6026863aca1b0378a3bfd1476f6c3a88639a3cd46e69152ab33195a2188527f350a8cc669a6a582b9fe932f45c546d1e9d2a345f933e735e13c8020b98560ed898b35cad50e468224fa35ff1aff42a293ef22b353972adca7a400d69550ed94d5633a4553918318206fc3ccf8c6446a00a8023e4c38bce3957f099231c3c31e775454c67aa36dc630141ad228374984c1d095c56547141c18d3426399a52e5376a23e44aaca179c024322746851b39cce94b0f8ae47e9222359392873b5d7b261f8f8fd27d2865a70d2b6d4469c36b4348769389e50d8a708313dcc00266d0cc39331f7032bfb35267a3f8bbf6291028fc4813ca06557e244a95851f948eaaa11982d2a35ea1b21a3ca1066f645e0d5c500330e69c9958669d88f628d2db649ef8c32f33912a9f2a4b518c52922c9d1a52a24668ca863c100cce68d2a427ca122346697431e71cd2c572fc49c346066bcc394d28213144a16ac6032af3a38df69d24c914d0c744147fbaef2c907b9087523af8a47b9f09f95024c9d2a7bbb82250c54c959f90175ab1b21efaac2432af2e747afc08250641260cf280811b33cb4aa7166b7b1e29650366a8604e266d94e1634e151f3a838c9979641f4725a82c8ce281469dd2a1d66a423a4adfd166c494f18359861265a45e9082c943d6a9f46403e69c45ccd10bb28ad4425c7f9b9538863e48973594247001d102151796d429658e5c40e79cd4c45513b52945719c28c05060020f30e1472a5e60e042c09c264f8a20cf864ad2b380322a323326321bb3522f91993191e5a752895c0b6540e117fe0cd41acb15801d26cfd5a570b1308436462b18f207003ffd95d09103155c418e9701848694b8b2cc79158000735e03b89098f33a62ce6ba835e735e43b1f59a20ffac08c0c124527645c31c61373f664e49791410032463292cc999158b2cc4b913a9571bf22cbcca7c82042868c8d4ffd8c61658c2a63e499797ed442be271b928965968d81c318590a12b0b3f2be89f8f38d65e8b350a756a398896519ea920cc592a5798ab35419a95be04af5913619e9cb2ffcbeef3623d0b81abd07b5494814431e3e29bf5168c52a81a0fe7c92cc94a1c85004750a95b540200dfe4cc5ea7ef4bc16d7231755be67266ffa684823259895dc8f5fcfc470fccaaf04754a67265067395092f4e44401e2cb4ce1d7c727fe7fd6ce723213e9f74a8f25598ae216c7158b0c7d2696a1e87b467ee42786590945a232b2fc52190a4592e0939ee2a556911f4f298a514a9907b246282c88800e13b912c34f8f493a600211586024029a39a7cc4c288a640f75284e6965610e1ebd5cc00c241230918c2ee0801bc44f140f825f1321dfd37d177540d085c7028224e69cd3074ac63e3e3a2b83b42a134b2f6e9d098961502892da47c94cdcafc85639fa9538aeb21b3226c91bb806577ecc32f6f1c9ba277d37110122db494c3e48c4127ee4d6a5ee5f4c919a49498e1f6b5c6525e93b28d2a8321e27415c044d24e9913b649929b5ca7c0ae5c7520cb3ff653534992741bd2433a1acc642a142bf5ae99457e91055922298f91d66a4c8bff07fa89a92e7fdd0a052a41f7de9fdffa28afc429de29ef4583cf881dca64c017d60460a8920a8f957921f08e6dcf82fea9a14a99978de8f1f6932feadc432d33c99a9243f3043e1fcdca0545dfb3114bd4a248bf8a24dc9123311282bc570484492996cc22fd3534428e427148a650f59a91f32339556295233c94c28944f66da99a94697ba8b2549845c69154d9699529aa704249659c95a81ba86f4656b67e1479365e24f664ab2c33f0d82acafd23ddcaf7c76c41fb27bdfc3c51f95a89324147b4050ebb8a02e90438ad429cfea624f03be687d51c417a92f7ebe00e28b23a32fb82f6cbe28cd3957224d6624abd1212be990792b312be94093655e76858839a7688ebe08e2851e33f36a68b4a7248af8c3d18ffe88930a5b9060cee9433109992376964fcb37e95fefa2085db460ce2976960fa94f6267899d45768144ec2c1285123bcb270a399a30003367e6b546b1d43dd4aaac6b24a753f9a9368b3c959e3c8d7e8850ec09e23e35e2028a2f7211665cfcb8c011e69ca64c546540a18fd859e4162b258440f0fba71549962cd177b1f4e4d0f7fdb83a81e017740a1ab5ca8f39243094304919686192322025ca9c36ccb9c384a1237d519aa42f7426e90b2a26e98b3526e98b3b26098c1826098c1f26098c004c1218ac4902e38a4902c3063f5a38028f393a228f394202c81c2111c31c21b1cd111250e60889297384441173ce1758f83167e61c1d39cdd111d61c1d79628e8e4c608e8e7c608e8e8c314747d498a3233898a32371ccd1113c56f831a4658e86b03047437498a3213cccd1106ecef98305216b2099a3357ae6688d3f476bb4e6688d26e6680d09ccd11a62cc397fac20838c1850e6480c2b7324863847622431476234608ec4f0628ec410c11c8931c61c8991c61c8921470b3f4668b4e6088d24e6080d2ae6080d2de6080d0dcc111a259823346030e7540186155c98028d399a420773340510e6688a28cc11152f7344459039a2828739a2e2c80b0c435831474358608e86f0628e8610c11c0d218339e70f16640083ce11183773044693390223007304061273040615734e971f241f73024961cee9b2820f16cc911333e6c8091b73e4840773e4c40873e4240a73c40333473c2ccc114f0e73c463e788c78739e201628e787ee68847cf118f38473ce51cf13c31473c59cc110f1773c4d381980be829f381253401e4152144e79c33a6093a641ca185384afd331f8a3f3b5020480d4e988004e64aab261525d86192451d53489b62a7a8f89076d2832c6e2e511e734e2c5ae0b158821cb180d20180450b73f630b1583caf1403a408bc117ee2ea14811144e0a78a39aa380327456a263740644b83be8742620745919fc6d449c50a87b4aa8b56634180068ac5e27933901e70e45be99312365663510186cf1641245d898a57f1a5e6a1a8d3773ccfeb54608a2b301da6c8530c9962ce39331f6eb2cc43a1b292d42b2c5d931c304492a209734e92142398132b32b11a9a69a78439758a90ef21454184190501a228490f04c422fdaa55923ad5434ff464a7d0133dd529a4273598c3c98fc7fb252f198a24734eff331419b24e588250e0d6a1ff469b960f9a5c355d13b501124122980e930485901ca00032e7147fd80257ab9c1fa0f01b4b9f92eb95d55838a7d1af86b4ea6648ab3ca9378ed55837435a05ea1f0614997392a406c905b47c3da04ea37842a5c82ffc7e86841c4d64f9a9b06cce796136fc7087a7a5a1901e00624fd77e245b1a8af7ff44b6f48a2435c89d7002e7c412e468ea3d38d142781a7df99d54e2f88d708f45015050c00e399a40ed25c0679a4c2622a50749d063f1408fc583de9c58101826a9532952a79a8062ce72145364268aa5e7c9d163f123f6820bde8f4bc8d00789a507484ca860f6d0b3c24f95e1643df4293d969b459298e048071863cef0d32926383837a02e43f1886f14533827cff3df589e56e4c903912a5deacec46aac1e0350993349f7414facc6d2e05659a028ee53cd92ac94c410529623e6f43835393556637123542723547d16200a1201f2201160015dfb281df4f1477c3ee1ce4a3532d151b2528d4ce6c45cb09693e79d7c8a700116deb0c10f1510c20fcee802073744b8f052809810cc9042a0461499098ec042248840c8017c30021d54388093080c810288a5020b1080c0e608411e10240d1b7891840034f851800f3c7811c24802c8818444063ce988302e3de6bca2703de1ca63ce0b0a4f20b59620b5a09086e6200d91d184392f262ce10ed2500e24a10f4c2125744f122a616163ce39844c021496600ae27a8b63e6b7d8b3030b09a6d293a0d6a54e72eaa1f7401a4a49c7758410e803b53fe2234b96d8d24e383cb2a0268c0a66b6f8704d98e942ba60a60a97cb0b570a578c6de1a6688a961a60b098189716d1754d1830d3c4e62544cb0fd784e9c16562f3ba2e11c6e4ba602eecba442f47601776c1602f44b9ae970b6662171093087b79a199c9ae160c6b81e26ab92e6ce6520276c94c97ab0513f9eb2a02bb2e2c460a360376612f2dd85507765d2fd825c405c37589b01816b8886052b85eb8aeebc23acc8518d14581eb05bb2e4c74bab09619998bfdb870622e971c443e5c3018d65245331876d5a8b05d48b4541f11c066cbc42e70b588685a5cb04b345da8b87a704d6c8e46a379cdabe5c22e3bae18acc875612e1826ba2eed3272c55c3f300cc35e742e2218d6825d5464f21143cc0e17901564c02070b960315c2d97e89a18142c9bb95a26f6bdcc0c752d403487b0e09a475e8089ae97ab05e6027289aac0b0184f7465d185612d19f64386e672b92e18da526da01736f312835dd775c15c977761980e442f970cd622c272dd80b1ae4b6473c9f8c0ae18980b3b82d580d580d1209aa279a970bd60d805a465246ac162c030ec7ab95afe2593c3e5c305bb5cae14b819b0205e7cc860d78ef901835d425c31d725c2ae968bc94503d66142301f180ce672892eecc22eefa201f381c1b45c2217d1757197e8a2b964ae1fd775f570d580cd8860ae98960bbb2eefba5e5ec05ab85ab0cba5e5ea301f9708abe1128930ac056bb93a0cbb64c0662e992ba605bbaecbbb68c0666044574b4ccb755d5c760529127323e2c1e5125d32574b904b46c6f341c40c355c38b45c18f682c1f86be402f5c1e4bab00bbbaecb66c2ccd5a4b2c7850795706653e4a0638d295af4600352b41cd4478e386694213998426831032f84566342d1e204584019223e7922471178381952031280904308edb40862a4d32980c1c90124076ec8ccb5c971456143ed91c596e9314d2828d46083ea022bc5c18558ca68111e2850bac30654f6c3860c8615260558b832512e51ccacc500f33202ce9c338a112e2f4bcce810832c323031312f33332f2e3e4434c8c0885c442efcf849a2e786189a979998dcf24385abe5070c007e942220a2918b1b06b022cb4b0a188c4c4b09080d79035966a6fce82da51492c0ee0c2e525aae979b1564f1c149129201b096991627354cc812c712978b47b2e14812588b0c221b1b64e1418e2c364059c20515901c400444124c64894186ca8f16acc8b2c485550c39b3842c37a50c10282946303222202e412d09782289981692c8464b2208159029728325629a008114301f2e4748305d4b134f44184ab0e04233b3646687172b2d45603a2548a005104acca001469600c29d28a005597cb0be040c00c4aa70899125862d891998ee6526260511b70384888699253049ec00f189613283922533324c7ef8f41b126481a1a5b4cdbc782c007999f1a1e486007091652686490ddb892c2ea291cbf522c4cb8c4b0f2f45727ef0c1922112c3e4650626062049647268e99181a105213bc8f04291189a9e283b2431136382d18068dc0e14981f2d3032b878a24de6470b91ec088c0ccc4c8b1197262df9c8e532836888cb0b5426868b3a253d0489997941864b42a7c7c78da169c9a12587972fb21449a125890b0f2e35c8e4d02244852aa241892c262397e88b6c5a442e48dc10000788210736b006196170e08b2d145080010c9141df735400ac0c51658a141f284e8260e2658d66071d70c85a5ee0823dee2042107ec0d1e106196280a1800cd0c000174228f9c1a3c1851f3e5e5a70821268a00b0c6c61012d288085141a22464c12b0628f3c92b087b8c38e314ea2140df83e4e0f33305d44010527830337bce8c2eb36185af0311303236406314460c5f7273d41ec2c5192050312c0c40a7160147060035f1480ca146e0bd242cb0cd640c2141c1f361998970e20a0041ea8c0031ce0041213c8228b063ce9c1e981c88f0b0ad813b026b4e091d981d1d112044c0e9738ae1c606d603790b1c10f36b019606a60271071000b6a29e2ca21860617214062c060c05cb85a20b1e0b20246ba462d29603e5c66309998180c06c6056bb944d7f53281a8209a4194822c2d0bc8d2e2e4a54876c12821891117595c5c7ec0ec9083245ab4a82d30fcd8028c170564697122f3c28c902006c822038096214435f8285f7ec42c89c92f33a21e82ccb8c4204bcc08b2b4602509194e84c5117ec091c5658f2c2e3f64b841961fe50d29c8221324050ca60717232e5334e1828f1820a044cb13595418a2c210910dc9484c10d21e4ab8f01093616e442a886e62b6c822e2a9465e665e329045e4e3458696928f2aba5a7eb4946696b8f000038017195e665e8af8d1c28f166482c8bc0003005114947891411486122e6664d1a188165576d2818616ef870a2fa4964b6423d2698102e485170c860acc0c303d2e2ea470bdf810b9b4fc1089449808461423929911d222829102b3820a506648302f309708a6e5b61869b9626210cd90848c0d2e606449210349905e5a668849a2440b1c4adc70cae61e7a0401076ecc608d33cc6001f944018512243ff8c089e11d761821325221068688219ef4ec91061b34b0a101b40a081cb1028e36d428630c3138800123a61d1ec02a1b408e4a30021e6fd8800d1784a0000408809521aa3ce90162890d35c0f0c2094c50000e6c80000338d120440c6d48010e1e18a1cab2871e4c58821522607881d446192f50c1186288a0030d6040024e01000095273d3b6aa4f10219a0a00a263ac8811a6994e1823152c0c5164e1cc00046c0a30d1b8c9182137860892cbeef282101c1e30773c071860b4c5002107811c5027c3f05c00a11547694f4e0d59080a891c6195848208a257c0f40103b4b94f4e0a500a465fe600e35d280c10b4c50021074c00b2918b0c49520767ce8c1e36ead5981d432f7f8c11c3c80c1192f7081094ad0012fa48882010b58a207a72b567e3062ca030a4e9aa890c20e8440f1841355a8d00a2cc089d6900664f610e98145c1050ad81362c068f902f302c3c065b12aa297c9878d880751f6820d98906b06181944315c415a60b85e906161851544a3161580a4300222c38f187c089921c9c0c4bcc0602f2f2e2ea24b86a03ec36f5c19f15de45fc82ac0cb2405ed105443114d406534590fad51eb14cff400c422b828a202454c3127ea03c99acc879b8cc6879b6c06154153844f114ce69c656b5f31994c44ec30992f327c6e84a32f4bbd3232961f155f961a2b5f94ef6666a52e7546663bdf8f6cc8777f536f4c72341949612045c59cf30347cf02424c0c0e2928f3c263ceeb8e392f3b5a5ce6bc7e0094c39cb3ab194e368e2c5ea4eb72c7755b87bbdb699ded6a47bb2ee7cce52de37cb3966dae99e68ecb1cc76d1ce62ea77196ab1ce5ba2d6fdcb66d78bb9bb6d9ad6e74eb70c61cde30c6176bd8e28a29ee6ebedcdd2ebef76ad7de7ae9edb4ac71daa661ed6a9a66b5aa51adb3d97276b3d85eab596baba5b6abb97275abb8deaa555b6ba5b5a3997274a3985eaa514b2ba5748892a349c50a8d94aa7f85889912cd0ccd00b2016448645969a634834496655936e75584392f22cc79d521841f47e69d00f580af803ff44f2854e6cd9450a8cc2bbf07b36c06559365e5f7200dca071f7c1842fa142fb56ace6b084220fd1a9aac448448d09f737a7e071e0533233f0f7a244bcc583ca3388e4a90907c15739a4c244f002fe49dcc9aece626ab34d9171d8fde82a0f01b53e437c7ae2f1ecc79ed40a40451107813735e6fe8605e3998f3c2c1d5c665836ac3c69c570de6bc6830e7143bcb274a1415af52ba19c525339ee77927d2f753e87bff52a76fa54f2b92c4b1387567863c799ee7e1f00fdc2c28ab55ce8fe7e590913ad3232be7468f2c5d865aa8eff404815353b21a8b06d42a1d8e46949e247d10ffca9d1e7e4dfcaa559e48efbfc7f201a9f81039a4553837dc4fb1586e54bc8ae76958ba3742d54f55825071c34465684e4d258b2595adb3296e90a6343125cf3981c20ffc524253629062c6fc84c82fdb22b8572429ac51ca9139a7d82365ce696a815f26f6fc98314d3f41734eb22457be8bdca70d9f06f81041f2e9a2088114e50c9357899c55ea39a569f59551a888f2830925a684505fc9fb9f9550de7f544e0fc53da45542ac214496690277cda6e91e8b4ed5681a2198d4d06450e080d20414254ff098d3844265628922c38f07959125ca148a99c7e29fb460ce9d71d2139d39e769927ae6e8a1c09ca6f1d3a9520b8d40dd67a5ce31f991059402fa52ac91fcfe8ea9862c693232fbe2a987e2eea19942fe67a2f8d313c343078f1973923c61cc39799ae0a932270f4d929d8c67e49dd031b35093529c28c0499ea8955892591334e63481a526438258238856105b10d74e1c1395d9802a5e65a555280f85ca6c7874aa90a49d91b423c36404734e13aa665fccc94ca1f88dbe24f51fbd90086639a614d7e2b85aad48263f4c704840b831274a2cb30e8a2510241099b4840d944a256a55a975f97156f7236b49124bec929639754a9668c4c4a353858c4252220549c9968408739a502db0fcf807ae867cd6552290a915ea244c2429220914924e1d73a24cbad449505e66631a7daa1c597f258efd8ba3388ebe7b9255ea53197a1227456a262952a77452734e1d283a344162c6a35305091e2632d3610e9235900c21a982249bd3c4cac21c24319347a70a29878e39a7a96b243964e40ccd692aa1e4e8393512cfe31e8be7f5923cadc82357cc39a77804892339cc394da41ff2f8a18b69020a7dea872c3fe09021f48393659a80329c2a262070684c64141f9a30a7292bd1e0f41dd207213767dcb06eb49b96d6f61b85ca7ceaeb91b74470e57b56f2fcb341a1505f14572bd60f96cc873e40e1d777beee49d6a907d49c26520b917a1801859fb786e7e58e0211e87eba9aee86ac83ac453622f34c53f824f350240b9579285466e3912dadf228211f7adee8572cb2fcc20f0767272bfddc8cfe66484472e2565c906d05dbb8d1845ff6774c3d144b531f45ce75a6a7600fe02b18e60e8174abb8248d0e5348ea154b6c859afc74a955a36f815c8fabb1ccb449480c83bed293df1e75d60a75df3c6c9199586a249a57b14a5033d1a86848e66ced96e631a15099e7d9384c3fb68a394d366522d96cce69eae127f453c99833eba09891750a53bd348e9ade47dd435d9236a11892da6b9a6514bf4f46a247e687ca3c75619a3a1671751ad22a5dea24da87a72bd9c813261e9d2a24239f913c49a6264c13cbb406c954c59cb30c3d273f53ca04c5449a68e6678a99739a48367178decf0df753847c68a3058e4ae4f1bc1f21312cb1a0f4811211251572c0030c783882072545f02015d18109557af294d5f8205d7a1e2afb224d5655357ea34aef133e41699a0cb58342a15a5bdc3ac5bf50afb21d1e8a3eeb3f38387d272bf96425262952f7f8e0f464252637abbee3fdf8c552258e9f54848654c3849a2ee69c269214c1ac6405b66adfdadfe5aae910f0cd7134637bffbed9f70e04bb777eebbd75b65f7bb59bf23cb13cfd707da06e9a6e94ebdeadd67fd61ea85fcbb8ee4d53ad51253757072aad56f7ccd9ce6d9e622de5799ea744e70a836eaee395cbdb725cedc639a06dbbedeedb5db98fb9d61bb8fb6f1adbebbbce5eeb39313c690073db5afeb9cc59ce71fd3340ed563bf65ba6fe579d3f189a6edce7acee79eb74e6fe179aabd55eacbdbf1bddb4ef05ce9877cef5aae19eabf65d6c98bb9dd332bddf6a3b770c68f963fbb56eeb74c5b8762e2add38ef3fd5eaf75f6f7e018c37e5b8bf69d47e8c37bec5a6f5aef7cad58df37f31b7c0d65d6e739b2dcff76b7e57a0eb2eebde759b7a4efb7a6b517be7b5ed9a739fe3aa3705b65b7fd657cbfb6a8ff39e40d5b96e7a6bdd2eed3eb7b3d0b4ab765b6ca9e5baee6b2c346f2fd7f7e6aff0f9d79d3fd6b8be344be06bbbe66f6bd5fedfb65b91b7e51ed75af3a774e311f81add34bdb5e53cd78dabe27fad6d5e536d77f6ff0d029d6bade6db6d1df65ad5f40376d5eeffdad7f562cf3d15b47fce67df75ade31ae36e8afa3de73a5df3ee38ef9c03feadbac31aee997b9e372934dedd72dffd4a399d6d8ea277f57675f3dae5b6963b14da7e8eb9cd7178fb5b3780fb8cabc6edd6696cb58d01dcce9be2bb396e73bf29b7000ed3cde6cbb5dd32d5fc139ad5b76bddf5fab5ca39b1efa59cfd1977faf38d5380e678dbeef75b7735ec13a055bf754d6f1b676dc75b139e6b4afbcffe7a8db5dd98e8d7775dad795b5f39ef08d836ad55cbb9b61ac6b9e203d0bbb75e3b8dd6ff6be7ef12b5ebbefb1ae6b6a65fc3dc0074735dc3f9666e2fc6fd2ad1fddfefdd2857b1a79527716da759bd374e57def14dbbb250ca6beefc77effb963b5b007bad862bb7f9fe33dd6c2500ce146f9db59bffffda0f00d3df713fffaff99c711780ff96eb32fdb86b4d7f45c2d39eabb6bed28efb8ed294e7851e3c718fe5e479dc3b9d728ea01feb9cb97cef667dc6ba7535ca519ef7d55c77b156539ee779382ad50e16c650deace572ed3aeab9ee6f5ea8bb35f7fee94675eae457ad9f1260e95af75d77be7dacb5e53ae579588246c0fa9dcf95e399df5b69e771caf3b0049dbef6e00907674a13ec8b52e7ba69556bcdb77c31dde4d537578d53ce51bed9afc70e77b6fbd4ea4ed3bae734236ebfde675e39c7b6a394e2803a1c9f5c6fac6cf6db63dbe9b6bb7bbaa53ccf0375df458f6b87bbcffd6eb7cfbaa73c0f28fc8238d0e855a890d3fb6afdf7ef1d776b69caf38850e96ca9f7bdc3dfd68f3b9af24ea0f625790a0259dbf370a6949adc0c01d2cd76bd7ecbf7de78fb41f56f7ef966354733a7b7ad887b35aeedd516d38ed2ff5d7e2bfd9c5fcf3f976baaf7ef7dbd5de53b779562a0acf95a37bde9cdbbd7abfddbe1cabbef946b1bbf9ce7dae631e6f86e8a39bd36e57940e1478e2ceec793e7e178a0ae04e76befa434a5d4a454b2783c6ff441a3567d1fc577b18789e7f92e869ee779d8127d5bdfb79a2ddfb4ea79e63ddcf3bf6dbcd24efb387bad0bdafffabfabb6755f6bf4627ee34463bc55ab79772bf7b5eda988da54abdba7bff24ddb2df35387b76fbfed2aed9ede6d03f031f5fc6abfdbb6aabbaaaff0bb596ee3b9e75c7ba5db8afe5b775bffca5ddb33af4e10d17bd6976a9aeaebf1e5b0f64c0e3004ddf0d6372d67ac379cf9650000789c35ef3bedbcfe7bffaedf44a9c25daf594fedb62fee9f6e2a5fe3baad72ba75dd615af514ad5d5bb9ed9de338eb8ba56878c379e3ee71de7be7fa83b5cb6fd7b0dfeeedfae53e18e38e5ace697d3fe6328d627757b7e69dd51de76c7785e8344a3b4d77ab658bbbbc41e932fe155bee638bfbd71cd013aeeb68ae95578eafef547b1e29f6f09d35df388777d5f6deb694e7b57cd049ab2e8f66adb595ebb6b33eefee9dd08dee0dffeee79b35c7d526fefe4eb7bad99c35cbe51f04e6ece63b677ff59db3bdeff0bf6dcf58db5fc3db6acce4ea6b75fd5cfb94eb6b3110bae2ed625e6fa54bbaddafe6d8d7bd71b8c35d0d92520ecd14259a7fbfe15ab5ddbc63fa4f6292aed5ebedf5ddeb8abbbd35d1d1b86cbbb637dfba8b79f5291c20d6f6493cefc60749d75ebb5bbeb5c3bcbbfbe75c5abfa79f73d9db7aad4e791e0e0e8e58965fb9a312c725dcaf7c3cefe65ae248d6f76e9ad5fcd36c79e6a955abf43c1c1cf18719b8527d379e479ef80f747738736ecb5a47ebb51e07731dd7efa6b5ad55fb2de743adb46a9dbb5a5bee668d1eb9f1ffef9f3df7b196f5cdd9d483c6e1ccafafbf7376d3ae7ee08accf1e8e795d39972ad615dab4e79dec9f33c165185d361babb7d35deb7dfbf559cf2bc535689e209683c9d62b8ad79bbd15ebddfbadcd59bf2bcd3ddbcce9c6b5be35857bf3b9fd2f0c69cdf56e3baaa39a69c4b799e90380299eec5b4dabc3bce739de5e84d79de58929aa671fd6b356b1cfefc679ff22cee3a4ef39fb54dd37ad639e579a71eea93d83dafa77eaf654e679ffbae9cdb5acaf352246b751ac57ff23c9c9c92908ee2794768dee8c6d16da9df72c59a4ef193e7e1785ea9fa54b292a5dae1a6072395d3bad7b0cf7ffbdcce34e5793899b5c38de71a5c9dc2cff3c672454d17e30ed76eebbcf17c3f4f795e397a9ee7f960e3ebd6b69d77b7f9c65d8da7c493e779f8245ce2799e47aec4aec437cef5ce6d5a77a5d77329cfeb3ee481ebbace77dbd37d37aa2f4f791ed9bd274fa507f23c9c8cd43e513c4f25f6508f297d43163991acf17492a1e6069a5fb288d4934f9d84ec209e366c8be830ea92480e31e020c30d415c6f10148b0c9121abc186186a1042c3a7528934426e9861061984c470371a820c8942650d306cd8662f645b4fd60769709f80c22f45ea54e60291162c0e2cd4930e2b5895c84f7e3c69b2737faa411bb62bee498e94e2a5de279ad1e984830a435a65430af5248227a0f0030212c1188068f407111fb614c599aefd4823538a01220253c9222f96bad4b4a86a108d62a98560d9554fa3f6203f9dca217fc39cc10916681082cbab74d8c2020d5230af242a2c21bf95fe7161e1639621842b7266a802838af852409f6f8d3a05e08a1522860040152a53a4fcf8441102ca931e1e274d769800b104538225d1e96192a0a0992428a6b890206ac2c7112c60843be69c971346d8624ea0b0d4e409d4e46822f110c5cc89dd6184205ae0c1c49c981d5811ea3811a18e39efa89203c98e32ea8052879021a0e64908091002969979393a9dec3bf6e6ad6e1144a261c17e302736c61ca420b860ce89cd4102821b62f8a0fb569863474fb246bf62f9a87895337a007e544030a793604e4c8eee9f8060ce9c581c181c21ebcf89f1a07f3b30f0a51f79ce9cd80e3c98821c94e0be31b30eb6dad2e3087230a7d55a7a1c73c839b1366e30e7b4980dc6c0d8a8c19c131bc10c60303113ac317de94b0f0a89e00fa580206bc9d6624f074526fe886f48f3904a92cc89a9b112c7508aead3a0ce90e621bd5f528a3d4a44f167a5c71fafd23de1ce901250fb24624f10179d8452bcd823461a18096430e724b529736268cc39b111c460c89798181d8381f8c31d1064f5cc899d3127660656c60be6c45ce0d3fd8ff85b809180054384c0f0c68f3762486f88e6b45adff9eea682960f9a132383da966f828d312796023dfa2620101dfc523a2d307c3227868272d4ab39b113f46f648150e6c44c00ea2d824078fe89533c4f282e2949ed9d9439e24a1ca1843af5e34791a7ef7c25187dd0178a3c736224984a40cdbf30c5fd0f487e40de09a83ffe75561045b490cc898d60ce3931314410348a4b5a48b44a93ac263df425e9a9885ce42215fea9584afc28a648ad137e222f3f24a41f3d935ac556d1aadc2ab8ca5685ab92ab781e69352c9e67b17471b7340ff75370c41feedc703f651471fa8e4a0c7d57892149921dfca8e0dc703fe5030a594b42d6ef57b69acb51436975114939ea55df697511090e8ed56eac26a5244351272b65d64db69595592d1f743756ffacc6c2b9c10c76404280268014e0062df32ac265c79583eb04170a6c806a9941163692f0c6036cf88109364a73cac42421d5208c0b73c28d420d4629a001cda42285096471c5154f30c1440296c839004e0964ce8fe787f0cb7ab0990e59e8419af930240b3d88d52042cebc722e52127c580207347043164822000594293f084923e5c1bdbf1f215167456616f753c86f95437e2b0d14822bd52703d50402070d374e8006188327cc3949dfc5300641cc3961608249fe94de8704831f676061460ea6150fb898000a7d134b18e1472ca432ee858005ac7c490691a5cffa8f5f3d117b2e3f8abf2423232333136a24ad72fc58590ec9055764416ac11b2416cca1050bb80e488431e7cc0147b1821fa72c0298130915f4a94bf20932a0cc795d91c3185bcc3934469539af4c520a323086cc1cea2139924cf004c904554825b862ce0d5b51e8c70769108912507b9f118a1ebd1e95608c31c6f8de7befbdf75e4dd3344dd3344db3d65a6badb5b6d65a6badb5566aa9a5965a6aa9a5965a6aa9edbaaeebbaaeebba9c73ce39e79c398ee3388ee3386edbb66ddbb66ddb30c618638c31bef7de7befbd57d3344dd3344dd3acb5d65a6badadb5d65a6ba594524a29a5b4765dd7755dd7755dce39e79c73ce1cc7711cc7711cb76ddbb66ddbb66d18638c31c618df7befbdf7deab699aa6699aa669d65a6badb5d6d65a6badb5565abbcc6df86ab6f2a8760010c615a337c23f29e2e8876821117bfc97a4832c910a8f90087a243afe08715c22654e2c89d1a7ca275d2399134b4014b1876c62ce893131278680282dede400d81273622d4d76ae4449a6438e624f9833b23817c39c0fcb972464657ed4feeb5b93a13f625cd27d28a45764066a1f8a3d7362496059e6c40a3027468001cce9cb2c64d5b4f4d6e18e1f5925ebfb9155ee2c558edfce74169a429d812c52fbadc11c13e94b31247b00fac82f72981313c09c1812242ff2c05a437396627845c9103b3ea7244a923cd1e951728af2244a142527254ea42479c273aa92c39313fa20f127b37054bedc0112c1bee3c30f2854a2d345076a68a61260f75717182063ce7d2249bdf2582f431d7eaa6fe4e4d733152bf4a48ad44299c80428fcca51dc3999c96380098624674e724eec08f603ce9c3349f7a3388e4a405dea255c70ab95f6bc9358ae5a23b79a263bf77e6495dd7b9e1faf5cbcdd2df41041b214419fbbb1b6004fab95f6bcd38aec2ac676242dc0c59c33db2a25068b0aa031a705b639a7e79d56e47c42aa800f723495dfceb69ec8d1af409fb21a2bdbcad2028b273a7362181c47255c04c11c0a1401879102b445c810972a5c9e98c004a64f8b34819cc5d0fc59a031a75e196995988655324a06e4c92c55e678de9c98ad59c060c1225de103098056ec60c396891527985c64e9c114655654b1a2ca9c43140101bfdbdf7b8d3790ad46b74eab5df5bf6e5703b8d64dbdd5babd1df7352d035ce695dfadeb2ae578efc0a01de5b68cf5d77867ca7d51b3e6bf6e9953eb8556b9aef24efbddef4e3d4d795e073f9e9beb05a22ebae66fa679d39dd3cfdd8e01dbebf6fffbcedecb2b87bd5f727921e2e2ef6ef7c79b663b8dd7bc892ed037e71dd5b5fb5cee7cbb116db1b5efb3a639eddde6f4f62de0bbff6de557efded58f4515a0be664af7add85fcd7d2f51289712222d6ef7bfd738976be6dd739d88023a6f59cb55e35cb7dc392d806802b9abdc77d577347b7a2fbd80280bdf6d9c7ebfed5d79e7e94e791e0e4b840547b596abdfd86ffcfa0cf60fc7837d670bd11538dbdc716cb55b35ad579af265a881a08824c0b75fb966b9ceebbb33bd271e911599d77cb9e7aeddf75ebc599f892802fe6ab6ebba73b679c39d4e795e19b244204455ecfe29e73bdfb7e6399c77caf390785e8e0802757b8d7f4f7fdf18578d539e17c47deac6835b4f40f400baddeaf1e5bd57bde1ec539e4785d6e1cb51ddfd6a77b7fd3e85c8019dbbbb7b4de3d4675c3d7785480a8b2fc5556bad29b5fef794e779ffc327529e943cefe6c280280aae72dd96e95677a7b97bca43299e2785080a6cb1e6f0b69ed3cdfbe50de81b5bcdfb9637de79de750ca85b67fc75de3ce37a35ba805c2fb75f638aa9866db7454fd00ddbbcedacd1aa77de9d1334ef9dafd72ed679d3db8a14b0f9ac55cafd6e77dddfb62660dbdee78eebbe1aae1eff262cde384ba9ef796b5c7b67226fcaeba6b5adb9dbdda611b0697c6bd8761e6f78d34d1fc0674e7fe57ceb99d3cbfd129b725cfe9ad67ce3faef1b014406a09af39e7fe7bca55d772e5282728db5ec73ffdabd9faba224fcd6b68bbfde1ad6196b5a9485bb9af65ced3ce72fbf1cdd83765973fd6a19ff5c73fe7a7076dbb8ebb7deb7a556dba2d0b7af1dfe1dc73bce62baf3d878de74d5dbffbfb75d3524302878cfabeeb5bb9c6e77dbf7093973fbf34dd36cffb6d909b76b4af5cf9dbf9de51b3701ebab37acd55b2dbf14774ce098f2cd5e7b31b5be6b39e57909c096b03be56a9739bc374dd32e5582ee97569df9e5b876f5e549d8f95fab35c7390e09946ed86bad6dbc76fcdf7b846d37ecb54f79f69af57d3382a651ad6dced67295b3565bc1f0b874d3ad75ba6addaed6da144b756a8d65cb37292981dd613347b58de3be6e95725fb3c3f6bc599bf7d62cbd5eeb45a09bbb9fab1aae5efbfb5622e4acbb9f3b8d77f5f64d731dfb721dc7a9a59c767bdbfa10f096ede538ae657cb7ed382150ddbfe6b7d7aeafb7eb980eceb5e5ecc7db6adb77cc040b82ee68a7755cf5beab5adb0b847d3fd6d4774fbb9cb79b7fb0d19ab3b79c76a915d81c9dbe1cf61a6f186f7e6f36e5793e489755603ef0bcefad76bfbb946f18d31e58cf790e77de59aeefee6f3938bb7d9dadc659cb37bb3b16c7ffbb6fd67afe37ced6ee869f1ebbe7e190439aa7d42a572a25e193520fbf26a592b5b5e59b942a93d20d053038369b3fb6b8dbb2d670772f0f3aaba9fed66bfb7715777a5a613be0566faa35dd77f397db68caf370809852b24d4ab5c9cd4af3ec95568d62a9a9c0dee8bca5f95e6cf5a6b9d73fe5790318801f59a4087224908002d341cd38775bed2abf54578d4be1e0dcf89dd2cf4d1017412406c0727035ac75ea39d7aea69d0e180efcb79a7395de7b3bede486077383fbf85fdee1bfd94bf3e7529e772249bd6aed53f7a114cf93026b83765bb6d6679ea9e778c7a53c0fe789e79141a298ba98c06e4029bef96ededd7bbfa5db06b5f31ddeb66d676bbbd55acaf37c3036f0f5b9e3dfeba5b6dfee754a55b01a686f7def36ea350db0a669d7ee6d63fab14667b0755cee34e76d6b9f7b4e636b6c9cc75dd7374fb57a35cf5df921e20753c3ef6e57dbbbbaafa637de4ea5072a3d103f373703c0d2e8b6fd1abe577bfb2ba7bb94e7e194fc37fed474b18747f13c3e32f1bc12f7537a3c8fe6a60459666032f8b7fe6a79d69b4f799ee739b969f9264892ecbefb4d11181a9da71f5b6fb56dd36eb63a06b6ffcaf1d5bae3f7f6ae9e301868bcde2de36ef3bdc39de233b0b79ddfbaf5e6eeeda84de1f85004753ccf8722c8936066687b535dbfde7ac6d6d37c042be377dcc595db9bbede73cebd60e36dff2df36df3b7b5e39f8bb9c0677d379cf3e632ee36bda53c0f87076b81ff96fbdcfffaef4d2fad4c3016e44ee37c71ef96ab5bf32bc05fa3bf52ce76ccf1b55f059b77dd754ab5af755ab69e8cbd3b6bb9bcb9dde59e6d3786ed7adf36e57ad71aa7bd4d01bf5eeb75efbeb5fb35e550e0b96f37ce751de6bf5ebb9da073fe79dd9ad33cfd5b9b605f8babb673d52aed1a530d2b01d6eed629f69ca75ccb5a2501ee9a770d57de3147f39647c06d9cb675eefe4df1ee3ec4c4f097abbbfb8dcbfdeeffbd08acdf9dd62cd6eeded766eec342a0efe678632e73bbfd5c3508eae6b5faadaffe033467ee725df59afdf75a0f74eab1cfbbebadd3cbd5ed631da09cb7fa7676736a3bd77f185be61d972fed7f6b6eb31d10c601aa6fde5acf9aab45601bf05dde99abdd72b6b3b9761aa0bb6edad596e76e6d573d96018d7676ab396fbd718cef0606de368f7db69dd2cc558c352bd81759c39ffedfd96dbb7cab3b857971efd6aebd7dfb5bdef6df5ddc5e35b7dd46bb8d67ac61cd080c03fc6b1f6f3ffffbb7ae518b71c13daf9cb35ddf3ab697ff0b605b6016b8daed6cae9f7bcd73dca529ef46258e4b44ac02bc7a2d771aa7654bb5bd3b4dc3b4d874dbb5c3d5d72e775cfb1da3c0a798db388d3bbeedcf7f37816c2ba7b7cdd4f39bb77dc447faf1e479383937a30fc31d1d2c8b6fbfd63c7358d368b75d4df9f1088645b6bd6e956ff7f78e56cda63c8ff4e0d63f427af4bc1fb02bf4b63fd7fd4ddb55e31673563009fceaa986f7e77cf794f69e3a919acca98259b171bd37cf986bbcfb9bde11b09c6f98d3eed568eeaacafb60557054777be3f0eeb45f37cda520d0b3e65957efb9e56cee78caf3a2600fb8b6b3ddeffcb5d7eaaa75258b878aada3f4ef7c358cb9dfb74e79de0f3605efb65bbbada3bc7297d31ea8c4e4e6fb2848923029214972f3047340ad75539ef1a631a7719d4f799e141cd57b57eede4d3b7bff8f8256ca599cb7be73bf5ae7a1a85c7badfaafe58dc33dd70670ac3be5f8ede8c633ffb4620cd0b6c63fe7dbfd596bddf205f0dc6ddf94eb4d6f7d7509face03f604b5997376c35aae1cc5177322f7ecbfd739ebaf3de7bf0278ad97d7bcfb46eba6dd8625c072d477cfbbcaddbc39ce86dfa9354ab911cb70a735fe78373e604d684ff3ee5daf9ae7b6bb0d89483caffb2171634ce05d31e69bfa0dffad6fee9eeb954fcb3711398121c0fe8e53ada33ffbee390e8e4f71ade3bff107d45c74e279a51b2548fc37fe785ef8a5386603ec00dd6a8eabfb725ac555d39dcaac966f922235931292243c9eb7fbe9f2025b8252ee763cf7ed77ca6fd506f81cafbf737d3bd62ab55785b30427e7a664f1844b52981238fb8eb3ee32e7aea51b4e79de4a1cfbc9ab58383f37523caffcc22f649d3c4fe55558129ab61b87e9edbbc3b2786edbce739ef76e57ecefcf0e37f4dae3776b33ddb42e576ab98dd343d7ccf9d6afa6dc674de328703963ae7faaadb6d5fa9b47a6b467fee9c58102e53ef6d676cd73de6b1c4f791ece13fc763f57b76e779cf51c4e94cb099bbf57e78a29f7b7fdf94de15c4db0386b8d76dbd66eef7e47c38b095dd5b56a165f7fefbfd92e81db7f3b7cbbafb6e3f0d68db894f0abdff2e796f6ab59af9390a9a7b8f7de65ed7a6d39122eb771b7decd6e8eea4bf31138fd9ee3a8a659df29cdd808de733affcd657af9a698065d7854dd31ed36eaebdeb8ebfd0ebc31ad5a57f9e7f8c6a91dbf6a1a774af7ef9ca55d2e82ed30ce7bf35873adf31d035d44e03a0d6b1ac5b4ebb5e28efbae3a2cddba4b379cbbbd31d7f121741bb59cdb7f6ba59c56b710ba5e7de5e8c69dffbd6e747cbd6fd7a9c637bd659c73396a28a92b085def5bbb4eebdef34eff0f84ae6b8dffdb37deb6bffff603aaabe62cee70b79bf67d9fe3775cc76d9affbaeb1bafd45f3eb0f8eacdb9cdaffdbdf2ffaf1e50dc7dd5386f9ab76ed3bc1c5af55affba5fce5a4e638e5f71ecee7fbe37ef3cf75977160edd759db51967bea9af1db572f1c0e6bb2ddf98dbecc6354e717a6e804427d712d70e28b7dddddca79bc3dacd3ce579254b64c1f5c6e6bf724bb3d7349ef5fd29cf0bbfd30a2e1dd0ba2fc7b7ae568ed334de73c0d94db7bdb94abb7bfdff38f05af7f862df617b39cdbb1bddd6a9aeb8d33adb8edbdd46b75bb7fdafb5edf96a7f6f40bbcd5a6ee3bacf6d8deb6d836e39dd618d6aef77de572bc0c506a5f5f66df5b6d6dedc710d8d52a242d0802bd577f2e08a559e0470d500dbbdb9fd9addf6b7dbe634c0fbf34b6ba7dd7cfdbe3d04f2e29a41c75b6f4eb56e35e67cb6a51bcf0be35a83f22e634d71df72f5fbea94e7e1e03cb9f13c5e73355b4b54890757ac1f9a9bab02971a1dd538f61f63aaed9ded8fe24a63b3b6dbdcb6ddce9af31b7b0ab864c0fd8ed3cb39ecadd6f776539e6784f73c1c239480fa2b572aee79374e2e342ab7e1bbefa75ffb9caf3be579dec82243259ef7e48a01a76d9cfef5ebcad57baf96f23c065c30e0bf57ce6b9cd732b6d93bb9ced09de53edd78ebca378dea94e7b5401c0f6eadc77248490904b54ee9c6f32e272e3330d670c7bdd61cddb0fd39e579655c65749cf377dbfc72babb9da6a53c0f47a75496469ed24d4e8b7bdecd65c6f5829fefbfd66afc374a3ddf29cff3bc29970bb6b675efb2b63c739edadf826d538edbb673dbe27a716e0470b1c06aeb2be7bde77ab7fd9e7d4a3f375705ae1558aa359f3b0de7edd7dedd6b894b059ebb1c77f3d5fa7675e59c8ccd595db5b758db9dadf88eb1354cb98ab9f59ab6d1bd53402dcd9fbbd8e79febfdf94241a6fbfe9a6f673bd7f73e41fff877f66bd67adf3777fb6502fa2fc76dddafdbface7bed4a4fd6c30f11374dae1274da79ae6b4d5bda3b4a3b0938abd18fbb6e6b7e3b9bf108e8e73ffffe351583e37b63afbb8db3fd733819a9b32a37415c22b0dbc605a097a854f1a8b6a19ac41432860040b023092000a311002038241c0c060432b1744a8f1b001400025daa6aae4c174aa46194a228061943083106000008208600620c4355666cb89d37f738b6ea1d3775e3de9acaf18e9dbc616bf623a24513d70f142b740dcec28d9d6187c8b3d5c996985062981a0642278a6e6e23d27fef71632a1b959d2a80ae1abad2ad54d8dbc10778bfa67028ccbb86809ff11f9797a154eb2d18af94c0f7499a2a4474ef48a8d406ef0ce3c6b4b985e0441f40dd2a259078d08211c6b6031dabeada20110e5393eb53497995b28157f24828d7d5dd94d2374607b8fbd4cef455a818bb8b224817a0191fc645c4e45318c98e863ef83367ae8f7f7985e6e18e8de832d8c7f7b836b419c26047408887a0d62ec49fb9214c9e2c0cb72caa78007af0af89a09cc6c5aff4a64d556bfb9f6b86139c20b26c16cd573873507a74d1bf906192d9763d42b133e4bca128f7a495b64534f1e73b51b87d7a4d046d9e88e90776060024eaf21d628ab33402fa1019ffbd14367466732d79ad7f9795755a76c98ea3584a51b03382513289c6db31384a2ef37d95cfd58eb0ac918ae82d2b7145b79e9b805ae1a4974ca3df3e2ec113e822e8c4c01241b78da0db41d07a128273ed4636b4b2ed95bd634ab4dd0dd588cccd23d0b450972a74451ffa2f4842b91be8b906e5ae27c205329d19b50709b98a22079b11a81594c8063d57cc50ae7d172221162b6f2c0cfdaf88a1dc6d3668bb8ca34409377487459468a27f72995ee13ec6aea9f086bb0f0763a38c7f86fc7e2e9d33e62235ed0bad41bf71896ffbd2f9142d7115a63f7fd3e47215a1d63b14ec6c53fda168a6ce73e47f1b52a83ed765a9a0583df4e18ecb14b910f256a7f3ff8ddeffe326265936641cbd4ad375973e8d8691df3f3c46a5985c22a183dc545dfa52e19e34a3a45ffdd7aa6a0a05f1b27cfb62fdd57e418131f5580f939b5f440f1334c1379d3c92fe294c811c8ef3f2f5fd1a0f16d869c67ed5c15173230aa4ad6b5dc3113c9e1827f84185f0913e5d092ea02bcfa29d35323544b31272534fca08b7b9f14067aa110f11595fc1cde0e8032beeb05cd1bf3c3e27bcbbcd615c2caf0093ac570366a29039455c0db34420ec444613c6fb0ef4e088656bffccdbea9728a506d7c583b07861c9deeff0362ff5adede0f846328ea98140977364210fb6dc45c86d6fd8e375397097b8faf8af8669e54d2995252bd7385a0fc4ae6715d24a3a3d74c80e26353169829d7057f4ed68abe44dba6e37331a6c04d4097a9ace414113e57255d5888a6fe4b19ff579e212337a7b03864a238ff92202a2a1c2552b6aaf95afd6118bc624a14774b5a384d459438908404c160ff37ba2f58d9bc52cb187af1fdfbaf125357b81ae032a59208fd0d344221ce8d88026772485ec300f19233418d0945992a1cb1b0ef9288cee255a07860c33f2e0649965c071d7f2d19044b5a2c4b147a2be488358713bff86f547c08a4d239b619866009fdcbacce3cc31665df5f6e4d1c1bff94f83c666f173b7cb80325b7cc8584c7e1cc7d52ee7ebc2b5ea72da5f77b99d9fa369fda076fb249810c264c313c969c69cc94355d899a6698a2d38a36b8aa8c064c041bdf78d0607de228eedb1812e66df22ab54353d959a553fe3e2d658e2567dbf4d9ed1d0f8ad9d22040c78f1718c8dae127889ec20868582c1d667a31a0b456b16d119254b66adce6e26044a1143e58a9a09a79eadba33e5e87ef466c2863f8c18206c54a2d50b624417ac4485aa254ad98743a59c7927ca3f580a4c6918d0deafd1505aa69b1d0c6b3b387fe34e327d83d349f4a6f100bf4b36f1543faa5accb8826e2eac63562f79f8dde97539462e9ac1ac66bfc55726441a580c231ec44bf28705482cc0e819a71568b7f81df2a6bf9f22632ae9d78fd8f382830cb5e24807dd752ff8ecd05c2cc544d2037b5c3856178025063e86c718eb4cfcc5610016195dc0ef2ae3c2e1d2f2bfe2b266ac0a136f30c315698c7d3da0ac2065256d5b69aa05334306109c548879ecfff8ba6113edb7d4c46d96c75b723cebd6f1f2e40574d8f041a928e7dddb7617b4f445ac24078d6011a3f22b6c19f86e206b04e0985c4ec6f41058f17ed725bd9e84d934e55740bf3ceba5e196340910e8521a25df1fe2432391388b5f01782dca4e545cb930527b2c831bfb6dab1fd5ce18e920bb3572df4864e2164d4d65a3c70a7187b8569ed27a34106afa4686e9674ad0e514642811bb3d7668dce1b299590cefc6740a9c4ccc2291f52c5ac45ccbfb5c367a57a968b1e6d4fbc4254d4499db7bf852712dabd3f3e3c9194c070041fdb035cdb094273d7f48df63a30dacf7d6879d9870551664b4eafb4a76d3b9a4ed6a0125a2d56413882142b16ebbcb7275bdf22d27d2cbfdcbf38e825d5864bc17a8df1e0d7ad2604e0435516107a4542140edba54ef813b3d66d1224b0c6e7536507ea4365c05ccb678966b2d054b95da6259dd7cdeb44c80c7d686d1f0250366093d96736e2eb1f9e9643fa13d10402f3bad554e0800403999b8a50d5a34235669ef77bcd7c592a0bada1c4fc83867927c88cb2a9f8fc0a4e0f6443ae7ba320844be789764a1edaa92c38734f169655e47c8c3badd909b385cf4144af20a3e2177024b1558a5a4b83811cbdc569a953d0f9a844946af28cbaa1dffb023ca8d2eec5ee7f0674ed3f111bc155d82bb4973fbb34237cfe1f76a95943e259359aab4b74a0e5cd75385bd7d613f95fbbe12084c150cbaf75de1ddd89207553fb5211062fd73b4240d8ffc9831902c0bdcf87f550d0cd479eca3a0250a508f60e21df38ac88fd1ba7a4e87dfa06e94b91f544694b072829a9b61d59b4443472011a30bd141466332a0621697317a5524da29a03da3bedea2ae72a9c09169eaef0d875b128438f162a8d0b2fef3eec889f0a871c7b2cb1f2246717ed885a8cd480268f763098c8873441af72db0007c8474913dfa265c91142fbe463bfd5da5953b79111cc0a04ae0ef28644d0c87bdfdcbdc9d911fcebb8c29a1bd77428a545af52bb277e8b0643e864ea2b7540d562689c0c6a14b0b803fc739aa41f08aaead517ababb053c0441757016366e1d3066c786de60cd8bf79ce749c377341c81159d42f79b237953d18404e4ce0f2ba7e8420495fe4a523a71802ed5ca8555097becc2ca25003a620b140aa8bd90a3f2c50f9e3a79427f2919f6aacb0603f5d5748016c8a24bc5853f384c6568b6493dd61fd1220255994bfd2f5e80b6e5d06c7180b644123f344beac498688a435b191c5a6596352d44f548c071687ea1601eebf7f103742048536c719e203a8c72c3a555a3fbbd2603bd4564ea9b691f1676e97124ebe43b228ae2757bab45f88125b990a6b8283e0f12b83f04dc152b191239de961290f22be1bd8dfa4a68c0c6f74ad4b7210125b2b7615f890ad8c85e09fb361a5042f736ee2b51012be6e6c6eefee094e076f2fe48f99c2470c47b6eea1fd00037df1fa8cf8904ce64cf81fd03a9c04ed99fb1cf410307a2fb9dee1f48033bc9fe4c7c0e1c38537b2ee29f70808bee0f289f9b83030f4f894c3964205b4640890794cf0d189c7e1cd00d64891f729a7e1535e806f4c84b3e6c3c2fe25505db14b8e12818cdcea3d8fe16ac278bd9427129a08aa30a732e6082b0cad4e1aea05cfcaae67eee1ba9d2fd5339d43a3c624c472a0d3e45648501eaf1cdb085357a798166e6434f112fab672fd54db9144b3d6f371b208bffb19d7f23e96d75da9b4e79bc6f06475b15a4bf9740aa4d2c516e432a03311a3a61fe64f1335e90f48aed9401be80860f0b7273561153b0e431e799337fdfa7fb36994bcfa3476f4af7cd9f576f320f3d1fd215f6be89d5d2baca97f42dda14fa7e8b8d2e6bd40215b0da799be5ad8d6b1fe657b2621c167aa19f3118e103b94f697c4f6621311f84c89e9e1f8efd7a794d94de0195d4e2470d7c26d5d2d06c96c27af23cba4f1f4ced3a7e37951f29ce39b77748438b451b57908895e3eb2e34a371069ab60754728532317db5fe563c7d4d0438caee02fd2fa7c2ada0d3eed189814ee98cb95de272944ee680abd53907138f3d05ba013d8f9586a6c6d5354f0d28ebc00f1fb84dbabfa005540d03bfee369e113365e19f0a4473e7d5583939441cb844e3a429d1f9d35e1ce774bce70b9e6c0bf4446fb6e0bc40e2694114b65f8119d49e47222c109ec1c9f0bcda1a1931e26717df4b612c8ad9e990235607dfa0c1ca5488fbc371c57369d27f55171b627e8a9ddcc8d800dc596085ca67bdf98ae1b1e4d02af619d5dbeea3f4bf99aa6988c13c9afe309c68aabb12917e4053d11376db3e584d3545c5403018804f81313e4cd7e14d842328828f537c79edb87d51f790ed96f388030295e26d7f406610a0514df8a175ec3965d9104b91b0e70395cbff63a9de69986e34484c5c007a54f7913213ffc26015ab8893819f7805d95697912ffa4c060cd19d5e77c9fb16ca357e29fbbb03835a9a6f4da078cf30cd76d858d27b13fc0102cfbd5d958b5c81c35e27d61eaa42e815d2924c88cfa5e43844ba0d07cc47991b459dd6347dfcac20f18108f263d295a04225db3346643069fcc0478c2d523142c7a5baa0cb06afd79b8511004150db9d4e0548bd2ca39f016007162e494dd25064712fbe7c28b47880580b0055a0071206789d09a78f02647bd8e606c3e509a9d833e599116608952400f1c0e382e9e51a176137b96cdba00d8e26152395878d0a4e5abf71cbe357636226d46e68ccab1d4bf8e1325fc3574a0232c1a6c48315f54c375b51808a507071e455b3266556c5402cca1a09c6cae9180c49915d013193a9dbf4e811cd765a3a737a84ac53a740f30faeff85809e4bcdb14b62fc472dd6183e9a1ff96a170a0ac002cebbb08c7909602478bd0c3ac2d24656c7ff7e4007b2346a8adda7c04b3663ec6c3fc60eb9b642a3df0db5898b01c09ff6acc7ad71272240922590dda15960d644a1581d8d04eff34bb0a4ff3c1195b9717083fe79450b015fc9fea74d20638462b1989a0f0a481589d37ad37728a0d62545a5cddca08c94acf4d5f6f20639d27252685ccb0bb478196dc923d57f72a6159d983e728fd21096cfd6541dd972b2604ee4c7ff50344f21dc0e6ede909fc5c17d667e7f083420be310ef3b004a458327b918660f8a1e04061df8c085cf652dfb49d27eefb96024d8e8a1ae2a6e9a81ff6e1175f1f62474c3f92c176134c0a7bc0e374156d69cf4727c73d0998768f2a4e9f8ba00f5ff3d34bed592b1c4549e8a8e5964de52062dd0a01e625e56524ef0d32547e6870e09b4ac22759c94c9fa0d5c6abb61e2cc7edf1946e2edbf708eaec2f1ca8df553f09db8d5e6db956a660dcdd0190e8649a4fc6b0a6eff8adc1dc3b729b4206df56f926de846b393d93a6f76a6d96d223ec80ad3043fe465b14c51f4012b9d91b892bde33dfb7dbbc36ccd1daaecee687ac7c78f775161768a87f56d8f711705c860e90b42bdabf925f80d21b08256dbfc19fd319167339c87aa0d10b5a5d64490ec4d7f79e1bcd36ca0af4fc4bde0654c6df39aebbca0b56d055b6ee614abb249858ebbc53566362d96b6e832da4f082ec5610364e550b1d48db5d6c8e457e818dbbf658d92de4f37c89bfe3b4e3e456a3cdf57f8de759d703da6a9a210b02163e9763908363f30327c29e6396521979f871d8c39f82d27b3024fd41825e053880f15f375e402dc549be79358befdd6ea49f1a8431f79da3558073d2c77e27f6d0d5bdf4e278d863171dfcbd9539787279f62e0f774ebe084062c3914fa21424a705602e6623daf57222763aa593c74c8ba604abb547c73a2e80e918dc44364a6fb6a140f67f9eefc1455f6a8d6fe6816bbdc61bca65ddb678785d821d24ad9a78f6b1f08d92715d2c464f4f44accb5f57c5b8fb4d668758e7a67d6fd5f7bc21b90ba22407dce4ca9500baf19749605702410a0d4d885bcdad87581afa9688dccf80a8e24f89e8082848902f620f73c1dfef2262000b1dc4b8ae819bbaa85108ea84980e42612272841f0d54fd0352c7caa1f38d12a7fe952d71ee6482e0aab2643f46d284a048924b830245d97062bb9b3d3c346ee560363d23c7e1eaea8e74184cd2f19d6c23249ae88d93894003d347eee077e99e5ed4c1b5eeb89c9b9433ae4b712271c6d63f3023986ffc8e8e60e25cec683b74fc43452022cf4d347b6e2e6f2c0c04654580cbe578f0a7e6cbfdf0ae9bef236b449f2c49be569de17fae2d11220cd2cb2e8ae9c5d2e1f714199ba1194606f1e63f3cbe15f14b9ff3537bb4d54693fab8b611efbcdf2b523bb9c67e038596383f6e9313ef5a6a92a330c86c3db3f6979b7447c2e77f254dffbcf0b0e36c1537ef21d61d40886deb4e1ed250a41e0723e5a46a9c9089a7c6c63bb9d475d641126aa073e42077021b36d46074ebb180b4139b46602fb1bad13fa4ebde43eb108af684f8aa83ad5ce5bb34449e8e707a2533400e55d33bf407e1ac61e5a15ac16e6fb223be8aec27b48e6c561d57dddb3fb644286f7f6907b241377905734d8985f59bbcc50afcc9c3ff6d079c5af26e403e95aad7e5c3528ad0d0f002d7d3287f3624de515eb77b5eb43966678326fe289ea4850d0d3f15c825dc7c4381b3d2860c2b1c3a783fb5df30e987f9f8a8032a59fe4acdaaedb4e6167b7d14e15f3d0c74311094cc103f7fa7bca76563249c3527cc7d7505430016e5b968af1e74777c30d4a6cfdd4131a410d4edcacd6331c87fcad291c5919971992341e4df9a5d3bad2ae3e429f7387436f3085b04762a447416a79dd1a277625609cf33fa2a80393d23dd5d757becf3cedb53e73f5a73056746004f37be512ec924e3658742bf135f1e686ad82963768c721d5d379d6b0cfe410f71fb83363b79549a79d362fb6702321832446aa3c879435776e01aa9ec0ec7113a63d19e26a065c7b6da3fd62906476c6dc7a63719b5ccd2dcbe6d08c849646715d0fc1a296c2ef28adf299f4d76eb94804577d44365521ceee39fa12e3ff1bafc1feabf14739d291c4114b9742951fdfa3a1f8ba2ce1d0ca4efb730158e956a24b8e1f822b2fd4bae701f43f1917183151e2d68895767de8c631c53155873e8e4ac391e1a0de2d1ad40578617e6f42c7afaf0e2f842a00da801543e2cd0662d71fc73f51d79fec4b9dde7fbbc43918daca5babd2c2df50f99ad16e36425cf1dd2302b7bbbb2a86bb1c78c30e70d080d183e47c72b272b5b32f1358413d1b95e568947bdda521656dcc43f1ab5570dd23e132d6c34ca6f4fcd62773fad268dd6ef8f007e00da5faf71896493301d5aa8b12fc33ea15d7f1185dcafb31c51ca3ca6386f0623ecbb9828f7a90d1c05bf522a3e31c5c318329d671fa3642317bea6ab8644c69c595fa29adf014c1eef18658d19aaf26a3f05b30aa760374c8ed13bcb2192639884e8eb63c14735664b1a0f614a0c7d0e61acb866200130f792cf2e1576e823a84cd2945590d2004cc2e720f327dbb8f1a7ab0efad3d342a7415fd38023aa12513ce0e2e385acd5035e4176e71b7e726436d76faab28b606a8dce1d4773833fbd641eac0d8377a7ed4ea475d8df7732ae68a8d450b13c9d84cec8b48377bf1b1d99a63d48d727978c6f604602b12a98e99289957ec5542b625723596be2c2775976a6b95dc84fa803933eefbb2d1252956d0675fbbcc1720ae2300445c4b248a4bab6fd696a91f32063a11bc05f1c5f801328b6e2d9047808150c87f3f20acd394a17a1bdab0c903fc65b9f6e1df63aaf63076b71e9b0f8687e2452462de76f1dd62a26945c22293291f0e04e3eddc6f022b49b53c6ec82ba31328d728b1a748a8f0b51ddac8bb2689091fdff4fdf5009ee304b84f9a6b6f1e221ff7e396d757a8c863a06ff2b3b31aec6deedc51aa3108d1bcdfce28fa7729a7178f115bf9b24f2e833e61f3ace77dcec376f03e60b611b0c20ca728723d07a6c7ec41f06a7dd7ee5922d5a13a9dec53687e8fbeafaed897d39a707d50de1d60dd69a55708c91a3b1b5fee1e9638e5a748a39c7743547a689c419a5ffec9cbda1371d77d69734b142a1f820f9cd4ed73f77ceff8bc4f8741399dc734adda5b4cc21d4993330233b651b39882af625033b67fda65374af77466fca3438f26d60d2a5a89e986570dbd55f2d1cc6339f4091b0e9e304ef4c1c4ce9024ae2fc9e7cca6b9126887409c21a5f11616f524881acb5d0e882f3e4338c7f3e873573bb8bab3a53556a704b1d994caaaafd5f1020904ba9b9fe1b4a3cea9137d57849084b2d8adef6971160a94c5ed3023572bf7cc9e6b15d41815c5456fedd454612c7ea125a0a283ad22a6c724f1d49466a95bdde1020ca4fada1d62b7a1c151134d57a4d0ea7329afef6970860554c54d7e22a3f3a09896a5b7fc040212d577be54f98a828c76fabe2971222a5182d9bdfa340a59bbe42831b947424b844d6122f6e68b7e1a90dd76c08d850bf86ca1a7ed5905103f0515d653eed132701435545eabc471b8f8a881ba9f12a1918aaa3d5abfda544e0a89e749d16577341216932355a7e0a0bbdb4b9b4a9fc691c1414c7d2a2da7f2820562a56eb4d6fa1815eef341534be850252795254ddd72be4486549a5f14ed39730441e80c87a46e220daf896a1f93260403c36d042f94866c5d4572ddcbc3242e5552fa79fbee7c2e883905dda5d85b3ffccfe26421269900a2cac94160b254e5134495fdd77a88bbd0dfbed634856ba036bb8c1f8b4a6bc76bdf566b02f0239e2970b9c8a02fab2ca31caff9cab6217687f1911933883714ac9bbd113a40bbe4bda505bbfcdd785302af3ee1fa08b20048eb9130bd67bab8e377c53a1e4b1cc3bfa1a1ba0d60cb7599b80b25cd7272ea9cba90e71bcd86197c9366493eec83e64598cd5352605b10651d0f48080c1ca7571374586659abd5c632377e2d430562df83596c72105c51e9d515c4eae3bd32dc26162fa364456a96dd09b442c6dbef9b3c01c005b905c15ef39e01d7d876ecec94b1b1946381009d068ba4e4a4972b590156e0248091f0d30d7b2060ef0d4e005a072bef5bf7285c48170bcf0d11ae2c1eed6608451c3d237efb1f402bf925b0cfb5634e2f95da94b74c95980c848f99e584d211674ee411a02f68e9fd2283e21154f792e466119bafd58438c60d1005be889adf1719c63180604c80ac17b2ac059fb9c61bd3434a8f4e1e661c908632bc5673a83fab3d73a23083b8934af8c78794aea081d712852220a42b7984797c7332af5fcdffd34707db78b928fc4dd0bd24e2cf7412240f654551a194c816b3c2883ea0ae153a0283c49490660baf1c055799de7ca159a22097c088805d9c2dc3d8224ec6f05be24a58bbf7ab5609f39d5ae4d2ba591fb885829be5e5029bcd40157e3c75c275bc8a022e5b1f372399fc39653f9a6a4c47768ae86e7659bcece9134238c31979cb8bfe7be4a1fe86d14423fd814d26139e48dbfaa8ac5110b85d74da014b3c2bbe974ec1d925c0cf337e7e583eed150423c2dde9b141cf3320b0468ff4c515d451a7970b5886dd9247c7efc3f5c1166ffedd7d49e7af74bcf12a85277d5e8bcbd3e8f520ca218a1d66524178f7b2430a9df80597866d1c3831d34bb9c10ce849b0d84910ab3c44f5d09503ddfa3080d23dbfe16e006e8f245db97cb40dfbaabf56cf1444e9ca3a96b0b94ad6a76c7986d92dd07ae4983d06b6893e1656114d7e003279628429f610a5c18cccfd91f264de837114027dad7a9e4fe99125ad0bd3da9a992e13cc6ca235994952bb47864a221b8de6db44e2f1f037ada9ec62c9632f0df40961584af05adb685bd3d3e57d81b639739a7036ada4f7903ed014f3784184901fdddb8f193fe6a4aa289ed7128b5792b428c6a8bc80e0eae7e8512bc77a90701f48101fd389e3fc2e8307485bd0f00b403eb2fcdd17b92da473c44b539a597582f5ed26024ba674ee6d38c946657b13fc44c1f513803f7241962e7d1d3a85fcfacd5474e2f29f94b62da78f947655379ef28be266b2e6bbfff2302f43286baf219e691ba6b4bb939545f6b2217d832fd38e99f3242fff694b1e0dedc476acddfc8acf6f22871bdfa6fe532d3b49d9b8a88291502a211a32b8dc4b969f09cf53d9d7283f21a5523df59f8ada98df9a67b40e3e7e13c8dd9ceb42a56f2bcd298ca148257eec774edf0fade827d7ae02eefe26873d1e6dce62a5209f73c441799a5ecf57ba724a5d196224b3055fc444c4a66d0ea7c1d3aa1875197294781c928951db0e233d2111b36af585663ae60bc12514e779491e72deb2f7d1bc0a237eceffd839fd691fdc7e1f57cd7b51b4b03678424c334346acfd370a0e6e2caf8dd3fe791e4e19b41f64b297eb2e96f790c7721be4962954536684c01cbc14fe4106e95d0a9908ee9ee3e6c513ea3398c1d2a4e485208c3fb4e401fdc5246f3e9a7ca6e98ad2e101c7b9162c276d0d0cab14e31f32e0eb03fc1ca9d8514fda127e6529d3cff89b80ea67e1928b32239ecc733557172b3d361a2b86c9be03e8451d0fabb65e9f7599cf0ed1d81be65c4ce5a66b3f98083aee944515811dad4403a2e90f4daba871db9470d33d4960219e247ef690ec2d5786cc71e9b13c73644f6d909b82677519c933779a0c9c1fb1920ae9ba630c1f679caa120022546d5aaeec45695b35f6b99342b8bfff7929033ca6dcbcb2dbaabe447d4f84d8ba8d1de496d1e2717ff736c8f271028d9ad2d49630849c350cfd87e6b41dfddd668ed8d3b046de2c8d27c8b520b7cf1c424711f5d8bc4664f01888100443ea9e33aea6c60387d8e652f5ed912ddfdb75d5abcb0235e25c8711aad855a6446aea66edd90b398b869150db0b5ec162f4480d6af25fbd5cd2adf981c64aa5e9f59fbfbd51ec679e3150a8d3bf7b89de247313b71834b95b6cc1357b64de867e22112dc04e5eaa5986dd2787ac9a7705a4198a6b8eebbb877d173f7323faba5b3f22278fe0a1d3f83ab0d611b8932fe8223fdc416c42c7c82644a9595281ee13aaca339e61f2276bcda02994614317a0a1ca6a3214c90381f85144729d6734a81f77b34e1885f2aceb3fbeb718c3b435243876571571b2ddb3f31562c6ee7afa6e8700649eaaa9738d7de2fa25484deacf6c38d7af0e3786323c2a3e8a4a2c1be129de32872dfccf3b0e1384c53755f6cc21df08ed0653055b7b809699021a03f842124474836b3f546ab4bf109d733c3775bc04c000331abef32cfe742340478bac35ee678c7ca42162ec646b943ec0b7577fb96bd74a49f60f1d1f40ff6723e0dc3030f972d81f718ef2ac59de14cb13a41a1d2d8709a1363707fac7de34a83bc15f7f465b1b596c2cd576297c2ff1ffaade83ea73f09083f4b9dd13112b0fbfcf036a280b49ad973ea8b3ea932c43394f2a0b3deca4241283904d0b26ea10781d7865d4044405b2888a4821319a2106e5b0e7de741d41196fa09ef36be36717e0c3f0f00ad8af097f1b5924472f1b69d365986db4263afaad60a1ae004981610ae6d9885c9d8113e211b0600d589297f52132f37c592a3aa3107ec33cfd72f7f0c2a80f22c1fb1cbfb783a9417fa99641e8d07961a709679fb4fe2bc482a00fe7002cc2d6a195b14545c8a7a6b7224d54b5eb063ccd543683687e0db18fd96d44236973ab3469ad60d136a2902e9d90d1202b1c5f67cd7ffe62067ea03f8ce282665066ceb410b11707dc8c06a2265d2b62b43e6f6ac697fee9a046c7fd2aca71bb73b2654b350521788ebd082b401fb1c44db144a084a7710bd001dd0a26ee34b18a6a6ec51b13b1360c08f696155e0cfc9534b3261e71febfdb86d59eddbc2db415d041fba15516e953a5c9ae7fc40e546c496eb277d6c92b5241dc370aa64cc5214ea258424839b5bf3a33b659045bb411cef180d68c1ec319d5f6224e6e869fbaad073a8e9af6debf87b57dddf929c381f856dead51b0eef61e307cc05a9c4f9f78e7e299794b78e3bbf6585784fc70ffcf6924f90196b3361e560b76799ef11bcd504b4a761467c057c6f554fc47edea045ecb06daa872ac984d626153bd61cd25df605e5423a8874493a263af03c99347261491ffeeac6ed97d97eb84cf2c24022c029aed11c93fdbc97e7d441a65659b4a5fa8d875ac5d258cf8117b76d5f0ec5bc09abe750e809cd059c0b3d2f32a6ca4184872e0fcf68d8df4628815c2ad1f245d4ba70e77fc3f8398457ecdca9d1f600d79bdf5f7e495f1f0c5051fa9dba5791c271533fd3cfd7b119bbfbda263d5182fa0d5bf7fa2d3b35abac21352d60d03343227fc12fb46e1a38a9a137906d3329829d09d13b5bdb35b915053bb33d81bd6cdeeda811dbe6d8b4bc3dcaf69b5c6e862cb335a756aeb63181e55fd8c6be4aeb2decbe2ae1f4972af7014d6ea4178efc2e438ffabf0fa18f5e28290b103b954e0419f042181a97d79e8b76912b583393b67a90d67b5f50a3e9fd4d7867154bebdd8a2e7bed7e232b4bfc146909d4ef3a2b1e70db94870061af92511761afe482697e38bc74c7cdf6088d72e6f3b7c95e435e015c93bc6a9154939787887dfbc7115057bef9e10d96d36d5e20cd3fff55621b97f8cddd9054a3c2b67f99c48fd320736aa107a4978119fe2f8690e9f939da36b47229c507e9a7f6ddd471aba7e158415103308f04ab3b18836b0fb8ac8c77358cc4965c2430a7a6f1f8df261db979509f2103b67d201c2122dc593f147d4c035a61b86a49e27169d8e2917d876ed0d7e8c0eb28129863978e9d2e9295686e37198f276ad7f52f1c6ab7cf1896b3a336e4a0f6e33e6a2b698a2336cdebb029de2c2c3a5515ab6fbf685d8f9c07c75fc8095eadafc648772779a048efe5f8111a5d187c76c2ecf77f82678007cb74a931afa9b4f5feec75086cf7147f3fbc59c1e16c5a6282eeb52f9868b08171242b6250a8b9602f45a8633b6675daf77e403de732902692a29edfa278a8b4ad9285c62a1aaa250c2283679e56acda66a1746d5425db1ea21bbf0271d9c8cbe6820888f17faaabaf425f2d844a4c725b21259bbb70af303cef5e8371dd14df4e9e98fa8581763a73e7029bd5b3f0866afe1396a6fb164183bd440c082baf2e7a4651bf518e9bb91d2c9b64875e809d02de8d5c68f1c41cf8a95c1731fa68857d88d7828e0202889cd1120a8d25e4a05a1328236027bad1088bb2bc76dc07ba3b3e5c67d33dc97434c10fc515eec1e127e7a5629650531cd28b5810cdc4f65ce38c84020397960a8eb60ca0458a3f24b223ced9a42fb0453e186d52eb85ff59926e29cf6f589e4ed234590f943d8aa32d5714a6da12dca5d9f45e248ff8f0f5567c2a103ed72e6285984e51649aa194fc185b1b84a9096d640891a8046d01aa1ff649015cc42b81036c8820c37a118408772d4801b654e025ad426a1b110110b2792d695831c3c5f4e4189aa07cc1205847877b1094ed1076aaaf64c190b7082ee4244a22da52eaa42ce4ca6f41b62bee83860c4aa687568cb0c7be3a209dd432f316f5b856ce70173f1ee840fd1c7010a81d08c21b6f71beaf4cb606f943e5f94a363251b603d115985633892a8014a770cd03fec941c14c70beaa97d5f566290bdd836a9b2ad1c35fd3c69d369f5509f801784111df8cdcf29cc0665ec27491ab6abc1f06ba9f14c7ccc13d0637abaea040f5811ba5246f943f9c38420468e5e1923acbc1a1a9123b80b8728b2f7a6bd29714c4031937c58ec74ad3b8582dc6340c1c9b8765a5fdad0a5c831addd2a403a06d736205c6a07c050ccc126cf84bf7844e37edaa2e14a93ca272bb2fa4fcabb365e0f3b3107e282046a26c01a4f6aa551d65132a8101511922e84f9b4b29332c99e5b0226ef48bd938ba132114cdc44cb86892534eb94324e03a5c8f4b0fc1b76176b8323cd8d13818671a28052e2a0d554c9ec23197ae83171a3cdf3571e97a2adf8879c7c1623d14a5fdcdb4dfa978861ab6800127a3725251ad162e9656b292ec6d38e91fa4ab2976a102e2d8d93ed4392915ed5c43cf8d77622258f621cfd4e76d81ff6db7a4b940aa447cfc3eb91053726e2a92847c0f5619c503a40f428235ac2dc2f3a4b707a64dfd0197a8156cf7a759f02cbafca8101208117a3b58e75beef76d547a76ad4791acd89f449177c7d4802652c933bc3b0f8a60c860f1a48a9351d0a7776cfecd41378f663fa897a92f3376ffcc1728894d085f7a36a313d7f522868b3cd48b925f7c4ad7222be7f82b75541f7942104ada65f25f1c07eb84380e332ecade44be5cf64c3ffbc368d4cd95dbda00a89583dc86eb683198918123076387fc819ca49248933f28fff37006f3df2c57df389819403edad153f7617064bd63ff588a9bffd605675ec6b721109584b4bc8cf08242448b07752dcdee033bafb472256209257137913f29263f6454f98109ebf2a0b8033739b71e68bb21b24fc0a51a47fd70c375c826b7e16af3ca2e1b7b8dce602bd50bf713e251e67b32eee6c68962f00a7d1507f83b9329dd40692f59a30944fa0ae3012d8ba1e144ffcb58a24fc7b0b575e46c81d3544f186e01540ef00644e31de978f92cd75539217bd4558e8f6d01c11f59c0cc774e9f17fdefa95eb81e57b3d485ce0c257b4f52536441303687a474791bdcfe8253f747ae3276b33fd744836b273d73d6a47ea3350c1a27c7e04b258760bdbce164ee8849220662877d420c247877503cd60e3311d30c63f46a9f196af8cd586237fe3b7db5e4e8517e22a0c161688f5e76402dd249f459d03ba057aca795bddc8c23b407fd25b4d7c65c2bae86855f0de2fd258bf49ecec45c46637088f50af9c8c54783f574c8f3114173447ce960ef461644c8f138f47881e28c32e16fc6d01a97e5af5ca00d47b95a7a84054512b8c65d302e3f728ff846a2336d91735e6c29a75a8174153c32af18eb5fe149c72233ba66b097416903889fc108b4f2824b2b9e3d2a8ed90c837232fef255098d33c0a6083c2e38dd8540827553a85900f2706bbbfd9cc87c907b42b142c3f9d699d98e08c9b7e4dc9b9be07fedc818dd77831dbc2a4f3674f3e72b2ff983647a0fc845923b84ed1ee6a82a1b4a09b5bc64855fa25f133cd83a57e3d7493c960d72bc2bf84d974e2079fe01118d6006577aa719350354fb04e04b543e8d3f77b5a757351e1aa0ea5809182ac9b2fbb1bbec1ad03c84fd0ddefae823b8b1ad73ca5f6cb0593398b04fd2edd4807aee968e3b8e1293daefd742d34c23fc56c0e350ad9e41088a4678f3f61f96cce31674feaad01b9949d835c3850a67763f0e2dcb61890e26574ea187c0153122593226665a00300f379c1e2480304b2f0ddcd501006b1ae47014a434bbcc22e868dd6df876e06f71ca3c0874fc31572408be74ee1239cd63c0a469ef4a0777fcb3618294cc040975cbaa7ec0d33f7f5a68bea66d2a6dd1ef23289399ff1eca76676d532df64671f6303fe0bdad0a911702377233f811dd31c73936b434c1f9b09e473350f2a7c9fe825a62eb3103a7cd81d77ccef31475b9a0b3bfe6d86802f07cb7bba6e2615995b8931b5ad1b764906843bbcfe720a185fee4e7f2006f0fd1b4253d296a7b74cf3b418f686768456dbd5002fd350c455a7c04ff9145b73e57787120b09d80d893e0ccb06ab66ed8f43cc5c5a13037ee3dfb643411c3cc180d615a5548f9217d7b922e520f3524c89329e29c9b8f8c1e0f95bb6d18b787d88d7d831df7b50fff47dcb516fffa6dd0f18e2bdde9e570d6e84ee06f80a7aeb8873ec8cfaa4e56a0006c7d5c6442503318b186380ee77580ffbffa4dc0b4aea215d0202ba8926b26ffbcbf6e5e62c52bddbd02e50f8aa3839c0cbd18bdb8d3a3919e3cb88bfe35fea395a7e158e48d6ebfcfe968946af4dc9314fc86b8de4645b1647750081b09715ba1dc9d52b53e9a5285ca0d42d0b750d6aa6c45f17c35752ba74c973fb5b30675c6f4825a38fb56217fece623427cf0ae2b1c01540f4f8c3060950f2a68644080352e1e376222d8204000807b7643f386024de96a2e2b75e0d1a60e3ad6c110cde58cdc92f86d34af841c29474babb484b7900180ff19d37e1285f985ff3bdee37a7fb464366e2f93d2f8f3460c6a50568f5ca7716ea0a5880d9be1daaa1ffa814245a5b88a32e419fc6368b3fcf68e3ee6dfe9fbbc75e6f7d7f05e4fec636c0c7ead5f1c31b668c71aed171838f7c97a744f5f86c1d856f177ac63302b9e8b28d10454540a90e446c28dc75cbb3085df648c47e4eb7c94a20265b68ed8d2d34e93081705a2d918367365089e7da3eda20d5b9527b1ef81eb17abde44c1fe0e32e04ed96f2169587d5a770f63e2f9f994925a67155718d575904547db46b77cb10ed6ea6ed0de77063ea5a21931a5b5234b4594c501951d742a31a78fe6836acfa40af3f0390dacdb0dc42dc5976896c37f184d0f957eb7a6e793d827a478c8e2ff44748f97305fe9608cda184b68e7a2d07133ea0bc14c9bb85781df54a0180aa3a7dc44d17aa54a53caa16789858887a621ca9a611ee3a6cc3ba1098025d2ef2156341102789d892465bb3b78d5060ee44abae2d776e9d0c727027307d686060632431a6c4642b70f7666113da345324b1dbe8faf0d943296a9e4e4e0f76306c343669e52c2b382d90a14c5041de259ebf8c16fd6e4b8d764811aa8d931fcc637f50edfb047665bebaa1a1c662e2305f93a22ecb3ade77705bf9bad02ae45e9646c198eafbbe9e1f250900cc3663369286021bd3d58d2bd730b8da2261b0ff9b09ee26bc4e4aa529dfc19c5611a24cdf2c9f1b1bb60e24ec4d0fe85b429087a05c3e9d464831f68af628d40ec355a4d7fb2599eea2fe9b3f521490f8fe33598d333d0535d8dc1fcd50145f67a2644c54a890bdba5e77a9814d2e2dc9272b24872e6880972a9be1be2fe291e44de29bd0ac7d7f00fe6913d9f4f65763ccccd0eda8a3a0940ebf4da996b595f6600612f777927cdd7d6d8ec8a4087447cc32206a6e65d4a250748360d5b85bbc3868ee73bcc4b155d509fb5b0dabd43e19355a6de31f4cc7f2bee26223a949f64ce454ec0297f53c6a9ed276d2f9ef9e31f4d589392c2ea4377020ba002712dab6ee5e91da1ac9791159a44d6b8e72e00216feca7ced7c295eb26ebe94f5e4356031a9e6fc128e71c85eb512afc3f5c2dac203b77981933cf5b2691ed86f1e4888d9e58dc66bb9f3e3f46bf70fb7bb8bea1bce0e7cd44fa4614fbac579051b654b3264294bb8304167d41dee78a841f5a6f76555f9bc2b326902fc1851a30c28668c360218e5fdee8821af8089f99d4759087fec14c8d2c25c325b072cb551bff86f60f82306150c9691ba8d68ee1296ae88cc744190e649aa1346c4a5301d4af00f7b4453f3f2841e8aef0cd71eda0cb437bdaac66354abc5d6f930183f5b17bf1fe85d6124e29a80dd938a16b63df859ac2e4a0b34897f3fc36fac1f57f5d92fa17f0ea7ec656f513af15b6b25defec6bebb784d74a277fe82df6f21f20f85b805a129a0878e344738ec3698d698cc800f3d5561a7e64781fd1d03a1ae9f0ce6723ecc04e4bb3f0494d6dd47bf96c90b0ef872d1f10424d777c6b17e4aeefb08698d5702f04c460c56648dfbfa9b0b320ebfe050e69cd914733e7042ca3418d4b1727fca8610c9b020bdeffc215c21dc9eb42ce16329b81d4e959822c777539eeda88b487aca88611584684942cd05c79eaadf73957d4fce986ff01713e58dfc40771f0f23af666edd6231a6d854f521bfd6645ffe6462c84178091b115f37232f7a6429b86c16c7da4d10087ce4371701e8ee1f9f523787c1e7bbabea20e5ca0177649819a92b783186ce76bab07ca1432538c11ebb0418133da116a22d70260e71a6115833b7906da203c5614d4d92e600d9cc367093ee51f16c32da24e9cff84b6fd4b686539c8d35bf3e66b07abe93fe799ab10a32e9b950bf23e7ae190bb5f4d5148098914dd842817a855627c22a67cc9c55af01f062cab43813d4acd13075a526a669feb3437948409f200feea6c9d1372c07b097254e4c2297438d5a34f6be87808a15bce1260557b66352bf212f3f21e6f95accac866a97348223ee885318b1ad3650b8c2ab9eb503c3bd84a16d75bcaf19c1128024eb43673a724a909172c6317e70c29c4a20a12251d0cfe93041cf2dc1b86ac7f04fc9739c02a068df6d4a969e918e8b1ddcebb139967cb4ecc42c11f3ecd6a9ef93cc7c7118edfe0bde42fc0b20087bc11306aa1ec3e7c756229ceeaa7f0fa4cab68bd022fb60411599a1f5bf3890d221d9726a8cf2ad7da81601d946e3bf2df83964bc3c68caedba96abf75906b7066485d20db074242ab4943c4ec69adcf212597c07d0c2243b0b0eec39ef85f910b5557bdc2e0f1f710e24cd119564b5924723748c95496fccf0278c46ff2bfb0530e00b882b1bb3511bdec14e426d5901031d24b201aa21b4c6db23142eef3455b3225c925c883445b4bbd7b9b5cd59e7a33cf765d3265fd9a324a8f30330ff88c880cff77a994b1ebd205aafad240909a6dcdd5668ac90635d3d4072b5c80323f5ea42a52a76973a365c2891a4a3c59f72e224e30cbbf0cd00fddb25e39a6edf82ab0983d36840f5da00e625a7c6271880c58ce0217274b0dc46495721100fd45823f28f1163923cc1f6bd9df1d74de0a27cb30e4e7af78d7de49dbc43dc9aeb3d71eebc59ca97dd64596821f44d15cbc7971cc1a01da3e9195226c54cf022583c2ac34ab120100f89495a55d1768a7b2ef387f2f3f01143c36e7a562a4d524a1134235cc22700d9481706b8d6f081bf6d1b28b57123ac35e08ee4b778e5b0d3a085d4c0323c2570fcec3873180a33292b95183261fbb6de904c9a34cefac13c00fdf51321c5b9b9a0323a6ee468445331aba11aa55e27b5e412aa41d277ccc465889ff6506f40c602ca979256365f0a8373f68fdc03c196c7783832b618f119b705d8b75ac7cbe5fea86369a6c96c2cfe871ab7a5a8e8dbe881652ece2455863a1221c4dad90232129e0a4b21ee3040f0978377af156914d8973fcb6afccd0450456dd15c4be8496b0602f59a752810c8a819221e3cc0949bc37eae98b361e16ba432edc106bbcb431dfbdf4cfd51ad8ad489b01e104881bc33869c0c0345277e01df58f214fd4eced7ed5ebf3c962bdfbd93f0ad087b01865819649a19173e93cc5069a283ff064c93f2ec0ea0c12da462b4642e61708f93ff489f60eab3fdb3566f61ecfa442b0783238fe56ef2b5662638ec6c215b13d0daf0a873b3878b64031eb78d6c8d7d107611b74b080e625c491e5e79c23d5c2e58031902fde3afbf1e6100323b2ed269fc0b9a45292797f1eaac0ca9672d5caf660068c567a7497cf6463493c0910431a3891e8daa17280fb972fbee82804efb2e3eb6dc63f0ae9926e36cf85665e467845f3788c0d5118128f38116c6d60a2613857229f727ec3b758de36e8800058891221e98e1a437f31daaa95a65fbac71d2f6a68016da3d8746787cb2066ce4f460423d638b8e01ac6a353504042265d0957d7a40de01f48dfa5afa4d9f9014aacd2418e46790057d68b72ce50ebd4752e3ea7632c1b1cc29b1dbe6f0021abf6d8751fa7e89cddc59b92043cc80bc2e6402e2584bd9b5db868cc3b575205a24ce229bce1d5c7b05cf274af05b666f8c2677b7b425207af20a0697249fad3f27547dbb8a05ad350041e91faa19dec092f4389393d432f1373a4c163b2aa8dd6f11a12006e3c50a89c6b5555e978c1ce80be34cd6d8fb8fb5abfbfee4e89e4142906e0a32b9d20e921afc03de8b4813c480656f52ae659917f55621fbd97a077044f900b9ea6224fa4ed46b60e4b48599cfd8635a580a0bb55ce9121bf9434d75f2c4e8c416cec0ea702bb3d084e93682c31971a3a1e2f691bfba4f06f8867ddee3979f315ffc4bc565f75390729d0b029f144db8157e6769782795416d83f4221023799cd30288cc139fb776409ba36dabbac49de4805e9ffb551e463501fb7ce79f8730c89175f1bdbe4fde317cbe454ff151238209d600905d78f3c4a59226f0a6efc018976acce2ecfe8a4699c22c280db2eaa003c1773d917eaddd6f56eac18f5e378105d43b6a38c69862e61ced0ca975db46e439fe69b03b7f76057ea6983d4bd240c4d5b13c4f6cfb06c8ac8cf5bf78670f3033b4b60182e303d92eaf4d5eddd1a8c3de0bb7139cdde96c4f32707a3affaa199d4e3681c6a66792ff229c32eecac31a5cd927823c2c7d13b1c27ac2c0c13bbeab156b21f1d6b4c37c959e3e53f0a8dc5febd39e7ecd15a0809321b04e22b6a4435246e48c6f5ca5bdc308c32495631db893ab3131a01c2df0f1a4f048b2baebc13c05e03239bf86ef3b1b467d190359b099439d17d7b928d86967cfbdee1122030c1e93b2127f8bde7c1bb1b8751961be858258927cefe8142344bf96beaaf89529c49bd20d40faf4bc144006f382f583554412f3a980d4f991b5661f979ac0703cf35f300421f8ac82bed1791ff60c69b35afcb9619da7bf198efe66b6dc21b6c9e3fbb4e2af644f389cdb19e9c0b51da8795f77238d88796c4194216de2128d63396ea7ecd1976a7998f9acfc8241aa69bb30fc7dd477716d623a0c5c9db0bfc870b47dbeee7d8695addcef0050769eb384243d2a731f61031e4f4baef747916f02beb96ee2dd01d8bc65de470225170206575dcab617f231b3fbad037ae1cdec44ad4a2bfbb56015042964661a292de762e2f29525c3d82246dfe446c1f29e5a4f5d05c80f119b5bff6b4b8491657edaac76021fa1415fd9f03681970e4a65371b87b5f28204b69a50eb779393c8a2e86fb44c8b3332a227fc4f396898d1f63ceb51f2f2542d3805b8ed173fd44e128fed360d390624d662b3713188d5a5957b7725f35b3ed805775dce51be748b1d79c9aff4da45cdea2a732cf7e5c16227333de624b1b99192f66e1781488bfe6fc0f047384c897cb1eebde30e6bac3bdd4462a628b722c1b8d63694ba1124bba43a86464c9e6f9d599c214426827ab3a0a5f9e0545a02f75166e7b17b5fa6f34f09e5ccf5a7b522ce6749a7beb8bb4e57db31738d129f1ae51cbf780f288a01dcb49e1e09cfe9f1888bae528879a88d05465db8211346cc9b9943d10ed86ee5097cd85f66f1ceb04e07cdbda814fca631a3d761c00a36aa1798c401e7bf133d613e0edaeef4fb2a7974f960979970acc4faae74d8915b037182d53aa9ac891139fe563eeb6e1970a6ccdbb4f377f8f79113f2c0fb22011f815ec99a25e1400a254aa9c70295197a6e4faf085f8118652e5233554296f90b19496c78882822ca4ee933f117e93de3cf6060237227c70f0c3b0b7e2da25e38714ae8098604d86a0ab686735cbab94fc9552208b6231f9afc86d3a3f49f4d88a889be5639a04bf97a2091a9dc647570d01b9b3148e05b5717a11adbc745675f8858bf19f3f536b9f3b5004ae4cfc8e1e2ef3a0fcaa6880ce46db6d3b1425b07e292f5ed03be4a5313e958c740561656ecf1fffd8b19f3e278d61f6dfff508c25267701f528bac53b915e80fb1cfd95572a2fac4b1ba440fac5d3775de32bda9c3acf8eac03739d8ca2484e393ce9a840fb6dc2fb2fd7986d1f2683e725f0bcfe8d5bf3f30a86dad41cf5c9bd6211a978364ef1202ef698f06c8dc75313f4e344d4aa8268d5c134c37f030b69b4123b3da7625168e7df902a231e8f6c7fb4bafbc739f0f274f34e639b4a7b76eabf2772e3e23b9bddd2187f4f785ffec9efe2d7a10d3690a22d82c236a6bafe2399a944bcd25bfc7a8fb10c3841b9607148b30f797cddcd7158283a0e938506192760a142b44f69a93edf26e3e0086739ff76cb4b6ea84802fe5433357cdf53625452b3eedf0b86bcab2c98ddaf006e891128d1621b7bd9cfd762db17651d9dfdcf0eb4882b3c7bb790b299339e366fec9219ef82c8e93ff70e58d884c5c83d8eaefa56d9f53817d378bf7b3afd6424e48cff85a4d4500568433ea10ae8f02fdadd1ac1c0dfc18e803984275f513713c2507242a0bb3cacd2630867fcb5e7518b5d2038c34472b26e8975dcc8f63df9155aafa01da61369ba8bb835cc19d23f6c7d0ed8b5fce09f7e2e9817645505832104d90a68743f32ee156904f0c3f24ac80f428f18af9df6dabc8470fb9edc0b835cc331f02dadc9286fdb00ee869ae1dc6c4e6ebd2ce8efd5b5566f51fb158c92b61d14fb1b6a0aad9586acefe1fe5aebdfe3c5c3d20175c0253fbafe424b83cf3d9606c73739cc4a10f6ed061ffac530ab11584c89dc602227920a25d5284be6f188fae58ccc5bf901ffcdd46641cd7727ff3c359afd71b3a2a445b84f2e111ce6ef248878b2702c55b1d61d5e1fd88a55ecd853a03ca20c1b23ab4206bce9df250cd624bf95e0c3ac23d14c1b1f955acc506c425f6ad2bf30f175841f6ddb76168c43007f2afbd1818ae872ca9196ba92dee276847deaaeb50759320c11efea784440dc1bdd4089a6811555fdc58840dc6f8bd8ec4978b4ed2cdfe24124434ee72db62bd6deff7e3ba37edde17b3bc3bfc6ce940c50b4ad26de1fcd8e894869a759a9f1465c96ee7fbaa03a132a9ee7aef3f7c9ddc1890d1618a83afa2256e0612c0fc650e313cc7b96cca477a03d8801e645eb1d3980f320be57e057743eba31911963e793aa54d2c286e5cfa3cc4555180da78b911e42255d2b41a16ca697c6afbfb373cc9dbdfc8c2f0a480ce6e7098193f9fc0d77333f7cc659927151ee9b59c0e0086d46ec686a08112024380279bafaee4ae4b465d79d0672433a949376b4cc5dbe22e8a24c1302e219b6bd6221db21194bb79211ba51378b50728a8d58cd208ae5e0b9c554a46c3c7f5da07aafeb74c5d1f813209a337e3fabc14977047018fe82d5805b7ddcf70bcc66887c3540c10e6e077e15918531122ad0351f1c675c349778fbfa0b29e833d4ffdd83c00a46814a8c4bb43b21dc9cecc56477232466e636b88e43bda5a641d5ebf72c5888981997351bce669ee7a8d655fc9bb3d5cb41b95de8265809d7f033a120ee013eea6e50e400b57f27679d42bf0d2c437d708eb74020c96d604f833044c4ec53bd85ac482394265c2a4ac2bae5895122d5f125afbda4101188c9fae6dbfc51ba9c065ff7540153df04807e23d9c12a1a6904af425ad4353c9894ab0230dc51d4ea813b0b46d179ba783b3cf27165f099ab3d21c7c1af6fa8982609598ba735366115349725c65dc93e0fdc2d54ae259f063556c2d19c5317b0e7dabbbf6dc724879fc784e68fb5e37d5f3214276c76cbf18c53c072160a56b683f27ffb0f0894b64353d01de0cd99d329f89a1fe81a129c9da8017030883aed96cfeceae71405950327c09719424514d5a698e585ec296b38e9604ff933d6c44cf1734a8a623c634d850128a5ff5deaaf6243aae0d414948a8039256db9759bcfc85ddb503638c2c2c1c3267b271f0894245a150e56be4647fa2e725c88e8de18396c519829940e5160112d980afe2032040d78a90205fd060546a7290010352041d6300fcf8d33b71dafe7fc157a89cfd16d19d9b23a88dc7e62c457b40af92a7cd7461e0e503ea82a60de35f1bd84fc98f0b55dd9b0d380cbbc21a66c15dab5c574feb16e62e897253cc022cb38ac896c8daa28c18aa39c1eb1d0f7254890efff31629507ff17d58b856dd21c832194d28320b81f1ac1dfc413f40d852ad948924e9892c9cd119bc18deff9159a3a5bf73151f101301e7aa84ef31c5e5d010965f54af0f218b210dc3a5dfd2ae5be6df022f0b46fcbd3930624d7bc0a9da5005ba88ac24168c435c49079ad1a77728036d45639244f78092e3037b46d0b2cbe1690f01dc18ea7ae2d58c64230e504f6272fcd3600779b379bec4899febec139f534c7d42fdc091e15256b92445d7701cb9b1fece634fef73b1cfb8acab74016f54c2e96ff205808e9e7f64e163f070bf635c14d51ecbdff4253c39f24f1aee95566c753f290ded7fb2e25323197d4625cafaae91f7a8caafb00bda6b7eb742a5a175ad4947f516eed15d4396d04cc12d2e9c7891d08611f69f48f631d86ef1f59743c813db36c35bac31c302df5f7842d0423d98818ba49c72b94db52f915294f60ce59fab9f939613aace89487f49542e375a7d21bf887d22273867304e3232fccd098e49a56617e951d21de10af4de369b7f61593f44bccb1daefd9579eab62da33c36ea8f0d965be9f68eaa88c73fa5e60c26c1696dee3e16a77d85eb6cae76ce862820c44d32db94b8577810afd39c18d81122f638a3c5125ab9361bfa8ca72231a74e60573f9112c21eb42b3409975d5804341d115243196d770fe4f02d304e4cb0b21fba77942f13124298f9981f55bf19303c70aa65f3667230d63febfcc74b91f98401c8a6a4ca3d91e915465f6c29eaf5031ad56e26698d2424f12a69e33f30c2c4f03ba6627ab079fb97f7ad55e26f23309eea7d0d87fd4ecfaa7461ba44a46900cdabdc3bf0b6afff7dada0ff89ffad9d41cea450b31caeaadf83aa9a5dfee4a3d2150486eb418a3dfb12f28de97f2d05809f7bff19e97e43d200c7f4f666f9c7c4960f29274fe1218ae01844fa1159d29e4069f1e09d75ec8e10194aebaa95d2e15d2aee639acd2c24add86def6bbf588aaf52915011c66709564bc500b40e6a42768e3b114c7be7070259104600470afa2c0901b67c7b270d721ce06262a4981e40f31bb77fe6931ff3f1ae20d05baa9eeb6a4f3296cd436cd69e55883dff60101c4f8e40d1f0c30465d9bc97b81f6bb89565e52b33b5b4e01e694dc00823940f29a1034edae734df65e14ed01b70a02a2e490a1869cc9dc2ac2271d3d13090ce85c466b98be5a19339229450bcb3da730ff56e02be7371ccc8f41da095efcca83a0f29ead3c517a80dab8f2d1cee442cb5a7e9d6fb7b40d2bebabf6e818a25b7ecd791b23043738b31f741a67bb86cfbde222f79e1c08b7d0dfd265398bc44021404eb8b4b0438bf0c4722cb88cf01e1f214bebd4bfba387710ba01d106c5000f1b88ce2ee41c861ec9e89c79be79a1d7703347f395fc84dcadd6ec20fbe0ba1c49d061113f585cb885e55091243779b226c01b0afa54e48807fb7728e80eb466c814a883a8a95ac3cc7a93053d6600553cda96802d981a55c273e266da4ed86d0af2fc616247559b020aeb192a71a8687b9fc8ef285b8fa43c37577f7e693d87e39b4d19090178638d4c42c875804edf20b4cc6a4ed3d5bf5e37a1abe3581810a74251f021dcbc46b97a634830978b7f7c0dff35b73470099bfa00691f96c78e89a505ec349e560b618b399de6c4c500edd216c6fe7b976078a5d7593bf5016370b8c85cee5a396066686545088ffb10655efdaa2f05348095f56825a3296dc2686c184bf4c830eaaede69b5e790a2a9608e5567ecf97fdc7370b8539f507fd9a0c907ef84570a5ef17b232bbdd4681735185ff4340e1b9ec92b45e7a34dac11eff45e0d63bbfd4f962f8d3b466d32708f640a0d9b88a5b364d497813d18e775ad7ed1dcc4de619177ad6b113cadc198e74692414e19590ff2d79156d4dc6d0d9e8ab427088d3dec7dca570b66d954972180de0acb77ff28047873c10c70018368e335161f68a7e13941a57f33d5ce3b2548057d752fa3e59f5ed86cb24aa580077eec28bc993c70cc9b20e8dc6f0593c3942e2104c3ad8b9a89030fb8644cab284180e8bbaaae620e787c496a217aa6d94756e5afe3ff654e4ea53b465d88adc4c8b9f1e532677b8f6bace49378a1268e4508677c82b9efcd41d340f3c01d3d43ec89de518a3ce23d7efe3e39db9c5f5371cb73d02d92e22c7b3834996989518c09b9f05bc65f885c1383cc40dda319e19ac0c95d3dda2236e710481a2a4b7ecd332d0bc667878aa25709018a8650c3d0a58404473cd313ca832f65a7f530e6b6f902e6b55b87a234c9634963a2bf0cbc8941315d0440c575d68760f6042e0ee3733775db90d51a0a957952573db2f0548ad119944a37693f4c81124fa5ec93119a5bd1a7aa4a8f022b9db5b3191d2b25d4509d4d113d87cdf1ebfb00dbd7d5d5fa56b13fe61f6155efc96e0b2e339c8507ec7a4184ec04dbeeaa42381f3e41efdd297407c01e743070a9320090a1cd00cb8d52e8d0a5a1c1ae693957f4d69ec43b233a2b864a12efd58dd8b815b7cd9b1643dd4d79ea578fd3cf412c8971080fee69bcd253ab3304b74262df68c545a0c94e5560ffb925f08c2706ff065d850ac16f6ad48858b13f81dbabed407f93189744775695fef3417b2f7b2c0132c629a2b4443760465dcde711ae2fbc820bbdf6be730eaf74c96c763644ebc72712cbfbd89ba698dd65ddad5c1941db117c4fd1d7f317f035616712ab6054c6065ea8dfab6efcba848143ce4e36601abb5cb86c48a9c1ea97a00f9c17205091d6bc0e95b05db42b6d211cca4c7e645002d670e39bf3042554a0f84b76c263349a00566886c9935af571e7a7b00888b71cff9705d38728eb3cde1ae0a776515a142c2815b9f65fb0b0a34ab9087f02295277c857e5b6572bc890aa258724126845a6c349863e6550e479e8c30e8c3eb3ab9950e64cef55ae7bc0e1d292ff944e53add96f31d5375adb0df1a529a9ac450f93a8e27b617dae1700aa7ec5f0d85a68ad81bc0064f9acfdc0185353d9cb8c0452a4b5f6b998385a3c80947711240048836fc6a670ef92780c3f01431d080134ced417602a2acaf94a9b9659a23d89f63216e2254dd7f10c9d6a76607a9de1f0cce4af22fde5bde728e575147e1e7ee2429fc77552277670c07e52c4c26f120180c20c5bb52ddb5b6facc40ddc94c1d72bbe8d38f90833534c57b0f30222de65ee030b4f8685325187fa6aa3d32a56e7ffec791151568e01a6c75d2ab2c93d8104f8d72b1f238808bcdf51d465669141c1e08870c458a4405842ba6486288e0296f8500726d7067f930195e14d53d7ff4e4b7fa563403c9733fdc7f335f2b17dfd6ba3690fb662a0683f30020869e2246219701d4219de388d7783260c982cbdc9491accda9ca0bfe0d15619a67bc743ff10e8ed61490317d5c53b2f5d49219d33641f5a0776abf7981a44f466d5b3874b76c2119afd5b051ed43fac008047e63770feaa1660bfcc04f2ed076e85c2ccd3fb29a2ff0b2bbfe7027c53236e83f36d17e9173f2587daee3c2dedf4a8aee48b565602b1582c8f30ba6427603a97100d40cc2ef0a1485b68a3bbd078605262d6fdfd4796ed2f3fb242349976973e5ebd9d60c69b7aca999935b36697e0f1eba8967d3c243df69200830b1b5f04b144001352ab840793070bda171b5cd426880922bb208d28f150425ba57a02a363fbf86c20509b885a6c8e7044095288c4010f3dae4f07976c657ce783ae500d07cba1ea73dd67962126cc3222c822fdcf805b5539c45bf17e9919b666e262b4029d0cc3671179bd30db439c205125755499e822a25bdee43c784812e71de143fb442256d906e639823f28b63b68f933ef9ef90f5b07239fcd20207f78d20a0f027b2117d9776b35592b8a7c9bb508f9b28ded4264a5a40f5c37ab64665bc72f0bcb29db06cc49935691c1a0c47f5d8d5c015c3426f04cb2c6ba957b9b4eb778b9518630a1094690852b9117f1166119c24de4fa32a7859fc04426467a674c29bd56539dbcc9c06cfe18a1162f0872b016426233786e3ead20750ae7fae173b83cca3ce7a9d07fe2ad29d76568ed1fd2c2abe7b574c5727ce39e5826c64950521c9cf7003330ea00e53526a0356cc55fdc3bed329e0db188bf3ff76f53966374959b13e5821c039e2e09bbb951ad6c5be9a3c42128b87b3d4eb932ad82b5f62c272274be133cb7108867dc0ba57b630cd65caeca1fd04ea4118671859ef4400a66a3757a1c7c2660f57ea0371cd8b24ea558d992149888860d402e3a98db08cf4a8125e09f4aec8f7decc2b2898f3026c5a257ff6423063204cc209847f8b263b30e760443c3f2c8db4ab3ef5b58eacd40751200cdc3d23670409be3adba86054b5adf1f378782bd94b29a8b9a18f6a27c2a465b0c7aa76dad443d42150eeb0ffb44bf403d8e04b255731bd7e8405a65d89908c76fa29e59935c902def06d181e8e5faa63f36e3c9847065d306a547b8d7fa5d9196f2305a7fe97de608a707d666960313f9db3e9e7e691c0b77cc930541e5b7fdd3152c91d0b7f0d4b10a13179812993ffb71eb46df48da247da832a10cfee0b2cf01fe2070ecbe1c18da2bd227ed4fb1fc4fe21fb923092b7201decd911311aea492341a0985d2e102a20681a8b78641f16417e6f6342e04b5a6f86ce11e0fd07e72533cc56576d771980b283c7ba19d241a474d4bea847fc33296fcf8f71c73db894676f8b79c9869f897a6912663d5799dbbf7c4db93fad8b0954d0da10f60c66a6db7190e77d582ad4ddc2d576abe50fee5040d1b3b825715980d9f4d85cb32468fe8d43b7c1e671a263d14a065944c603729d2a5fc923a6760731e074385e918a7024c43f4c89b408d1c4cce0fe9252b8c4eee0008d5d1739c474eb33e8a67e95e8f5aebb38b80907dcf827dae646d5d2ccb761799b992154ecd50869c65aa788c5cd685f71d7d0fc71b0b50a3442765d3d888a2fdb863668f6effef42294ca80d7a94a893ef887941a22515cd713e3a265f1ba3ca820a9081585bbe34afdbdced88712378368c2c97ded2cd9aa815487783418886ec424f0ea5c9c3a0b549588a77db4659505c50c3ae0a2d015fef6b97ebccba9a3583dfd033dcb6a7073c9d0e9dca176c583338001aaf8044c3d70b0fab9f401e0a9f50cbcdbf8a738fb5f9c584d36b1a0b3189464cec513049dec5ffb4a426dfa4495bb4a274400c69257fdd8e749de73719a02a8366bf937552cb95f70e16de02596e3a09bcaf54cbd08c95d58e6e70747a432640087694e41ceeed2380a54c4030b993a91a10eda3e6421b4021c1536cc1f48d707945113c97e85983ee40ef8733d69f595d8c415e822bce2a96941916a817e64af4ebbd9c696b88dd2fb76fdc7c345f1bf20ead848e3d06bc398a283e32a0e575848094df944a1d69d9a390c9bc2e906a5f3c82de9a16d00e61999bdeab2a4d9d710a25a874c27803630e184eb6c41d2f82dd7ff2621b18a32013f026efe2b95c2179076d9463ebb2a30a4fca7e274fdb6e4d362cc429153860530b493584374aa9e580b9892ccb80738dd8f7d617a22fe9e98367457c6b801e5b6e59cf553c470b607f7f9421b3bb70472bf9cae3ab94a307f66461ba75793816ce2606aaba6e0d6ee06ef477fbca6d9ebe05c9f734cea9edf91b2c966901736d0b911ebb2a91992d4de4478e63847828024be86368876d59ff49cb1e30c98ba21563c817b67f44c2a62b31f604753f1779595ad7b552af2727032a7cc0108ca7fee57f04bb369e20b93bb15a41a24d0bc6730b41e7209025b1bc8292ca5748b4e2f72cb6dd8a97c284da20b02bd6ca1076994360b331676e6bac163c82c707352d61d199d70d2a7a9e44311205dd576cde7ab6a65071c07b7ed9fb5110514d8468016ee773820dd1b477b68bde160bc2cda7fb5f40fa5e4cf05be53e94a8d59363c197688e28f9b4fd2b2920d0e186d693a28acf3b18ca715fb46f5f18256c90127a203185630c1e28f3dee29aad8d0ca0f6465218ad7409fd4a31c2f6f2007c7581956f190393bb6155375ac3bbe31f9371ffced3ed363af8e4314e4d2adac115abaaf7ad6550710cdc554440a8fe2301c79bb2fb468a370ae930bbf3096a3f91346add1c791bf02e3ce45708d9679fcaee56e41967e0701da823f9e5cfa881f88af11fc5f004e4f8f7957895d179e815d40aa726716a99dadc64f5ba11a41b539ec1f6c6e589f68c17be1a873237e479e4eadda1eb1a6742c9734e513a449834a841cdcd196daf6897429b4fec787ee331dfc147d7ef3fce156f6b731c55af0d57fbcbb95069f62860350c605c56f65c18453af5699fe9db21ea85b40f510d6f015a11bdc417cae5fd6ad958338f0b8736ff10c7f38c26d545434f7b55e7d0d87e1af96d6acaea43eeca6111cd2fc66ddccf7c9871a10cc97a4fe4a4f43ffd5965c1ffb38bf67ff4ef4474c91552325916b9a8e1c4aa31f1d187eb0feda6ea06d44c916db3168f0bed60d5e39fcce0718fed93bf5dba83932b84b99360a7f95e1f00cfb56d3e16a3babdf13ce8d1d3cd7cda0e5e2824efb301727de6bd4ae3e8965bd4fd651448cef5c80e94ff02be4a85dd0d3530b026c30100000000000000005c2bbe35fb66df45bc4c29651d529ecf869d644a494a99d92a826b59e120e00206883475773778101b0e7b0db00ddace7b98550bd79a8aa2e89f64caecf9c54654124619408268400c1ba1d0bb4cdd4c691f1d5020f5af7212fbd698f51308a1495f1217be7afe124fa082aba99acbf1a0e910d8e8c467b96d51cd66e4063981e687cecf58e641bf9b78664577f43acbcbb4643239034890214244f00e84049a5854052d1acac2a47f76e02365e2a4b3567125ad82ed9830f828b33e25456a124b26131b97308893d9ce6645688e62f018f229501b96b035e68b9fd029849055e2343ae58baf0ebac94489d7d4472f13eda084a6643201d2c3c624cabfdc68f966acef954479a253a7b01ce5d7974c264180fcf0d163033622718e39089de91bf4e590e0ae838d27f9122d9e920bd878c4ef655a94f6f6f8b933021b8ee0347de65cb67cbcd788ad472ed47752cae262011b8cb8feef644c0b32863a2da25febf8232f46e652604311056997fe44eba7dd9888545d9dce18e6faa62b4144ea3c9cff88fc8b493d04fa43a74c425ace2023437c498929910b715bca42a832ba4e7d7997fb87023608b19d18ebca0e97944e07e15c34b19a492e5947108e6712ea537cd67652033602a1e92cff1e569df386259349056c00e233135ab3dbfb439ff344a36bda79b67ef05485551ee1a64d08f521a54909cd2baf7db1e58359acf39f6ffa55caeec1f28bdf6f2a7610557ec0861efe0c4fbd9fd9540a9520407a8461230f89151944a356756b28021b78c872ded01cb33d821d62a0c0c61dcc6933438ee6946765c964e2010f00000f36ec90108d9f74129e84cee6086cd4216dd0c1c61c4660430ed8a51799a4ff8bb68e0736e250eacd6df9d4e794e95367d880c3a7ebd133ab766a34df60c71c943e7da71bd6d253d1f24bf95655d22b021b6d5884a51c3275c5da8eb1c18672a6050b6a292c96cac61a8c1a2f2795e3e7e8a153831d773ac3661331b234dc3b1ab6537d8871d90336d0d08912179784b0e8ed0d31630436cee029fda0f33ab3618694e7f6b42f794aa9fd0007f0281b65f84ae676bad42a171f19ce7eb731a711fd1e351b6330deb865bd974a26131b62b84c9be63c4b362a898360230c6e2a25cacbdcb269130c99c5b4de29fd4aaca664321122c4c617d2514ba25d93ccafbf176c788175f39482998e666f5db0d18574bc747837a59525d5820d2e24749a96cba7f378dcb760129dddae936c257b9c1164880a6c68c11017523bdf3726155332999cc04616b0d0cd79a3a6fc3987a260030b5ff434eab5c6debb62e30a8f1c39ff217a77dd52c18615ce9bb941c5ded355621b55e8fe7f2c85262f19990d2a5cd24c25151b64061b53d0ec3b469716f94d25296c239b1e73360b6e7f14927717ced32995af64281494dba9a06bed9bd22730dae2cee5159976a513fc4dc12e54d00abac126702f66ae9dc45b748709c991c1ce52294b33992558a927f23cbcd8c6943bd85002ea64c7103a26a22e598c196498f1a3033692b0699e1c4f26d3694c2ad181186c204151d56976cfceee63251db07184c35dbc970899f142c908f7079de3a77449c9cca7c14611dc6bf7ca297800f0051b44c8d24d3db6addd8cb844c87a00d91842a971d4d6750a19abe3011e0050820d21985e51530ab7291ffb60a4f79fb3de566dce513172500318a57c7d492919a4c8b88c1abf50df4bed35a6aca9927ce1ca89ff06dd77f7782f94ad2453837de8db382f4eb272c736bf09b5f52e56fb94ee4a09adbab12ef43ad1b64974b8cf6f2ebc3b0d769f99a2e2e2e2e039fdaa9ef4169d950ea9d14295166db107f5412bc7d462ccd5e2ee4abf33ca35735368511a8b2226747898d22cea90b129937caf4dcb82f7783a794691aba2587437feb57d41f44c58b4365661de7a456ed1c3870817f555b9225199d4c77862566ad48af3f205a5d49dca17ab8415e894c4b7d8e5dbf279155a6ab21251263e8aac8a4fd79dcaf6b8164d536156b99e35b9fa994d54146d9452d9846aab4b9e22f398f1d9b0779b82a660c4798bbeee9b6eb70b354ac1867513f6b1b64a644891cec96a742bcb5dbc8c22d11d57dda24bb46da2387b7bdef1bf79ef94196584c2725369cc2ccbd5e5a0486efacaaffbe8a9a29f48b52e369adcb431351bd4f04499d28c6d94d169b4dc89c52a9af418ee32a91507353871a7ea7411eb309e64a850631359fad6ba6c55bb2254020ff040c16432c40c327850431387c8e9a78c4e1dac93821a997836fcc9644a96fc30964c264084fc0092010e84608707cea8818984f56c08b51765f6bd849e4b2fd96cf8d75cfe28c10e317650c312c8f80afe1f4a1ed4a804e62273738ed671295432996c8007106a50c29474909e6f4283e8f8c30b3526a18cce660c2d1d9593430d49b82eaf27b4a714766464a811897488ed334fd9bdfb86b0a10624faee7c41b48d10116d478990f5c00e1e50a8f1884385da73d7fcccf905927e7c200d351c5186bbcd7162f2ba74de8c31968c1a8d705410234f6eae887e08901f3b1ee0a3062358a0801a8be8e1a38c0b9c31440135140124fdf840063200811a8968400d44f4f051c6054ca0801a87f051c605be7f0041400d4324a04621849cf13758400d42f828e3022c28c1026a0ca206355831205043104202214401350061801a7fe871801a7e10408d3e14a0061f0650630f2435f440801a79e8e1a38c02d4c00352f5b4c45d94ce55d7b8c37f4a281f53f79b93dba1112a089d4de42ca9741d74cf9c828ac94b7416d1c1f82a4da631524dc63934565af4e5e4a2b4de838c1a7248664a3194b0a4921a713857784bf59be2266595f48f0dc270387b1e711957b3bece37e0aa6996adb2745d23846bb861db1436df6633ee37259309aad106fe4d99bad920b732a6061bbcfea42235c5d83215096aac218ba3473de81cdc94e77b811a6ad04f69ea7c49e62adc6928e720dcf494c7ab3842831afeb26f88e878aacf708e481379496d0e76965c0d3394b335a2645db05297ffa103203d800c994c2850a30ce726d1cdfc8f1da38c1e1ee051830ceece5cf6a89b827dec023bc4d0408d31b823cae5cf52c6ac5c0c6831f9bbd82909cb701810d5aeab999f452d080cc8f3743e961d53bcee0ba79c343fbd7c7e4c1f2f349ba74e7cca74a1a42f76867726216734e4c7f6ae408c1a5ce8deb2adbee591a763c9a44710202bd81182c9040535b6a0ae076195414b8a553850430ba9264b96f219e492521652956d6169345e368f05bcca6694a6dabfb8af50b9bf7becac952aaf15ca1c39f2f4994e49b8554044efb9c60f150a5de9ed4f3d2651d1293c42c6b2ab1cabc2475240f654b20a2a768c470139f6f6290711d5183350030a975a2cbd1e96947ffa0968f71c1a632b6ef1e181359cc05dcab165c3724613964c2667ec1893898f1f5b46d66802b2b2693a79612f090f13d64bd1d2b6a73832c6ced82041b6033596504309986acf6142fb873dab643289418d24e0df31f88715a15e4d25934918324053c0046000841a48f07416e197f635fd948e90664e0beff9c3520e1a637f9461468f304eb043043c5c50c308e5995e8a49d693deb808e83a93613afc7cca1c193e4a844420073de881076a108133d96917748a88abc6102e93733fd5d4aab9ae2184763f93ea28e91ab60dc6a1298ecaec7a6a5f01c32446ec3ae6fedb181252041abfc882b2face99ff995d5fb81bf2a2e664abd1a241a0d18b8276cba8b399d7728608347861cc54f94d65a599cd2981c62eced55de243c88afea58b83888c49a798f36591736112a5d4e67c5f71455b3299f880062e30d5992df3bb192e060047a0710b7d2fc7d3fef76174f603882d9693dff09e364756aa16cf0875ad496ed0f03b2dfc78ca747c3e7b576508346671881ad39dd59451e42fd090452364ee3fc388bd34c742cdcadbcee91d2c4c58a04e26bd71ddbd7ec35798315b8893599a4eed0a37e9f0b1cdc4e69472ad40c8efead9f7f6d11f5624639ee98dcad952d4ac22edac285f3ae7bae1a7a10a4575cb560a2a769ea6e214cf7d6f4d848a4aee665c12b731a72883de3bb551d1f275a640f96f1e1daa3c27e929453ae5706195c2bfa81852a02d33d4e5cec2651b05fa83fce4722af54783001102011aa24078ea0b9fd45a6e463fc063c8776032e94146efa00c1aa1b0f572f03acd3db6393e8288e1021aa0309a3a9341e6744c5b29994c6ef02fa4c78f2f19d2c304343ea15bef5ae5ad4d9a691dfc8fc98403343c715e7a2e717fb1d3bf6432e91104480f1d6c2710e95e2e36d7a9e596f4904f93491822e031a1c1095489cb090fb9972e6913e78c7cd01c16d56a0432a447891033ca10010d4d9847a36f10a7737ace967c804626eeece33b42dfb32aa56432994cc2c8008f1bf40e4941100d9c80062638ad1ecbb0315d66bbc790ee61011a97c83daca8d3e097f40032c4041ba06109de76b4cd474d5d972c19638ced5132a48c773178348d4a94a2d9c78bc13694528901031e1ea0418947265d9e71a31ad098040d49d08804e2368e1475fd6b3e974c262ea0018962c588dfbd92cf9cb4150881c62336312b76aed672a743c3119e785df932254aad1e091a8df82f336c9d5f291102030d4660e115cd5b3d261934051a8b40759bde1539163b1a21437e2c0c6828e2e47e4108d13df130a38046227237ff98d3f45fecb764080d4470dee9ce37c6d8e4641206101a87d0845cb39223d6bb4c5ea0610844a8dc0f4a372965e92ed028442ad64833b13da2b288108ad506b16c5254cc9b16680ca2a46ec3b2d9668b151cf2e373200853a6b60e5b39a960e2096804222b9dbd349a96ea6845810620509ab616d5f47c640b051a7ff0d566c7eef46897af6432f14325e4623eb605bf5f4b26931efee3078d3e6423bfb19944c326081c68f00133bf6d8dd92f994c7a84a1802040921000a881c61e5075b519261aa7e5d4431bcc54f43e7d6d0c9282461e10aac763afefa914f15079a716e5a363c54a77b074bd3bb958e9ec6f8752cc2f6742ab7ca8ba0e9aae1032e5df3fca4c87f39ee8a49e7b34fc73d8efb2a4798c0d76914331a8f5d38d137e1bc6c134f5a5c301a6121ee5400e2c7429a9ac289a62d28257c86105e6aff5c4a6cd38ffaa907c8dec5cb37183faa8a09ac51f99d2e73abd29b07f3294909b9302e24307e19664e5954e14ca769554cadb50613e50306a2c2d32fdeabc779e8010379672d238a1e49f5144a713ddcb6942499deda99363717326d8f1b76ba356acb897705e7e0eadb78ed155029a174363a9a464f09270c88ea1a57c3e56639090e308fae893f14d8c58e78d8056eaa77817c1a4e7c44b8668512a11caca504b2d87e06d4c39c4e94a0e2194527a4fa3e3efab2ac138988a0a1a2c9d384b4b180730d49439b641fd8f4c2a0f0fe0f84529a3ddc6ca6019937c51a6d5ae9c84fcad642f4c594dada4eba8ed182fec74e9556e6310aa63bb40066f192164edf9ba705cd4cba63c33e2cf45193b657a5dcbf11e17a8e4b1446dcf47374e021cb730785ef2bad9d49b6d51becda516344558b0167fea0a6af22c93e5d0029d59414c75d0a42bf7400b70cce298746fdfc7aa64321963458043168c2acbf0f9b2b1286889513ff2f6c72f3860b188f78a991577df64893b008a80e3154517b91d665db329ff200e571cc7e546881f19bb4e2561a4603249028e56f4e9d3bc8389967aac6432e9d1041cac3845dbf6cca4e5c00e15ecf8008f32760443760c0e4c263f7ec8901d2305c1b10a2ede865c12cbf64c9380431579eb5dd8ffcbd176c4910a6cd547f792dad0a6848aa2253db5fbac16709ce2f33a5121cf47e61299c2f05b0d152d5796e9944c263d26930de02885eb257aafe29abea4ea020e52f0baa7e7e5e4e8a6b41a708c82d9945b31c7bc29fb248a34dbeac61d2164185328faf410a682dad49e6d0040010728d0a9d495d8c8ff5fd227d098d4fbc266a9baef0964c36f890721dfc2de0943463747b9892795e6c4c9cac654e3998bbf89abc733b576890f91264ccdb3176794dd7599583dfa69d8139efe614c6c42ac5b1215a388e8127f65cca4df2aa94cae254ab9bae7342fd427b5129d8e9729c720cff4a7c477dde26954d218dd4920379af0d1db314f2a09cff34c66d530ef208ac479ae53c638234b9f798306a2031c904874126725977cc4213d0895fdff7da3ff1c04f925e070841b45835ad2515fd146a0d62d665a7c5af50c23d64bdb92a37ad386cb22ce547a42e5ea53de28e2106eff2f97e3be89309494cf1b5fc9cd3244a0eaa4a6ac995752d521f679f354592b456736446a5ebfefa73f57670b71b765df4adab3e58c1a0721ae2b99cc73fe6aee9841fcda15938dbda8780c8720ee982f3353ba9c4cd71fc011081c80e8e4c89eacee775bddd7033c823c0c70fc41937e7946d4355a106160c705784c263cc6c0e18777573b5dea7b90dbf701fb146176a36c54753e64ba19cadb7410baf21e7ef5ce51e3895c19e9e17f99edebcce6fa6d1e5e37e95fd253fe9ca278c07a836c0d133a584cdee12ebd319a654634867668cec34a6933359eaec39d493dc988efe841744828ff24e574f6aee7cc0195ecdba35825cf580e977ca8d24dba4a832c0ee89c4795cafad1435e24c0018784a79b964b4988fae90d6db2a0bfe3e9c90f931bd05b35e9b91beeafb78155b9aefadc291f72367c6adc7aefeada837c0d09cf6e2b69dbb377abe1d3ab3ae319ced2e734243e3e2c874c96b4b8683068116b5ba1f65fdc33d8f1313a2731722cb766286ea964d23cb669ce96e1ec790b95e9fded2b19b29ce9f2c96c4997ac6340ef5e8fba9bfe282331a8a937a9106275848cc280ca22c22f98c0d095fc8ff25fcba2fd822932ae8557f3b42abd80b693ea1542e59f521734ad0a1af7a2589e8f0b2591fbc16478b5b7906dc8e029c45f67570b9f960c9df24cbb439305467afd99d2ffd1b482852a85f20e291a2ee75740c89cc60d32d8c8a015529593f68ef765a7ab82eb396ce69afbb01ec34185520a259e2ba97c39f41fb213c03105c4c7cabfb1bfbca089000e293c1637c58690052cd0411436ff60165f636eccbc6432994c26131f656c8f203ae880030a9c5f08156b6c539ab36432f1058e279cd7a2e6f9fdfaf5e10a7a0401927a8cd1ef650801060e2760223e980cedbf5f630f7034414bd22a3d6b76cbfc8e808309e5e857a242da7adc3ec0b1842fcbe98bfccd127028c15c49af2e76e828ab020147122a4f13b30c0d637a2d4141057020a11cc4427aac52413e029bcb4a77ac55e6de8d50e5f5c8aaba08c9be545f394c846305bdf6d3327a221e424abc66b97d38294a1c42b03bd52b08bf68f605239d6263c73931221b054696572be7519583b6fca268315f6b955b43857c910c3298f7bf8af4a0ecc559ea4b4fe78df9c4bcd8767b5359cca73fc55da052f6b8125eb96b92b13f26933080dcd0852745e6ced6a719e7940bc45b5a7927f5ff976660093770510e9accbe3e684d16e7a1834581226edc222bf77c4afbed7efb31c63823888f0d22860e6ed8229115b7375a44841bb5f0d233a8b0d1a4cbdff631069b51460f1d6c901e2bb8410bf5b2e5a9d9d80a9f30c278c00e07ecd811c6031cb0e38c201f060f1ec598c183c70dfa4b446e7163165aaa9453fa4c217f47e5862cfadbbe0c6731e6bdaddc88c517c252e51c2cea87088024dc80c51ee492f08ad3633307f931c6581e32b8f18a2ce7182af43e5b922957b442ab20ce3f5f8c5810b3e2c62ad6acb26da6f6dc3606c8901e434a6ae0e39f0340c410c10d5598b2deeb1aef7a6572e0462a5a8f1d47a80c423fcf1ba8389ffc10f2738d368508f90e32643211726e9c624b3a2653c818212eb8618a1ba5306da7f74de5a72f850c39dc20c597ff9355cad49fe591d12d08726314ffa70a55e966459ef2862894d310ab316e36a1aa506032061d35b5c624561d62c608f20628fe54aba0b5620899926e7ca2f8593c57c7f6f4182b373c8196974aaaa793e8fb9d28d7afe8d21bb4babd9ce8c3d6cc48117d3f75139f8a99ed1a93d8c954137a6899a98ae2299a4c26fe4ab594ad4ba6d40c264cb1b5e2684db94416ac369bfe9217942cf177f04c9aa63c5db412df568b76785ece1ca344d9c3a5b7bcba61443589f2c5112d6f4a8e852e89525c4ad9d27c278dcb98483c3279a718fb14321bb8010993f0f21c43e3e51c46c9c18d47243d47cb7f319756fa8e50de94b4d0b14ee9e46984e94d778e6bb83c1a3302e1d9e2a70cff418933083716f1c8a5c6ca193668c714e17ef5a80a6245ee7349b89188741e55152bffe50622f88e2316ad479765b1dc3844d9c3575c8f3a274677c310852eb724a62fcdc8aedc28c4ad6149be866c7e26dd208469473e28997ac4fa350833cc7c104b1a3a1f3bc20d41ac1efa4408d1eea1e7c08d405c42ebfda891eb93610f6e00a2b8772f32cf0815a3e80f257516cb84d0b5f51c3f349b1f272f5acce49a3ea4934aed54ea67612962dce0037a6d7a4956f472508f31c66600004ab8b18742c685521943d66d7c430f8997ff4b9e5a837eac643219c38d3c78f9d399e7af7966ae70030f87ecf83ea5c1e454a737ee9087d9ef7b7d93e1d9c1d229e59d13bfc9a477a30ea5da389a448476f5890e484bfd3f4d356aa3690e65292f4bf39df6679243e6f13cb69b5750e3c7c1edb92e69abdd353a1c0c22d4830c95ff3bc7df50921b36c58d79e11a77c3297e54b5cccef3a9db60cf9f4ebbfd7c9ad46c48e8cdf7651fdf54e335ac1f3f45de3bf89e560d6aea33b9bc1dba739b86caaafbd63aab36ad6878f46b334cb67f7ef50c5b6c79ac0cf24e76d40c79e6ea1cfdc95473548662f6eb9ce494d8ed4906e38b6cb9bd6d9231c780988fd621ef94744b8a01dfabeed0a2bb328361e0b27b4e61a363a78260d84b6f86a5c725d1ef0b576b8a794a85729fcf0b8ec9a5c5d6f872f3752171b6a6f45470cdfe00c00837b86034a12e9cd0a2edfa16ce2647a7d469f92f6ba15c3f3e2a8cf96f5016aab91493529684d09c8305b6cfd337bd55ecff0a957bccf0efa3e6e762853bc7fa0b732647a5ab90ed6651f2b64b35a5c295ff825ef24b169d4231bbe88f9bb2769014b8fd511ef2ffefbb1305946f5dfd5957e7a4bf018553f8a91c3ff384e2c8d4d8d6e557ebc1c10d275479834cc2572fe34613cca3c1fadc2e5ec7bdc1044bac5b8afbea41965c32998ce0c612ec0beb9db759cf746e2841d314f27ade19749e66c8e04612aa386b76d7a7833eed061292a327af5fd92e7846f23104c88f1e378e5065864d41998ec260c10d23202a87fd9ef23609378a90af785059c74496bc43840441c20d221cb5468c52a23b3c3c2564f8d831da083786500e5e25ad5faf3794811b42d82f4c5c6326b5de9f92c9248c07ec3868c60e64c60e64860340602318368061885eeae59229994cd8c62fd231bdff596fdb32daf0c5a7a7f6bb2f6ab4936520888d5e9ce33cd794a9cde9cb800d14d8d845afbd3db66bda29a92593c9ffd085717ea3baaaac43ec2593491827b0918b64e56cb59c6c12d8c0853159d8aab05f29bcbbc5f2e139f5dc438e1296fc00628bfae7b4f3e694738ead16a996b7d19f357607959230842c116cd0220ba163b42aef344db3d07a4e2c5e59bc6c5f6cc8023b0dd14f1a46074df6c8808d58a4bcf48c0c97a3ba9f766003168b45d373ad7de93144365ec107396b396af7811a3c09c8f03199d4c0862bf89255d1f3c9a9eba092c9e46cb4424f174ce952f2661d0b6cb0c2a03f4eede60d3b9d5a45eda69ff71b7b2ea954f199d66c29324c818d547c313e96aa3796b4043650918c5d3569a52b7a454fe1e8d7583966e61e9331c5d7b71d7f1b83fec64ba186e8895bcede0ad5fb08220629bc944146cbbb4133532561a8c0c62802c5503df1e39d31c4071b7444e2139e625d76d26515eba10312a9b338275553653b3c44c723bebc0cb31d2d25fdb6101d8e404c56b3e6f246943284bced8c97a961461c837ab0d0d39ecb428bd8aad3347445c820733bd0a188362835569a2bbd9809083a12b1282b159334cf127b29820e4418dafd52ee097572ffd7e0c7a200888e4394b94ef9a6d210a9fc9dd5fb6d649d58884d5ef6d392bfbb672144f57257972e733871fdd031887366d6d9a8743953238620ca57ab92596467515f101d81c0675ffea2568a73018499c2fd82ce18e254d01f0c4f9a4ce58a161ff37e40664f2a7486c625f9e9e8c39e3d9a7e12d917bb7cc00a7684c0013c820019b22bd811021e43d0c18774654e9818ed198450101d7b709414a13bc8e8617ad3c3d9ae7273535f06f5d3a300073af29018ad5d1e2ded4913f150b07613622a9bacd7a06263e8b803ebf99fcefcd56187ce7445936fd1a9beae83d6962b6b16f72064ea201d502327ace25ad036cf21f58e71a7f27b8a222487539e4e29a2b11be888430074c0a1ac762ae6543f5515d6f186f3f8771ef3dc3b1012b00e377825aa72fd36282f7d490c74b4a1e8efa29b31e68c1b6703a77fce72ba1cdeaf5e0363b5336729ac867dc35a2a4b4fbea64dc325d663ea7e8f8f264283d5761bdd72d01c399ee18debe14697cece68318359bbe721a39bd8208b818e3220ea2e9bf34685a6c9500cb1a5d2dc064f338dc1b9a4b347effded5e0ca6cb26d7f9da3fe20f434aeb96d57e25b7dbc1e06796fbe09dba79f717728f2163bc38e59ba35e5893ac1c66c4e918157521357fa5d64a69764b72a158cf66c2734df63f5bf8c2e89ad3144285d5b8408716b0ed8a551773efa8340b755dfa66677c53f129994c32a0030b494da3a6b67ea3cfabe30ac6a534bfa1df0dfe85e8b0c2a131ac499166f943e5e8a88241e7c8ed54e9a0c21666548c5ff9fdc4a86432411d5328e78c7d7629a59825639f063aa4607b908d1936dabb1e3f7cd4e079320923053aa2a0030a3a9e905e3b1397eb9f83ee4e40a676a5282adfe1978e26ac3a52cff45330fd951938780d6c400713162545f63aede6b8211d4bb8dbafae4525437e4c2661e850c217df73f5d35732999cc11bd09184553ce5878f415e2dea3a90e0021d47b83a96fcedeb0d0b72180f70000c3aa0c30824d051041d44e0bbdd4e85d0292d3b256508b1808e21e810022af987c5aa57e93c188756d34c594d96bb0918760e9beded4bc8f259e417be303cddfd88eff63c71033bc4e880482f4478b1b857cab0af27acd33c3620b28b76df4c6e929bedfd974c262df80d88e8422417ee6b9de96873c96422820b4b73d0a2e498858f5e3299f49082c82d30bbca1c841495d4a44c10b1051653a98efb617c3c82482d4a137eea2e4767cfc192c9c40822b44099c9664b6f2ac6264b26132288cc02cf162b66d25154c5a7061159a0b79e2a9b5269108945fba9a49c48d54d32078b7a75b55bdb57934e9941e41576accdd451d51a2fe98aaefbdcd244075915c40291561c26e229e7288ab042d377da9d37135945faebe242c7f33e8531e43bf0061155583a7b5f873c4b7a0019d2e34120920a6eddf594d9e5f598c020820a839c4d1eb7539a785702915378da6de27542353d539c727c8dae14cb66538afa4a2751b7f0931693c210a9ba6bdbd19487a3707d544d769253e929a2287f3389a876fdab168a73fa6d1aa1dd62299508284a0b158494fd4d6a53229f40df345e56d097f5b06432c1838827d0f3fd96648fa6cb8a482790be31c8538db1db2e3a8870c2cd4a3165a2bb4aa8b0416413888cc94dd3338786b1643229838826ccf154ea8aab8f4a9f92fee16383780f914c6832b8b7fdc51ca39a844c262b10c104fe156c3bd9d566be885ce2acd671f64b9318d37a40c412a78f71c4fd8fecb8711f8854a2f2bf749639fc477a6410a1441bec53939bce351ac782c8249ef7ececf095d4764812253f6d72b1ca52562f12e9cf3a2a34d87b9e205197d612239fb289bdcef8104c2663ec8721f208742d8bdc4aef0a768420056598e1af01ff21418008e9518619fe0e80258838e2cab672939ec465762d8834c298e4728bb80a17452d106184c822441411864822d0272c5d6ff36ea8ad0c1144e0b3a3dbb63cffda2a994cc8e816f88f4fc164d2029143f823f4a5fce69c53a30ff876c08e30840c79214284ec080315336347193b02418818a2ea14e386cd79d494ac6432710a8814e2efe47219d583903f204288373767cb89cade53b3194206d169b7cb522ffa22bf92c94403228260e75fee424b5cdcb76432c1804820de689e297c9bfe83bc0f204300514e3b232b878fcba309c223081032ba051610f983c9b57b838a06952d974c263d10f14315bee5b49d2c9d491fc48220d2074f6655afcf39e533f109227c6863063b8b9f3f7d8e6640640fbf870adda093de5c62ffe1234d2661f80f1f1c984c7e00f191830d62c68ae8c14ef152744bfb523d1941240ffb08553e6efbe61af251d2e379e043080f44f080684bf2ee3fe71c174e266688dca12c716d172b8d9e731bd8c105113b2ce3a16cfd4d2566844104913aec796cdf44a793f7f9870a5620428735fb986daebcf177d9c00e316c20328774e8ed5ba91d59edca01cb15da262f55e99cc6a1505ff2457334abd881435f9d4e8fbee811577943baf623fff3fddd76c3d5697ac182fbe98c9588b4c1cfa5df3dab272d436c282bbed774fe2791de1a8e2a26436d12dd292f2c105143ad2953c4aea9f84cae40240d8d8d7effe4abe97ff30ff000801344d090c7351d1d43646e48c560400a44ce2062865544789bec4cb9ef922152867456d3b7ef5c173ea5c40c1132584ad5dbf46589ada964321922320611319c4be7bd8c8fec36a53054197d45d6a64ee9496040c830f522b3851fddbf806b9271476b0ab3197bc1dc21d2efb45d282e765cd2f143c449b9d0a5b0afbabbe1b1cb2da4a5a25c9e1cb560ca23abb1d4a7345316ca23c22a2e7a32113a58b037fc9dfa53f964cab90266f3417f6494d0dd0a59d6ef1c63f941d6a50a7bcec15b53863b530b151253b110a629542a9f9452b71e4624853b2be62b69d6c755251f68912894b3dcc7ced430425f3ec02250c07eb5f3e5cedc614d4f3873f509dde97b359e64fc900ff8107182497ace7cf254c5cd7b137451318b95ce36af2d133cbd1dc4f8a7762d511862882cc1caf8926aba77e39394c0cb855e139e4b7dcc93d025dd4f5d393354d5484057f2143f93f07d9b8f7057b07c79cf59cb424670e42f3b25bb8a3996481116f74bd91e7b22a43abe1ec4758c3df910dcb9955791bfb4d9161102326705954c188cb467aa393395e232070ccc2b4e544688a765bfa8845fb6e019470953f1c55e9d9626534ba954f1c0408e5e94831e9dc434c69cb2ce8bb2fb8f65eef82e32752aad546c73aba20b4f5aca986ed5844a5b181f48428e5c9c73f39250bebe41e5940cf92093890472e0a2a0e3667ea997d3fc1cb7287988db185247a954db8251f93388968b1d460ee4a88531fdf6aa06a995a5428bc3e71af7b81dd3a2b36094c7cb993d96811cb2388cfaab8bb14dc9dbb138a7d3dda675d2ae6c58a47eaa1b76324ea9f91557d4cf9dc66457dc3f6649462b5d27a3565c6e417a12f55873650e56fca172bf9ee6d81ec71cabe8e5b47f780cfaa9833954a1e8d254b3931a45a6409023158fd2aed06763d263ce818a1ca7486658ff74a663c5744a1ee00015e430458f1ca5f0c2c9a7f5dcf1417c39728c821323739299a76a352b0a7b73a7349fe692ec68280cc24789facceb9a41a038a6cf1de3e3f6d3743e71eb556758cab151533c51e8cca5f568fa3e5b8e4e1c3ec6bb6022637acbc1895484dabfa88c856f3ac8b18952fbbd8a86c9073934e167b3e49e2dcce3ef4ca437899a114268bf71cfc881897dfc4b9c0cba4c4f778974d9c68698b398efb343560342725802ab0f572bfdb7a5e2259ca312b5de7534b9693233c9851c94c862c8d0dd243f8327719687d8061173636b0e497417ccb25dce4a55758e48fc9e94d6de8dfb8a411e3920b1c51c32e7f09fb539e91149cb7a4a734e2dae7947a074c88ec13b8d308cb8f5f435fe59933918b1c5b10f93a93223c7220eed1daa3a844c1d838ab0ee4ec9aaf809428e44f4614d537c798c5e13229c75d1fa0f67e9af3e84363ae5fc4d77940719c2af2443e756aab32a7d90a310690995537611134a5e3baa043b8a043b6a043b4a043b2a0453c841881c837053adebd7b45be6680e411c72214ce8fd99b4d63fc06332b9408e4070362a6ab65fee51ef167200c22c2adbb9a75fc37639fe90e6d2a5a5b488d6ef73f801d1286acc2a53d2a51d21471f4cc9b4c673df6bd0b1070772f0c14a4bffeb9b99b762cec8b1074e0679f66f7283bc981de4d0437a1e4ee78cf7010ed8408e3c5c3ac39c8a0be5c0433979c8e47ae992dfe90ecac5d0bef1f73f45931d0c6e77ad7d3188ca7f1d0e1319a31f9f940a3d1dac52ef2da75fb4be9f032294c94ff51a843ac901f9d69dc53b3e35f538a45d77dae1b3568d1c0e5fbaa834a63cde65fe0674acec1fa782920d6a37a8ee9f534aa552da4db7c1313b1d93b8be0d5a66c3e17a1e45676d318fd79092af5416da1553c76a703e745f5e56b3e8a7415f538bedccf974b46860b53e835e13d22e65cfb055c9a0f16b3955b666487fae7ee4ef828a6b198ef29dad4b769d9ec8808fec91952c7d4af9183c53e97b21e7ea321243e6f1736f6d76cc0c0a03734acf6fb08ad115020342bc4365b6cecafe0b65fdcdd3bf17bcb3e423527b63b0bb60292183c8ccf441c75cb0b35b78d3e695179339b69050fa94f4d0e1736499430b25cdfa669d4f4cc498230bc9d1767e05437688901e6508e12103925fa45fa9b7ee44c92b55c964a283fff13b20f145fe3f428ff82c5bd1954c266690f4229dd32a465356af42487861659cbc683bd74e9b92c96417e56c11966eb7f5c62b994c74612657eb10cb92cb7a10925c3032263368d8b8286385dc5cb7f31e642593c9197f831e40486e81ac8ec83fcfb639555bf0a3d46bcae823b4a79041520b2f4c56c694537b5071c728630c125a1c4e89d04a99bd4eb36894ec327b51f9f44db26073cebe0fe71966203f7a1c92581cc6c63c2bbc3a4c47031258d4299ad21acbf4d3e7bca294e25a92c973bb5bee0624ae3866337efc926725632bce9bcffb4a93dce6838415d689d5c69cae17a75ec51fee549ccb1e3c66be0624aae863ceb6e96f94fc4f0b490576263f7a19a617747c8cf1437c94e8a0c790af012a90175ea3c3de5b74ff14a9911bfe7b722f35a6b893a9fc2dad9c43f5521cf649e5c6c8bfe74948f149f918645d4ab1d5d324a3e0ce554bbb9890a5531251383293bb9c0ea7f145a1d0f2e8dc32795b6e01194388183c3ac8052693fe7186102301c5273c41d209ac62a8a6d1963e5f6c40c209f3db890d5bca455320d9042ae97569ff1864b2701533333363668a128fc6114834d19ab6beec3947afee4c18c235c7390b3a27a11f437a9c116488084830719ad3ebf8bb1533e8128bedf79dc5bc6a2924b184dd9b17643cb541c37905924a5cef3185d85252e2f14f49a387559a7d54209944b6a562d2f461f126b7804412e65827938e25da2edd9168ece7531cd94c199912904002d9cb27f48cac0f267fc4166c6ef63f668b314b261347a41b3c94a874b249c48d2888f8cfb13c2ba90009230e95ab74636ff6a82f220df7db77d984089d5452468f1e2f24c896c1031245184a9fd06842e6dcfc44b4394686b1b03fca8337204144353245860ca3b7f13f44aaacd2855c9021d0ee212e8af2789014a2137b1bc467528d3e21b4cc26a7fbbe437f3b08d772564fcb9daee22711c423b4f8765797d6a8974c26814873d8d097f4a68f8f79200184b596e4eadbe898ae2b21f9c3493feb6258de58d5fc60d4525d72442a08923ea81fd463f5bfdefbf303091f4cbf5223e25a64db7e40e21dd307923d182ae8f896f7d6838d4a269333008005123d28d2532ea552d401923c2c63d164a7d84a261b20c1c35749a3f85a90cf620ab23f465f80e40e7a8a356ee9921c5db56432994cc24824763889509f2f16d73cab00491d5096a673f8e911212d2474702c2ba987bfb85c0ec91c4e17dad2c69295f97808247228c7346316db2a87d9e3901213966bb3458f3d81400207cc92c68d5a1fde6b44049237389b4506d778ca723e4740e206b4696e0a7fca64f5d700491b4c9ea14744ae6d2c6dc96472021236946b7de39d9554aa60c964226449d640a20663e998139694cea22b253ec628a38c1e2469d8bff35d5e8e3997a54b46408286afd2f383bad431a32fe4839c81adaa0f7a4fcfb9652593890a48cc50ea5c9923f43b969f246548dfe6f568563a950e954c2629202183593cb6598af8fc218f410d57a24b4b97305bc580a9d058f1bc29c77761488e902af69d03865d4f75f6f4de6542f3857bf448d176f2a976f142a7c38bda5dc8769275a15bf9e45b1ac66b54245c289c90e7a66b41e77ced286347e0dfc33f089021425c41b205e4e85eea1297a9c38f245a3846cf5ca7c37509adedd8000f922c686b49a58db733e2e41120c10232eea67c90b794f2e50524572836a9b5e9b8f8395d2593c90a48ac70cce9f62a46f38b1de3214286f820a9c2a2aa6df1e3e5de67a9c0b668788c6572d74d0a24537857f3e64be69d47d42452b882d2d59ebf331e7f219044814d5a6dbe5b992be441208102231e1af46763b8479f40e2844b7da9bb0cdb39772369c26b723d68a7f41f362461826b9d2fda99c7269d6740b2843c9aae96cd24a4280448194320510227e4e8144d53a9b09533820c190204080a4892b096272d3a3b28d57f3a020912ec70c9ee3bede9391523901ce114db5a29a66869ca43628454aed374b1542445f077dea366d1b4d2370911f2b49027f4544886a076ce569531e22ffd3b7800493f488490a5d27cc1e36a90bf1923c1a8bd5456bb7c615793c070adb783c5c818464e43467e915f095569e16916eb2023beb0638c9673c7f04a0f2a61a417a8290d2abd92281dba115e64ff7deacf3ac87c590f23bbb82c3654a79ce3a1426118d145aa3ce7ccd233727bf4000fece8c00e0eecd8c00e0decc8c00e0cecb88003782417c9fa8959cd2978698f0b3d7b94cb7876e104466e91ba3d4fca2a9ccb6f0b7367f9f4e5c9635c3b08901e41fe870e3830528b2a5b527d23c5e28cb4408aaf56ee101eae2b646c902f038806466661d021ea1d833e657ef6604416e557d61deb5032e7782cfcf5f3b8974c4f285d8f238cc0e24ef71cebf2a86432711f3dcee01e6108791f41ca10e2011e4376b0819157b4759ba2b58d97b0f8013b187185418c079974bde898a5156ec56e7eb611964f25638415b96ec9db1efd195f16a30723abc87ac54bc5bc8fd7255574ddd14e6777dae753a1a7f4e12b5a0c9b3e0b8311545c4957e86d0b3d19738ad22c8bef88bd541a9ba220c5525bffc89d61ec60a414a41819c52153cac9ee83d094543dce0832648c115198a1a9832515c4c51187c298b139d6ea6a5f0e0245999eca643a3d9ef33180a492914ff01e7f7f723725991bf4970019f1c4ad4988507b163f68071bd821ab011e658c74a20f624145ace7442bdb964e9a76b6106dc28c37552a9a674f21d484dfa5fc54bcd8a6b29e0984ca68aad358e81d31f187b473cbd195fdf0125acaaf1565a39fd62c516d8d30213ca9a8d755c28b576a3c090b25387dfea3c64b291b27c1265319fbfd5c0f329228e913111715eb31120963e9d8b134a77bfc483dc810b23ae8d103c8100a8c4002a5740eabe13ae792f7115fd9e80c19b9d16b1c71049aa49fa8f035ee9b32d28836f454d79c87ab6f8411c8daa0ba63a81925db45147adfef57d4d24448112779f173a60bba1d8489b075c36d5fd6b64b0b042388c8d2eaccc29d38307208f69272598f1196a98f18a28fa2a1528bdb8fde8530c91873328397dc5b4708f1767b5f902d7b536d498f2043ca00126432f9f631869032320854fefc6a5a172d9f8d08a2c748203e25c4d346536e513a648605460031f287113f142e3d8fd231c6e0f519e9032a9b8a29f699cbf93dc207f4a8d22ad9541f5333b287b3838de56ab1f7d0f550f08c257ea14dc7d8d860240f57da0aa71dc3964c1b0f272d25fb3d736c71f30e851ef5a3eed5a48b8c1d8e26336992f696aeabc1481db4d04d666ee21f2374b8fa343f656f14d5cc0d46e66052296d6d9cb6d63ec718914325555493a74b1c125ea355f474fa8b958f1138d4c14d78ec718fdda737ac61a3e7d3b2fbeda00c46dc60ea905a0e46da605a86eb9884d0fe16f38611369c3ed5f4ee67d3c35b43665f3288b27d0c236a68aed62c973c133a08d3d0b8a85c2d3a25640c2368e074522a7e051d9634e761183943b2ef2733303ae5301977ebc2481950ea2ea8a6ccef65bd1746c8b09de896d410a12a8b5b181903aaa12afae8bb8d2627c10e0ff088c28818b85c3a4be7858dfba32a8c8421b5ba9e9f395c722085113054a22f262ee67a886587183cc8001284037c18f9423aa5e77dcd315e1af142e1f38bac18eb2eb863f595644b2e94b2c35a4e7c5bf04b25ab3c95a285745b34b19fb3ccc5580646b2b08b7a6cdf2acbd4da081646ae60cae39f937656ae8a46acb0689cdacf31e5d23ca7305205e39ebe119ff3a624f3c20815acf5ee0b6795bb93bc44062353487d33e77e56af16e487f8c8c18814bc1e2ff5174da7bcf551a2831e42cef81bf40032c4c74814d05bf5b1c3b22bc7a1f06919957b6dcdebf2849355d0933b9f3c68b764c40995785f67f8352dea197f831f658c34010fb355592e7d0af708132a29e627f4ba7c776c09cf79ec10db61efd2864794309284f3cbe9936fbd2ba352428311242c3f2a8cfaf7e8b9c42347505574d4654ba8aac78411231493b20ebf3e8d2bfa4811ce9aa5b16a3e4430880d39f7494b5959468680ae24445cc53b85ed478480c90dc2e4db9d8abd3ea02318a7cf6646cda3926de6073a80c169ca5be1e1d3f10b84cb7d144fa6935a42cc701dbee05dc4a59c1a751eeb92c944472fb8149ea2f75690cdb36432c1840e5ebc36fe1b72f363f0fe18bc96d0b18b4cceab75909949d6583299e8a2ad187784a81cecd25e32990442472e30751ebae3e7d8418ed0810b83fd767e87f1f0fbf9c00e1db7c863bc7865b51d948cd0618bd72a467bce3f95692aa9052a73d4246a9a2e3d5932998cc11f984c522274d042043a66616cf69777e8f862ba92c9840a3a6461484b966144cd873a8805e7e9cb45e8a01227e8804541dde87ca63673e8d274bc22f1a6f38dc79b0c177685962a7bd2e95a2ac55a32993c41472bceaf7cc2bf4e631e53c96402051dac488be5307a4c556e8a4a26930c940cf1c10221638c71811d3b767c0e7c3441c72a3cd576b249f84fd0a18ac4ae5355d83e3d0a32b0c3033b52a0041da930bc29a5415cfe4a874dd0818afe4b9a08711fbda49a041da7f0d32fb77812e273ca01a0083a4cc185133bfa442fc5d6525cda4a78e6d266959102cb98b36579df4c0641c728ec1483c96ad031429580a0431466d518663bdeb55a0cc59bff37c510fb1916fc810e502c6b39938ab3993e4e0ff048018f0df040c727ac9c5b55d53f33cab0800e4f946385876f6749d0d109a3a7258d9af45a3ed4c109636f5c934178caf96926e8d8c4c9322815b5582f77d1446a2bac7fbff42f9632e1792c2fdf24d23fac984875349529c9ae8f115de2d5fc4186ceb6e898b104a72fa771755dcd1dae046f4ae8902b3552944d894f9ff876caf96fa46612ad7c4c39269ea3d2b983ffd1a3063f7c9ce124d021093f6c09038e4da824f1823161180a0542815010000083a42e0d00c31208203054288c848201b16c9bf6001400003c3422524e282a2c1c1a181289c3c16038180c8602c2501810088643e25048244c444d991e01ed3860bbdd06ef3dcb1caa0c37ef8f250aa03ed448c1e2bdf10355e3d778d1345066d889f28f16b370526743d09735b57bf51bd1a012020a30a8bad8701e3e1c7856c639ec88abcd1bd9d72c73715de090fe9151297cbe8b72dd3ef1a807453bec9191807a70c5f96c3d32fed108aac9345bb0e3b339b683f30cffb9f43d0e3f2a918f41c6d9f8c4ab4393bbd495d079a95d3ca07746bd6672e65711b3d64ebb267755084f85ae96e6b093505bca3a252c8fc57edc202bc8179bf9c9babc4a33dfbe3275f76ae71c79f5461e6a6bdd14f2bc75c0a42a27e5b208600cd232410d718dc413f2a0216b8c3317a3dd94e0f8128c44e1828c803337b5ca4deae7e99076f966a1b204e7eea0a414f3efc09b236a880b3f437144294989609487f0f0065c3d017719a282b9569d975a31c2b7d347027ab53b16d4972dd82aabb96966ea2ecc99be442a0b57ba62b2565c8656e0651203dc6f66a3fd465c2f35ab1ef556dd53a3cc3b276e52d5ad9820d96e93dc0eb39c7b0c985954eb7d07e61e7b6fa6b2554c016aba076f4e1a771297b826282b44e6f5740c79e6820c9252d0a28f3cee2f1e7a1e668fb19aecb999fb6b1c38bafab486bcb0b3ecc1ec96d7a75392cb86846b6c5046f7bf2b2c04613865238b8e51e8d7bc6cb982324e01fcd7e7b4c60bfdf9a4da8afd24abc67310827cc3c8226ae6bc14c1043d577aded3071c0b9beb29eec4dc028b1fe0c1ab4e2e837197d44e31f77b1800bc1d8ddc4ae4f8da3d3926946852d0fb861bc0c4e97053336fb488a9711cb3136c12966835738ecfbbcb897b0cdad162cdf0fb5b643b37c2e750083f0ffc1bc5d95d8b68b2c44d7e209215074497801f09bcedef74e3c683d6546bd38ae9327fe74bbf9676313b0b0eb6185af5e54a50ba633c8e1e974fa9cd107a1601585c591487789cf07389b836b81f65464e5a97eb303a6d8ad16215cc08004922ba0de57bd0bbfdda41c633e3b955edfc91d96e7469e0da73516b6e04f91688778fddd7fe117ad761e3b0670632eebc7e7823d8ab6958bbcc96d816f02bd3a02524510c646dd7782ca334dd3907ececaa3a33c20b1ef00c41d000440b09549db74a688cc15626a588e243820571e340a37f3a64a9fe4eaed54d6d3eb582759f266268034a8e300000e716cf83bb09e0b0dcd2cb1d3109775a6ca6fe7bc8a5beb97037775efd73814603ae09330f3b7622034a33942ce78e178ac50c118e1860913c3df9cb7a9f2c14043bcb2d38aad2106192fad710e6dd6eb8116cd5f9dcfe71fc18e3fdc420b8606d7b9c543070261fe6afa63f6b6703e61f57d0b1e4e3d289845e8ac24063ebd90b8d18bcd040909e65eadab6a44019928ce04d47e8804bbb5b2c4e792f4a4a9939ee12eca478ca58bc49da3174e1be06181a775ac78f0e4b0166864594f503cbc8b7a09b59518ba3d207477871f258020eddcb064ddd5baba3a44a9031f34b3253ed5b6479b2151b0974a00ddc031af55e38530f7891555b1352c22c231b35a8e60f7ff6d87ec00fdd1ab6032aedc1a80e88c216218a9a972ad6bd466a6c027923b823465d2a50ac5984b414e92dc32fe6613b82fbb2bb89cf1805d64d5e1be5e6417ddc1cf3ddedb5e637a296ce7b780380765f42679c61bf01df0ef2ff03ae875fc4ccfc65e0f5dddcda39fb2225fbbc1134a056de2d0ee826308c8af7c79db407c60604b51a3fb5edb130d3f3fea6ae3e625cd0b01dfd24d05ad6be709abde01a4c344a6015a1605e91efa600096cdbaf60a4b3528a97fa241288a6c4b5eecac5fc3ceec5908055f0afccaa61549b26087e44d2b32bc2a4d94c1961e6d1db7bac60611826215af6c22248585205331d0aa9619db503032c7ec84bfb6c174cf5dcccd4d65a7e8e208419f22fa94c403ba2e23b890a7c635dfd366e4edabf4aae19c22b8f30c81e8ae2f40e4667903ca902ffeebc83e2b3438d511653355567eeab81736d343d4459197f07fabf380900b51471f3a8bf50331ed8e517b424e2793634bf63de36c497a01e6a79e53c143edf2be43e5e71e6f3c655f19f4e8fa20eb9cac0538acb9da2e54b45a148d463efc9bd8c8c5ccebff328adb95ad9b532747f156d4c80cbd42cf3a55e546a2bc3a1eb6ba4bf8b1c06b122e657acfd97b79001754b832011ee4814f5423cb8d9b9b7469d29928a016ce5ae117e04b23ac51975b05f1f575b241bc61cbbd017d3df6f42125fe1049a8f72cb77046b92898becd9334ae597a7667167323a0abf120564fe81471a7aac4b24ad80adf33d92da14b608df61b4626fab4f1cec618a81cd10c6b82853fe52b37efb8ae24195bdd6c5970d3d99acbf6488e3cb529e7caaee9c424249132ad0183d163595e19c07b7a4a6dc913bfbeb67fe34881a28467e0ecdcfc17069364e072bee095d044a83191849244b862eb81b4548f91f7b21c4887efd81d47574475e0b52731020f4005220bf2d9dd4e85de1ee688a0fc753b47fa1fa850f1fa62c14053ca86949c8269c2d067b9ce650d848dcacb65160edb227eea3529fab78cf81ae1e8ad9926229f7065eb6c5ed4be9c26d919b479642429381eadb3eeb80e504465eeac306948aeff40020957f0ccd852aa6d73778d83cc6512976565ce9db2aa07ccc795ca8f056c9286c8a875b9e26ca46565f243515462d306b73c2b8944660835ba7484c697b61923537d922aa1dc041d7a96994ce7a0595d334112ca8b833e5cd09602e9edac8ac500291ca6a2986076f228292f214c2873a613155c2bead641fedd6f428fb41588b16a621f4e8f7eec060fb1f1cbe19f5b846cfd48d921a452d6cb05b88a022be56f2ab68f95e4290e34ac2e7a0538eccfd3ac73bbd20a0cafda8ca7d3172dd723f8da3f3e40bcce2abadf6fde9d3d299bd3e0a9deda691486eb22e5cdaf43276f91cb734ea0149f72c409d7049efd88e029090a0f4197a865e2cb1ab6d46734c196c07cd468100c1882ec21903620c47ae3560a3553541ca5245dc95f5f014ffedeb680e4141c53f8c191664d23bbb2fba152501982999788a4a474109a3b011ab06a764780722e356322fb4019d4406b0ea1546434ea8bc0f3730049f971caa2f812b050f1863cd4a3a3fdd8959d5c27e999e8f1886556f4210e29f9b52571321a5930dbc533b44e5816c46243113824894d2bd05edb368df39172a310e24826e7c6c851b5c72d6a5ea935c3321661952078d550ee92a31ffd00a8761039ab8940b54f4cae7af362fe85d100af44bcae49d6bc1eff472fa1af0041b649d99ba64440a700930009d0a29f7a73103a41ee78000037117202682555eb20118df36b4d1004c624d1baabc4550c05d1f2b79313b9c8485b2b3297e3d0c9da09f763e8a410356ad40521f28b1c287b6cf5a08884ee65e1e4374f0aeee5d4b42513c5fd3939fa755240901f85a9bfbab27f72a8c0148b7ab98a68348ead0327967594ab57d442d558e30d088711aa2352e8dc1b3d53033ae62a68d7f9d52b8edcd04f29757a654ed7385844db2fe35f525887c2fc6c691a3ecf4202423f6af81962a9a42374271f866d9320c342fd2781159f7f869c3980e84d636fc1ca0e33a501327636557e76221d0cd47e44416e26aeb3c9e4e9430f329d8413a59af77d0929d26ffe05ed79925c083b967b787c1feb0ddbfe1b9218ead32e00de0be25236088f71b910631191ada584745fa445dfb9c9697b080e99e6a83b1a77d6d92bb8a8922344fc1ee80a558ec29448357e4ab9099d79386a53afc3a3205608dc41ad05ef84dbd6067e2047ef21ea43224945645a4caadb449c2e592c4689d2489808a08f59af975c01b505dcc67aa63c7c0d251059a53635b91fba514cbc6e925a6229d71fe8b5307fc72f690f0d1be2e16fbcd3d9dbd4ea770b58d41f03b6edbb894d4734e28a5519cd56f35ff747e3c837c496b12f6a2969da21656ef41e6e2a56e340bebbc301301e9b644848b06720fb85705a00b595d3710d42e0734d63040510f500ac700aab6031d006c2475a23e3da79efdff83d7798c24f095f32d629d2078c34664395e3920e06eb73a01efce2403315610a18ea27a467b860e937bc6751c334fa261a8a781b0292a7e2d48ec08059f72708d0a14b786e0aa55ed30ae7fa23099d12a94df8475a0d2dda7eedbed2e6359d972d6cb366f7371a938fcf1e28f2c96e455cb3f72731e805831a711223573f4756afb6ac7f6190085324a11e1f6d5a6ffee605e34b353a1f234ac4d86466e01f51049081f85c3aeef073916f82e40a411834f6418532caf91d5dfe79f47d91eff57426656adc98a38412e2e9d6e7d31b9aa4077ed704e768027c913ce9b24bc6b6f6616fb0d12bbc7cb59f5049c50bde58f42b304b271bc054a53613571f22f22bf19df66dd74d7074ebda9025f241eb72763b7b8eab89efcf9612447c2ff04ca42fa849896e4ccf58e11616546f7ad39d9f40e8d64aef3a94d9b26542b1630aa6aac0e13e810c4dee21d892a796ae88a65131c95318f7a87cc37ab87b2bb720490af143cb173d8954db48b4adabb199186be6f20e4f95d62c835ea478a4a34e7f502827dd111a957ab46e86be0f83d69d5b8be5b4b09a7899d699794f1897af3148bffccb8a190077747f222ff7268c93cecd7205e86390314c8ecd72d63fcc59066c9746bdfb70097aa765a0574f4924e9be6965a05e42d3daa5d39bbf52eded864c37bac8be0fdfc1dc915a3cd49030abe728a03279eccc612473bb13e3198987004d5f871433623f12cde1cdae6a975e65afda244ba99a222fc579eb2a0c5e48c9d64ccdf43a1f2fc77eca4c218aac56a249c59cd05abac4fa09dfaac39cdaddf48804efa3adb3ec67e9b577a52f63e9a8907378b8f4fe191da17715a758a554f2f11eed6a148c6a6325dbad70b44cdb6c62d500a27718793c49c03685d90a5ca6928bef6c0968e1d9905bf8aa259e1581a2814d22e29bd42016ab52f295305e9d079f4f65b6e8299c7432b37b35a244cd52467eddc653f8675fafa7ad98e32729e570640d247e796877f011b7ed3a0fe0d8a38b7195b78b026f6a47a759aa7c87315f5a20f6c58a555b271de67a89258680831690e5c242cf24edf0a1f2fc82385b09176c28be7a2623cdf3fe45e4baee4f23b242881c719ed2fab73c249154f114407c0e9073423aae4ce0d15d61594a005ff129d2936dd35195fe53e85f2452120d2370144813ca36adcbc60065edba5d26e66095de1b5a386c914d4501fa8b52d1a90529958a1350c5646cdfbeee01e1823285d4008fbf7fad20902ff92fc8f4d9403e2ef2a2dab23b98628b8654c642a6c0199b864df10d743fe54c07257d61da42124c946f7c270d21cfe9dca292b755a117b2332a3ca6c47ef4021b2d2bb6871577e9bd4f96bf2d357841a817bab2edf093188d07ce8a71ed60bd346243ca6e320cf702833747bc6f3c8f6c90cefb6b610786200241297fa419a3969d26598332817100dd0612444a8b9ef1ae22a77519436e5e63cbf9c4ae18233152fdf8ba0a6c46f0e47430de6e423959a336d687e7e9e503d9283b3d9a6fd2ec872b336019b0d1afe3c3fcf2fe742f99072b3fbb6674abaff083586b246a7ff16b0151124c866886730c4eb9cd956605621f37b65b1b942ac626836f454c18779cd80fe087a646101278764b714f72741b8357bf2b7133a380f3dec9cd309441b8a048e3cac19ce3983a1146a3845aa894c5b0f8ce01ca7418b25c36fbc865fbd5ea3ef5d3a3eb55ed5c87511207f56a43672e9e85aa9700def4c97cec9a4f9c28f7f94097d316f5f0b604b6445142d8d6a220371c318bffe25020622c223cf66645ab3d4eda710a31eedc4672bb8e81298360179b127bcced23287a32dac765c4bd381e893ec3836426fbf11b2f3ddef13523f21481b71e3a2bbdec768769da696c6f89c2eded81453fa47f8d7825feac90ca30a5d64b63bd9f29ab5a27d0197c14a7bd50ef3d357aad8304470be45e6fb32048fb230ddd1b56a72d1769b08fc40631ec39ab8c3cc4556944b3a152b03f0f799471918934fd1c38a8138b1f6c9e13d8cefac8373cbaad214d6dd9bb3770ff23350527b281bb99d85e6499014c4ed9b0d5944a1e1bb77cf72e904702132841b23b35a063146c328d0e865d9d5799530dda5a092942855f42de3314f6bb83a0edf1164e829ba1f098ec899e8b742d4dd0c9c53d393aee48553f5976241f13f5e4c467dc1cdf6a129c99fe9896f1d14b83d37d930c8bdb3b1392529005bed3ac876f8e5c1285ce097038edb0902a6779f5f28c23d8a6450c899dbcf63b3d713f19315e27c24fdf4a7c669cc9e2fb149ebe08e26b4ed3d5ce44613b12042979688af75dbb18b600727b875f0aa96fcbdb2254a25b2ded00af64275a045338606d9fd8829a7b9e3a4652b8b2c4761b05c2a5feba7eaabd9e526bac58b1fbfee24c06ef3de7dba2dabd6f50338bbef06d7ca49319d914e37210bc2173dc4e3972323adb30de07e4dd00fc7f7fcba67623167a191019f40010b787351c351a91a2a2d1a70f134e12bd3e72d77874d5a02a7dd619aa18114ba592b00995c9aa631733a01a04ab942871608c189d3476e85d6b1e3486a71ab1c59ae21a9c1f2524ed65b70953b2acdfc818b2a4c443a614b6a04aa2743b90de4697ca21d5a30d16dd23fb9c063b2b30d050686f305a166ba4f3452f36c5917de0241132d3dbeec2a3b0840d4a47f28d8de93d6d1de2325e123eaa7b7e657f3d47cd7dfe1da597f531ce101806fe44d329309a74f0a4eebdf5ec61beb41666390ed3ddfb6ab2df1c980e89b71474d121a7963f975e4f339a8a9dac6cd8c176e3f4dbc0933d2870cdee40d6cb7098ecd0b776780ed6f1d59dedfb689c45df79c81ae74b153996dbcb7972c17cd1197ecd3969cefb839ff1e0ca3d304a6339f900e9d3d0f3490328055800773eb160cc96f939568295a91ed48dc4b60f419c14731a9a148c7b95b27bb28952be432de8fb1b171e072958b9dac690be2622cad893f6b3e3c374796f69f22763f8c6f175cbf931feb5da11f3421b30e0ca0a8f12e5a6b46e5bf9f98fd3c6ed370d79ef52efa9b8f21450802168e44cabd964dedd4610af4d4aed1ad9b204f4d779e4531738ef879eeb9842756496a7222cc972eba9c456064a3d5de335fef30a75ae90d8069ca6c89f4cf16ecc00a5b93a977e163001503dc33da124eebf12a5b08f805bbcfaf010e564a8f557cdac2dd644db69051330b216bc13a165652bea5c9c4bac4049244fe5c0af541ead77dfd3c631003ef86a69e957c4ba3e3ae3043ca4eb621ab236c99c5f337f26bdf1764bf045f5cfd4c2a62b92f26a1823825e1e90b2df364238f1719bc087f4773f2fcc1293d0d133c66758abaee5a8dba4751f10070fb886110d46fc16899cddd31b7b291e71d7d5bda7ee944f95cb6d00c759a99ebd4d6e0bb85c8d520aaed17de94b8d98f72681bcf9e6cadb941d6232f742889d839a1b0dffe41f3a3364083f7a9b9ac9a5fbe3200d0ef8435d9030581d2e1ef973cc2921802cbb3c19850cff7ccb381a48b75333a10576d939f35be902e5a07e632bea3dfda77966d9447dcda77fe6d6c47c2b5f2cc6d7c47b7692be1c1bff6d2978e8b769f521984358245e32e71daabd96b55fbe9284dd0573d4c97106f3fa614a2f4b98e9f941b2eaffb81fc0bdc9249db961106f6022d9f52d79c2b58dda5b8fbfb7226865e1f8e30db23654969db1073cd535022811dcdc148f2cd07ce85530390e825e80b9a2fb2d6b9e268a6244faaa62025a306bfc8e65c2b6cc9d0d0aeb8bc94e7763af7fe4b9b5c956b3825833039862127a094e26c99d9fca100dc5dbd034689e7e710e31313b5c147cdc6dfdc1efb1e7cd61487fb0d458a1e6e530bd0262dadc04e17ce7482f9e93a8c5b6b895a09308dd396c8d894f51944ee968409e9fbcd177c62923eeeeaab2dbd34420f488f2be87ab94d11db624e7fc85405439c4afbfbec0ebaf34c7028bb544f1e9c340371508bfd0a628524676e214db7126c8e951ff650a5afe08a421b4b67b43a09c3ea9c18f1d9a903fc8532290821541296becce589cb4d6624e08ab9d5840a3319047d6ae3aa884cf16f6840608c047ad44032e18917c66e15c25054dfbeb7e17e3a4f6ec67ab43c56495103bcfd9e5823e15c7105e1ab8040cd848a27fb5cb188071af2a82ef70bd3f27b72df3cc66c395fc878e7c5b23ab9c5bc318739696b659c0a0652903d331b68e89ca18e105d2841b00152ea422f26b7d24941c910902bd05adec09ce17a86408ea74db23db75c8a079d92838087a7d074fd92b646d9a07b7174ba84c78603054a8687ecd75a9c955101f56f026cb9d0eb44d9688571199578188bf5f5e9095c4fa7c23f312acc76ceafc8667da9388e38af04dc80365e4a0d0e6cc734e68d78a26f081c9476edbe065b24b5eb2d677bb365366b0a2677dfcfd157fa98a57aafa0d3640529cb5c3fd40fe46fe107f7973fb8bffe83ff0b3fb8bffcc1fdf55ff07cd2ab20f50607df0e36fc8b181af187e9cdf8c6bfec0fc15b709b30ba8d7c66ba1337292900dc458d664e6f6951ab718bebca656b48c9fb12fa2eec02ddf4394541781cfaea000aaa472d263f8535d174e6607472e341700c4329be704ac017886266e0225a56c14432a31ff7f055fb0104d61601fa0ff4042a5ba5a0cbe38da188860d3f1139b85959080a2348edd1c00db598ed484a9d896b00d3a2a880ea040385f67e7f5a209d07c4432ce55f49d37c541fa1517e1811f0571140ab05ea37dcd5948e68b841263d4f50f5bc18272e577594160570ae88984c65f5aeee0514e08b687a1d9149efc25b16d4e4f2ba876a0dcb946721b28bb83e3b1cfadef59fcd0ff9eff475f8ef3f2eaf2336163766095a95b65d54847b42c7ed7d637bb67af886a002aaecb902ed8950bbb538d9fa0fc6fc8245a9098fd68cab92781a391c587a15388b3b92efb63c06a8b13e0a4ab02daf3ac1b8d325a1b76f589a4ff1faf620d5c9e18dce331107d4ee1c39d7c19b98c1199fa004231984721078ec9a0946cd26f00f434ae50c288bb7b86d1cf218f082d2f9a70153bac858448d6a521c3a7346c3e70bb8761fa5d3d0556c766fc3407b42f584b829b9bcfd54fa5aabc4135ee0892977fda2b377a1fb3e446fa35dc01fafa74c86cd598cd15ed6ac0049e3ec5005d2e84f7d6dd741cf9cd0b730b1287120236019db66226d4d6126a26115a069383e251c86384fc13ec0fb6e15efde32d324abfb8af3b1100324f531b6428d9038c9b5f2049363dfde74e7c2cd0804857121079492a7ad96c7aad0b7d98e60b662601e5cc60cd27352aa2f9952ca4ea46f6161ea4ac7b36b08341a43bc9f88bbcfca2a9cb706be19826d5ffe5c40151424e7e7b9327f8ee0b2336983f160bc21296a49903a065fe39a044f2b4d9e188bb3840d341d2de86d398381fe90ebcbf4a84082f058064251174e31d1c4f5e675743be09d63a763a6129065ae9324551f41bec81c6165cb644d36dab263f70acf1e6d78680f426a6cd1c866b3aca86d87914612316f30de58aac82866453cf7621632e71103f4840765188ad90e0f3037703d9300aacc7e1f3bb4bd0672b765aa2daa80a85ca76688b84d2b48c6fd5cae37c3fc004f7397723822a139ba06daf6acc6c79b1e3fb842fc9d318b22b700f9ef65ff2da8f6c47668a7ee13b58d6ff01783d570be02e9e33af14315e47ef4441b5758d3824ea7dd73963fb62bb8297da841e7ae54e962d54b00eef65a755ecf3408b6b6d6986c2fa6980bb8c8b9284a2424137cde447540406ec611080095885e9b1cdc6bc9b74fddf7b40b7eff5c38e8bb15dff4fc497348ae4f1dce078c4de911bbdbf04b5517ff2fc791ea2dae97e1afa17aaf190c5a8ace845ff5195253df17cc76726d0296551cd72cbd326c3d350518da7ad56167d938ed366955b2ea1fb58d6d9358d175f1258a96815e93bfa1ca40159df99b6c736bd4f3ef015a4d9e82332f72340346550cee10536d75a08849fe7e02a44603a8f0f49bcfd0ba7fdc133f2fb778615ef884bda137cc0d73614ef8087ba17f980ff3c33cc2b750d8197ac3bc3013e6866fd81bfa14664f0fe39bfb56f45e711b1be62685a0530fa4e985ea012618c89d72f81039df87c5f91777de902779a736f357af45939c6cd0cc0a5fe04b9ad69bd55df3e971f6b214b930a26d064b230d44e321ae4c392990def03a1619cc11ab96462e0e1af82543a5c38447b2f8b85ad03a9d3c8d9102da0ceda6abe20e61c69b0a0e589648ae85ee14d5d516303d30ab394517d035319a9ebf892ca5be765952c52ad053b3f6e645fdd86927bc5391fb35a6c2ca1caaac9b06869d454d41679a25334d2c94d0499d940c2598e1b0230b85982821c81a5d55dc798d041004a8dcb95d57d378444ad5afd5e3b1dd77a8136c4f353001daa46374077d6b1f9759513e60384d2291cf905455ede6508abe86116266727bc094f65d606a5e7990eb01de072a4a0c2351d46482e3998e9448fbab2f6c993f88ca931e90719e9b2883a7a594c86f3ec588c1fdff024f411059c4cc0ad17efdd091c8aff2938ad758be05837e1bfa1faac649b3a0c32b0c2678088a90d3ac609f5e2cb1c4689a101e4662cc1436dd3beeb279cafa724fcc2c65efd221529e58e3eec00d5c6951edeb781c6a3cae2483171d07c1be9920e142f9d74ce5466ffa2f5bf531cfc6fc2f809e1ec3a6e8ac26bcadbd5c2ff971dcbaf7198c7a419e8d063619601c0b688e1945c2d58b8e57bb9217dccd388d43a1c8f5dddf223550aa18c3c16225f5d4d16ca1ddb99e431e933bd5911dd4b69c80181ffa94e8b1bff73350134603ce13840103ede52db660a32bcc4e7185d5d33989ecc218a8c0947e167c0e20419227d1c378ba51f7fe88784ffe117745dd7dceba0af2e8a418a111c4ee19d24280287102956e4854ad99e9c60550c08cad8839e4928bb3649b72fe9f0204e9cc209e9966b15a862af27b2388288aaa084dd8ba78f3e35c32995afa11b7daab27e87ec47a09e7ca3104104b9660b6f6d35f554821bbb1adfd4fe2aa459255a0bf92ddf8ad64bd9b68f45e180b8ed8c0858e02d38c714e2f67031972516b174616fe8d22878436599e915119960d07d014edd60ef5df677b3622a174e090a059847669ec374c1e0f1b4e1979a086585395a00bfb982251bbb05aa1b40e5a3ef23a40b406e350feee18a67a9036d58285246e95be21b720cb748d38a318f08f2c31d8c6f04ec0cec2e6b25a892a6f114985d0b375553685465e3d59b34764bea92aa2a271092096c732f168b5a113bcd334c5ba7880154dfd422c2182d3803d39e3749d079420a4ce3d2fa33fd9b717bb5355fb8e24cbc5726deb2c0f085a96c5ae93f1fae0dddf7910427377433409f07157b113d81958ba3fb53fc0709497aa9a8ab8b34b87fa698a7059040e3cc0f1e67381c32c508d48c0f0248378d558dacd5b71d9d411af79f372d7c5f6c6af3104a0e03018051e519f8b22b9b3b3004b902b80a6c80191bd2164dd3b9586e465dc77d05fc343efdb05f25b323870d1cd2d9f463118380fff790c3ffcb1580626442769827ac9839ea416e78a01a2bf2521b0f7e17d4419fc32967fc27d81f9465458423d4a08a24ac53a011aa5d233504fbc19496ff3a8bf08a31508c06883211ba9f407f711d586fbafc94c6894227fde1f17128587618c542d9d480be3b1cae6b3741bcac73d6b57f421c5d7493a66a47bebd3dae05dee8f5eaaefd2f55dfafed27b2cad7fe9f796d6778917fa34b770430dcb11cb096dfe66be3bef09dad2471b4dde70c832c01d12176b6cd084d1e59c68b49e14c08d00a0ebec9c2afd04f355efd247c08c40f2a087557e3b7e82e17a71b591cdf78c89d134b9aae5128649cc583f95afe20843858710889bebc5ecb797b2c7f631d02e9b872123a512910b3d6caa507b69e46a749afb99a806b766842d3485e70b8f567d6b08345e6b09fc09e2f8b3f4b7ff8437e294bc14c8ca72dcd5e1d4189f5fceabe79bf3ab7cac3c8ac083a3178b5a8ad9dad0ccddf139fa10fd4e4f977bce118336cd2a7cc6561bc0fb59229e83119a433d2dc7e314f2bc6df2c4313798f0b682f50313f2be00e9e4857976514bad65acc7e57267063fdb9846ddfd39d167264c385e6b1b10564d0cff07e1dc26b3f074a48ea38e0e8826aa5a704d8bec4145843730ccbe9288f01e61e08f7337d6abaeac37b840a20804d112a8feca2c83e1889b321085f31caefc3f64c41ef9581d9dfd5a302caf0dc4867791b22589be16cfcd3ea0f8c737a76cfb87e20b1f00ba9c590a61954c081eecfca0112680b78632d96380083e4c989e579149f46e58f2490710acf7010a559a9f14ac1ed9a64b98929b11fdb092cc750d7c096e79572acaeb9466b89adf283d65544a390f1055cce912ae98875519c518044944219f851a7a51d3be054b5dbd30ce35f7f594e1d627c4245f7e58b5ae7cb787d43c681a110f4cd7c487eba9356f81b183d0f53e6acaea1a88af857340c50c8b0ac9369bea20b61bee565de6ede1602a5aff6aa40beb03cf36c92ce0849f18c811a93d388c5bbfaeb49a1bd8e8dc91cec8919a3430f1c0ae0722860725e6e315a33544fa69b4785145bcdec343e6a7f5b5d12bb8224af7220d68a5183fcdd234a47740da13ec234ed706ed6ed25b67ce6e54e70d75659f44421c4d93a1cb72b8f34444a0125b9f9624e0db00903e59de499f6a70fa34b8c38990cf53718d4c498c8c6d1b22a23d96f01d97074fa058411b5515efd40e2c6889944f86b6371e7f82903ea3b39437abca57d874f4fc01834a7cb40612fe85c58f6373b2a46da0cbeed3ae2efc645e7f0386cde534b46c23579ff3e6792e363ddf2a4edcd6cad61eb9dd6f27505c2e9313139ad60e338869b8210e6ba04821bfa12ad0159b879f9f67f813d960ad3369ec44cda8dd0c4fbb64e800f30dfb4cf5c5c16840e05d552498fed2c9aeca5dcac65e3090bfedb475c8b16b3c777505229b1ec24835752faaa0bfa94ff8517fbe18836adec487ceb443fb47b1120e26434f7557bc0082ceeaf7bc679de70fc98eed29bf461b46a33c891f8d10566076f14847c22e3f6a0f0b8130917549038e932905b300ec4728fc0c21c02d8bcc116b47c561f5a0ec58dad09f621faedfaca55d55d966329641d86ceeb8955273324fee8d29b71c188b8ec1090daa5d79eafbbf469edb52c2a6eaa6625998631ea9ecedfece0955f2d7b8fcb2cbf76d9f91a10072658716424fd51425a80adc8e535dad4e653f8a56cc8bb2c42f0fbdaffa89ba39c8f6e2302431a14a4781c92a181a5dfa8dfbb7ef77877790d9c7cdc1576486fe18ebb35a65c33307be6732181f2d3d857088b29620cd93b1a8e3221c1a0f5d5738c2a704924c2038a5a250417e12a90cf6fc21b6fea56b787fabdebbcaff6e5c8356ea4217091c4556bd3416dffcf1d57a1a8fdae7dbd82f3fb8f4f94a9e41b8b23d5007941694458999261c47e4dd54f9e8b8c2234eecd570625cbea1399ae91c123a57b119ad8a6053e9c48a44a865b343f81efd6748a2f150b29a1147ff9513e17612b7e38cea887f3e0d115aa2c6a9d0640f2d7e614f5d24c34b6bd5803e9509dc944f47d20b95aeff17682b2af7ce58bbfe3ddfc59b7c98249e859847bfe0411f18ea607c03e230dc8c92db4528ebd9470f14af110616564d3c5c9f4dcbe08e9629e1da9b572eb81fb9c12089e124690d5c580dbc1919e21e7d81d5a0d80450a719bc7ed0e155d178588c0cfccbaf892b89e3f25c7806e208627d030e89cbcc652890225b8a50f920162596b8e1d7d8bf8c989fd0a6dced064609c270cfdb725df278fc75d7466e6472461e9c068a7a053c4cc0956b8b6a1f609c5ddf965edf58bdc98a42bbff511dfbe2f10d4362a9490d75f9822725bb9e2e5811e7db4e196aba039edbb3ea901ba81514ede0f130c1b106e667f026e51db30e7069571a6165f5e85b45e6eea69931ac84e30e35b5e82ec61a080ab465f7f1d63ea6e4a96a596585041843fee6b2368de54549ba11216e1fd67b23bda622b583da0049d160b16309102b1fb1e8ef630ffcb79d2a170a0db8ba2f9cdee0d39cfcb343bf847dea42e35edfdd0825f4e8073d592c12f3cbc751070a4ec132c49135de457ce46c090c39c8418f170d81ec64d2cb6a51e11acb499d6f13aa390626f0fd4f10a3a08db4b819f287d954c402563b7e941c2f4616a954f26b0a3618a0ef7a284ffd9f7eddce415f411c944e8276d2f9e172db36d8f02084438e19d4b31a4c996918ea80caad61b8e03d1ba731c49b0a9f381b1a7880978f58255fea205d8f19887600f25cc5214e39d34acd94a51310c309e0f808577f1ec9843313d9254bd377bf7e6f37208b120ef6467f5bf56bf36e5bfbb2a5bf5b6ebca1dfd9f406bc09746303beb3c60e113530aea690033cc3333cc3333cc333460f9a7fc8227d5a5352443c43bd206c992477ee0c1b12ee1f000018b68e663f32b9fb83fb0704f0104811ad10ee3fd250230579830a657dd09b0e275dd2ec66bc88c8105623c89032446e4c61b7fae83095262e05a375ae245f8ab7a538222272811b51e03279e6a84e7cce950b74102fe30614dc8d61529a68512ea62170e309462b89ea70696a9dbd00191ab8e104d7c3c5f8e60baf5572a309d5a74dd9d1a41cbde4e406139693742bd95856b0dc12fafc1c245ee3a526e40677430996874d2a615a52427823097b65d85b8e65dc1c1db9818483bd96774e623e8f01378ee074499896e027ba9d18e137d1643268e618933f430d344870a3086cbce6cef22dd1394c84b3a685cd26f557597a6308d96ee85cd906e4d1f8009b81869a800c14dc1082bff9634c4a62df98f003c20886aaa151674f1c026100431f5fb14af26c658f79208c5f9c9d3b772d96449d1211911f3f9cc3f0c5929a5eaadd35d0b88088c81a686809a31777ee2d29d8c7c86cb31941748080307811c62e1657939345c564267976d01f1011110282307471e52ea1f3872dd9c70fcd81fb40e3c2c845c28922a13a96b13b91910111917633d200c2090012062e8e4930efcb25470361dce2cf7679a5219673f4b648c8b8a1d3bc4d7a5e0b4af6a43177adf5e316088316e710a9b615bdf3d29bc5dfb972684ed12f7e4910204182042961c8025795970ee5890526be873a71d2a379e8030dffe1a3d1701fce66fc1082813060915dbe3c1a6c0ae315d954a79bfa983181305cc1873f297aee106fe10259354018ad3065d23a4997c7ce222b0e95e52eba8a27117b44446415c60bfbd8d1244c8dae8a73d8922de6d7f856d1c248c537ae41c42429f6de405061dd998919b55937c5bb819c62d98cf7db91173f6d270c531ce5637e464364ace88888c88f34ce30c3477b101d31619442b5323dd1849793d39884418a447b999336339ca775444885310a3a598ade9e0372069020695c18a24822aff3c6cfee9e93230e082314e71333884929e95df8233e800c814018a0c84dfed3d2923851d1079b31448708111111e243d6d80784f189a4aaba4c93931686273af5246f97fc9b9befc449d3c79fc99f13f87e060de97027f76f8230eb8f95a348ddfc9ac0afbdf56f73ae347f26fef4292779c5d5a88f09aba2e2cd77c949c8fe12764449a1523faea5bc25d0b1d0f5ea2ba1650a1ddaba6c639a12a7147ef64a8a5142c924daf512538c5529f2e39250b3a6ce555bb277c7234126e17e5aa23a68c72141c625d13e7d4e4ecc1fc1e9b459c6247ec9993be24d27e626b3928369bc11ebc4560aa27f9a2ece08b5939d9c448913165f843154c99bdd4abce02b620df95b91f143e49e884493c42479a7b64ee28838fe9e2062e793b93e44f39e843a6bbb4bb321124332cab97cd2740b9167d64fee5c6bb64d88a4ab1eddada479d21e441f4359dcb42591c91604172da5b98919f76007c2248c67ac33fd914f03a28c278ab97dfa0f85ad0fb3d1dff22aed87c2da4b92c3b57fa3ee036aa28e97f852c9c4f94089f76249fa94e1f31e885293d7d3fabcbb7a285b8a95746d79379b87bfbf63489570628c7828ad4aaca8de1dea249b7267f195d6ec606cde7f175de9a4d5c1289eae3a3e89d3313a987be365eacc25f7660ec6b1cdb1dab01c123f31c6e4f3c9ca701c28b75a8b1293946f311c8add4a829c8949ce167e03fe3935ae2dec86f34ebacda85e680bb721a12d564939ecc74fcc062e4996ab96f3f24cd6b049f207932f295a2e5103e272964b9c92bfcea601f1f8a91be4aaa4b36838c55fcaf9db9e4113ea46538e8b36a9354392c70caf559e9b962d4397423c494d4a6cca9201b7309f90cb7d311dc3494c969265f2e725c540c67c5d395f1a065532d4c4ae1493245130d4394ab62445d686a75ff84366aa6c57ecbca91770792939c66c9324d32e14566d3da7ff7757940b7fa9584d4fba0575cacaf2564d7a522d58b1631acbe9bb71d22c98f1275f366a2aec584853962d8d9a4f3ae12b2498b82513cfbe165b810f7be9a5b9631156c18c57e69da4adcd245448dd2bb46e7df02a9982d395c72bebbaf32605aa43424fcc9455b428547a396325f5fc6aa0c0499ea9b3d984de4c3ea1cf3174a76b75d8543aa1d5a8eaa5a92634f6f2398d49e15f2668f727c76ad49895b28424326477ce9fbc502a21b1f2fdd6e275fe49b8c4afde8a323d9747026b21365df523589ee4958e1be1cc8eb5d12efd4b16a1d439bb9f9c10e1ccdf29fb4f0b6308e66072497daf692e06c31042b973926389d351e382c1d083f496dcae29630a02c32896e4daad4ba7fb2f348faa266682c6c7be484a72dab0f1cfab6e2fd0b7d0af31ecb4b9092f94934374ef59eeb7c92e34b53fa9bb2f3eb5892e0a9d622e0aff51e99439860ce262ab101e4d4a624a94dc62bfd63a5963deec39892d18118d2927d385ad925a1c1197eed67f33a384164698dd678fa633791685383ff9a3e7e68f659145cfb3a32299e21b0b835de7acf4c2a2fe247293362585f7159f7f5412e98a2cd515ab7a5a561c5ba1b60955712a65c48715495e25557792cc5f5bc5ef513da63ca94d8e51c53569e25ac65c2af03c394a9749db561d2a7cabe05912dd2934bf942bee847df5a6286aef322fc713ab4b9120aa25b9fbe8984b8a2ce70a17b2379a47477146d56a13364f5e2a8ade3d3f977f0ed6c1509877e7b32a94a70b415178c9526bd9f221fc841fa2a67f323c9d9c278a38e1544c96ebeb7422f91393dcf165d3b7c3097dc35a652f9fd0ed6cc21cb36dca5af5d399a309dc2479d763ea648228a1840a4f73162c07137f07ed9ab61c2ad3b9c49ece93796c3e137d4b586ab2689acabdcf5722e165662cb5e8cd4e89e225296f747c1267c6f97e0a719a95c46d55fde227e5a870243a79a3ffa6ba5f86441bb34e579930633ee2fc5937fef89f55e908fcbfe236fc04bd116a8cfcdc1c3f7e868ce83775eced5757731154a577b5fcd5fc144149beaae3d595b22411e5124d58b9e98ce241045b527dc6ce90c93c44f94bf0ae134faacf1046936225c4bf726685d842a664399774ee1442144a67553c83e0425dcca92113c46361f2a6cb07c2cbaa25e61e31d16e038842bf592a5f3fcbdafca1befe3ff714d3926bfce0a6e8605f62318568fad078f44ae7211ffe2be95292523733dcc3d12f5ccc4acff6440f65f394233b2ba3bc3cd4a9b249a143a5b91c3cb06216b3fdbdd7d91d2c1d13c6932669246387fed26ef0cd5cb397a983f1ec4cccf11d835e0c1d1264c367f3eb18c3c5cc414b428d77cca3f315238724292d8ec726b79a4c1c8e2161e6b939c9f9c3c10cab55251d99e37f032715f2a25887b7ef863ace72f8caf0fd7c1b4cae1a6e92cd863a9be45c360f1fdd359c357d7493e28410570db6e612f73b974ea669d0362ebca5cf285a161a12d3d5f8c7d01938a9f56357493b2589193c93c7737de8f7942b43d9936b7ec4df87870c65924dee1cade7b1338642c5f8f973e88c392b31b0399b9a6bdea4d454188e1317a9b5d9d94c81010fb9f1fe1335574c7d21d1d3c7a2f2bfe4555e603343538afefd665517cc5bed174ae8d24a15178a4ea2745c0b1eea6fc1281db570712d34129fc25af697e859d8ce2eec73ca1419c742b13b95b82bf9e1f7155615cfcaa1648974ad90e439a65cd5b75db255e0734aad269f54db2d154a772da184750a7425e9145be6c454a57087f99c268ce889c928d896bb297709f61f84c21e67395ee21392aa2fd92574db3d4ef843c3a4b6a7096a727bb7caa19d1b13b21cabc45c527c70cd12b8bf18b54f104ba6514252ea2ed3de55d32b09f576c88d17fcaf520509d55bca12ed97ef4b21c011306d4b37d267ed9542002314f455d385264f5e2904288226ff6b9f5872b0a8140210c1b328eaea7ec2c7a81060085a3cd13e5892dcf00920049334954b93243d9b0f4639d67b845becf238303acb9e5286923a56fe8bae9345ddddbe58bbbf8478994baf7b8167949c9a84af4b352f4c499394f24dbce8e15d74a733e27ae286adba2846fd8d138f1f93642ed05799cd59e2e2b43e9f0d5adafedd82cd9824d12bc6d7e0d942fbe85193259582570bbdb75a5df432a78716ff9f76fe3e99459227fb13f760b2cac292c41ad7c9bfeb582cf098a77212636b8d061679fa4e9d5e4e53ff0a63d3bdc5ad4eb25d61a7c6bd9892529b1c8888085981884862a315ed89394253ecc3062ba8bdd42dcbede57d5985e12f9ac4a839c63e33f2e3873e1a6628c0862a98ffd825fb5a8a57290536525186bc8b5ead91f3282aead73da172544b3ddb076c9c8228dd54b1b710cb9b110dd830c593ddae3f97e33a65364ad109dba1e2a3b4fac549d1c9f9a959d4c6c6ea51fc1b5ab6ed37b4f52c0a93a78af9e4ec6679a2a1a8f3e94926c6f425d706149d475eade55446af3e71d6736b1d13e3b8c6ceb0e1094d3dbb58ead6ef6d860849808d4e946351a56db6fe849c076608f9800d4ed8d8449663888f11b226ae2431b261b29189c5badf24139324d8fd98c8db72637cf905b1d3181ed8b8442327c8b8c5d0bedc7f60c31274dc2ce14a8ccc1f1e1fd8a8847d6b779773f2a8498e1265f41424fcedfddcb2868d4950173f8d6f566d48a228a19b2926d92fc64c176c44224d133ded1afdc28790283cc6e8981a2fe3765ab0f1884ae3989614eec3241347e869b2b9a24bc5b49a4610f1a16233fc277d0d0c6c30e2ddfcfa37b397a470116d7fee8d933f1a3c654444240d3558604311e989b904f1e06ab1f28920757d437f8a92eb521b88404dd0b27c825c92ca3c04173dc222cc3befdd51b0610832c3c34fae7e2cf50bb1a546f5b025d5c15e28d82004517f9e7ed169833e8833efa3fce43539ff6e4310e53cd72606cbccf1f62dd8084431de6654b096730d204ad1d3dc4d674d67cb136cfc815b4f521263c60936fc6034ddaea9ca71730788880419810356e080321c70c610216ad8a02fe0ce817643d8e843baf759b305ebd6d4dbe043624e69d59d39ad6866630f8b9cf465e6de5bb26b430fc598242976cf7c2ab1f3d0c5d64da66fd237910d3c50b9c7539d1cb671072d69499a7819ad7d63c30e054d726bd95340b05187259338679ee2d11304481a6798e1a3d1500ad8a0c37e19d53b95699fe532d89803fbe51faff2f9d47a35d890831be65f6c4cd2461cf80ebe2944775a74520d1b70f8324ed62bddfd59a70936dea0c6f5fc90de62b92b0020820d379ce4380f1166193237426cb4a10aef3c92e226f7f436d860630d09b9b333ff73b9d12029708003da0164a8c1861a8a12b387d09c3931657660230d66ecf05c8d9fb4933c820d34e8799f771dbed4537a062daea7d3e0d919ec23820d33fc1fdb5e4af7329426694979f225cf368d88880c41c339889b4044440336c8b0941c65bdc9c247194786d4000d07818d3164e1a3d1258fd810836f9774ef5d1e51011a251011a9818d30949774ec3dc686e7c9b1010653ae749d36c927cac92322224d813328b086060972810b90414693c1be821f3ff48c324444c8d0818d2f149fc4d4120a64800c327a60c30b869e3cb27bc1d26bba0bb6a714d9339777f7a40a36b890989389d1f3bcfc940107a4808c326c6c41edf8e144afa9aa106d68812ac133aaa7339494648d2043cab09105f3d64ffa49dd89b58c98c006161c350f276172528ad238a3352022c2041b57c83ac96c3031a78889ffc086154a1dcbb7569298443644b051855a4ec54d5556de3743b0418544b1f829cedb4cc88160630af6bfa5be983cabb52d85fa4ca5ed2a18053a6f9ea4aa9a4d8e162878297c58debe8ad89f60ca79dd54314ef098ac60c309863e31693ab4e44ccb0a369ad087c9dcba9a2e6476d1608309740eb326b856c4c673095a99c5b08d8bd949aa0c3694e0c796d8cc9edc828d24e859f35758cea555e580321c400220d840029e35e588f89d24e7368ea055a7e6f075f28c894ac08611cca89e53c6a62bb1e2368a70f8e88ec7864ac9930d22d441b37a893977e5a69105d818c22669e5fb08550d6949c38610bc92afffc26609d92920030806fe6d7993f28f8888f81800308a29f30a7392247a9286193a08bde8e70fe9c84272ac86adc9c9fc9dfc0104c8aa400716b09cb25ae67e043aaeb0a5d4b5cbfcdaa0d6888808193aac604eabd099553a4927958e2a1824fde2eca42343d40422222343340d11913586e8a0c229bc25190bdb25f73d05fb735abd92231d5240d4a2c5b40a8f1b71e988c2b9dfe367aebc614ed4018522c3fa8f58b6de543f81d0cf134f3e93b431ea04743c7ab55cd8ec9b60bc3c6a524d8925fec50445efeb2f4d9cbaaf976088ed454d1dda75420be850c2d1d3d585ca9d24962410d09104ce7277e78d8f84e439ed8a3049fcf8a18e239c21ebea157323fc52d931fc97e053253a8af05aba64ba29d2fd3c3a8880c919e3c674e4dde7750ca1e49d4e0e9b246fefd72184fc72688a4c791db709c6e2aaa1d32e6f49d080d17f5a8a15e7f3c5ffc5bb2a1763abc59c5bf751831cbe783ab5b325c9eed4c43790a317e74d6b59b2c950f66620072fec50f94257d677e01e08e23a002202936317a5a7922eaad499cbae0b4662f39b1826be92a00f111117e4c80527adc99a2b09339ed50f90d1ed6800f981831cb8c03c6974b494f2447b72dcc2ac392a9f64d2b6d824217affbc3442db5a9472b2591e9346bd345a648264e449a13c76a57ca47146909115e498057e96991a77ca42cfeb4d259b9cc24f2c16fd6c78eb6469392f990316095bdefddded1172bc02350b4be1c4ce15d70409395c51c941634e2619eaafcdd18adbe4e8349ae44e72126705e35f6fe2e4268d76ae62ed3039259ca8be76a20ac345eb933def547c72781af38ef54d0f15458d35cfebb16de2788a2e6cd38ac5540e53f03e96a93ad75d2c739442d5f5ff549d49e16d4ccfd173398af3e8f4dd6aca191a444dd0851ca260754d9c2ccb512147280c8f493293db9097ac22e400c5a95e4eeeca8ff31f2e21c727b2d02458f81c799a334fa4d9ad5463bc850f7e8e4e68a9836cc7fe0c95737002f32a8bd1b3630961e5d8c4e95fdff2c534b1a9ac652b49fcd470e5c884af1999337a3613338c89e494f22795d2f806eb12a518a2f5b527c9a14c2cc1798999193e5f6fad4a949644f66ee39964614a746729780eeeb937f493c03d7547b7eb89a177491076b9dc677242b527418e48ac214bbf62d35e53d0043920516b709d9335cce5a8e578046d7fbad162c70a262687230c0b626dc2c87f06a91158d409de9b4d663ac7063918b1ecc690daece9dd06722c620dfa57f1444f96d41ef1e14344840243d448038d0f04012224881a1910e243d6c0800f72282247225e132b6d633ce9f385ccc88188e389a1761acfc7c31ec971087a35676dd64b377bc96188fc24d9a4fe945275788290a31047cd317fe2c929f3a49a908310c54d29e7c84cf251cc1c83302d4d6b5f45337a9b4310e6ce59232bf55e620d44f2e85d9bf8e7a07de70044927974b43bca8bfd39fe60ae56b26c97ff922d63fce06e7e4ca761da555d8e3e50a92e8365ceed5be27c30e76ca193ea5a8a8872ece1b7ca41dfbdf35eb61111911c7af03cd2cfe752b8fa2f471eacfcad299f878cd3090faada989c92ecf92ebd911c77403665a6069773d8e111799dcd52f920471d504f277c928f95a48708f1212a822039e89014a3e37b593cc71c0aede92db366dc4b1339dc75498cf04ec5c1164d1db7f50d21071caa50296ec9ae77d2856fe0c4bdeca639eb4beadd905ccc457b7b50eb9836b421b5739b2cb3e14be279b76bc2b5de39c8b106aebbc495d570e27a6a3049252621538e6dd2280d7fee127dbd5734d0b1dc842d6dbb19f50ccec58f94ecd1efac99612959c5a4ed7463dd6548d2bd9b0c546ffc5bbf110b568d210d162e878ea218ec123a65d32671e9c250fa6a6f2aa9cd0186664a1245322d091a3714d820c71768ed91937c4cf812af111f66680ecc0892860f1f0cc8e1054c3ae21f7f39bab089eedc5c0693f270c472700111fd35cb1cfab15a0320223880103d00e6d8022678ca50b9f2245a6ea406350022823480904044a406407e4820871658e992c743bc09b2d3488e2ca061d376333b436a252407166efba01564a4db332644474cb039ae9018fb4c3afa244f95cc6185aacfab526d4b899b56e1d9edafdedb4eeb961c54f8ce664eaebc64f3438d337ea8310211911f3fccd81c53e06e2c894132a7e8319c430a656ed5baff647965e688022661529be0259b506f0e2890d16c3e93c65ee6648e276c5ee1be3ec6a7cb5c0e27941ea67245a9edd49f3572342111b32cf627a524eaea831c4c30e5cf942b69f426b314e45882a299375da95a668d7790430977127e83070f63f9620ce4488255f1cd2a321f1111a9400e2424a734f52953c29920a201c40c2165e43842b9c395fbdee68b2999c308c6984fad8c5d33f9c95184cab66b5473dc2aff1c44404af82426d1f3d307cb0239866058d3d914eee510826b7a6f5b261e8c3d252943a5b5d6f41d609427d4424efecbfbfa85318ac67854aa993cfa02fd0ee2e14163ae8e42c0d10bcc4bc60413a23a53f7031cbc207ad3e7d0b7d162e542f4470670ecc2d413435bb264163cb5869a00872e38e92e95a027892bd1e3c8059fb5a6d3fc65ba5471416a87bb5b8e6e517a3d293d8e8fc316c56dc6df94e736f942386a510e1b976731bfd297c800072d38694fb08d3bff33d5153570cca28bee8d49cea27235e290459a3aa568b98275fe742cdee0394926e6c2622b2948ef47e64c42f50af56fa3a7a50ff5e1b982d1f063d924ab1596149d399b2559b186694c5319993e7331c0b18a2d2b4f6ca6fd875055f14b27b1df76532a87a9d842efe50d693d7526a8c84ec3832671b2b25f384e61a62c5e2646a94a2177347098e2cab1e1a36efb6f48d5c0510a7673664c8256c6a8a79f91c61a2958630829d0682967516dfd3c1b29e0184527f9a798ead39a5e0c0026e01085294242f4aaed3c774270848292da2cde7878a8d2f0016488ff581d120488097080425d0bcfa41a2291192b2ba8008e4f24fdbb899a5289c313ab5cb8c6132f3b895e14707422f7da0d0d25985882070727124d223c867936c93e035672806313a713e7634b30c95bd216e0d00421a572bfff2989de67a22ca97b5dc238309124175368f8dba0fdf50f378303429080e312ef9c9c369efc9fc5336b0c0100137058c2398deab1de761bbb127e8969bb3143623d85831209d5e16925de080c704ca2bc24ab87cc382491774f87cfd9704422cd5cf5fdb866fafec00109cc4fdecab85baf1d5901c7230c5b412f98fc6669612de0700479ab9dc42e2bb50b03400c381a816be830d970f372393818916887cf21dae2262f8b286649553aebaa960b87223609b5d0a99325315d1c8948be6872c86c3b6519c3810836a6beb568322611e338449f7a4f4cd74bf2d605872190b86c525869d4a660215cf34d91b72684a8b76acec4b83493d341e4b91e2c749aecdf2682e8f44636adde63764f204e3956fb97a4ef911240909ba40b97d6ad289f3f587a524ab152542539ec875b329eb8f99e6a34d60732859e49172cd72e8f041c7ce8a491f3ae68feebf21370ecc1941ecfd7f65a27893b01871e4a37cbbfcfb124cc8d230fbb4942fdb6e745c08107d36eba93eec4b034e51dee34262549276a07aee4d412ab321c7568d22c652d2979a438e8c0855bd9abfea095218e39b011be3963aae8be1612e090834994ff174d39c6cbc781932429544ab182030e49e12dd1de49d0f208c71bf814996366f350929c23222289c30d9f2466cb26a50de3156e031b8a72c998c924b76c08c71a8ed7e94b7c2c553c1c6ae883fa689a0f571a1d1c696054c358673371a0c18cd0acecd1a63967719c619b93f65035254bd938cca0c79208999a12bbaf321cca6aa395546fb226c1418636345487ae65cedb708c81faf5ca7bbd235f3e0e3150d2ef47578ee7b6151c61e84ee8eecd6937369361031c60c04ab772864c72081c5f28cda7edf092279e5641c0e185d7dba48fc1452dd489fa88fa0052031fe0e84229f6b44874db760efe1082830b5b2e4bd7612cbca31f1111f1d12a041c5bd05453a8342edbd5ef8888c80f7068c178974c4cd217e3c8c21fb35bb91b22adc7026525c6141b1ff113a700c715cac152471372435c92471260021c5670b424693266fbda164715ce9456a3677b92cb250de0a002361e4d3b2f2ede53b0423744e6b44a4139b9df62d6340a75beb9de5b9f27c984c249a3d7a73fec7ef809548f772e0f9ddeb34ec02dc8743ac1342b884de863a7c95bf131c1cbccf41aeeb3043d66cb945cdb9284470946b52cd982c4252b394948caa13e27ee61ee1912744f929c2dc5daa76647f0733e39e5cd1b239423933d552789f3a9229853c999c4ae8a137f229c2d599656c9cad91d82a2c13cf57c70082139cefcfcdf969c5582b17974d4b7ce1e29d10d60acb21bcbf74a7ed15f4ec25f4ae48e5cbe30e716b3ee0c927157bed10b2bc5a5bb24ef29c37c83176d90bdf85e62821bbb70452ed74409f917f33774b1aee55fc7790ae6ebc88d5c98f5b379a1926a77081768c65425f974c34335624690143820007eb8718bc4345f29153acee26f0bed5278933e49dea8857362885dd5db033768b14613557ed6d2c5fedc98c5bf7933cbf3b75f7b6ec822ed4b662757c8b5bbb128f233e7eca7a167c04229133febab7849b1dd7845f9e3427d5398dec66fb8c2332ff1c450d28aa2a42dcb33e90d569c842ef1cf3f9d5831bfb18a5353792731d368e6f6862aeaf392d65d5e5267be918afb53fac91733e5a242c51a315fc1a48eb379b9710a2a2f343d9c374c91799233656c0d12bdb9518afbfa728f27a9a762e60629ac943b9e8de54aa2ea28cc713669896215b33537b8218a22367ada93f72adeaf831ba1d8beeda3724af21982a293648fafe3f52776fa09c73f57ce55eb1a55f2c4b93e4a591e753b7ffc30e3063db8d189c233e6f1754fa2fe1972831389baf33e5227d63f0bd98449ca7baab3c99527ac09d23a4563e72b137fea9cea55ac6c6317720313a5d62445afcb3a8fe11b97d0a33a7c92e6bb3b4f416e5822d9dadcaa7312b355674444c48c1b956036a65a3cd4a5d29612bc76a54be9c6336e4c22a9eb3633b12309bf4ac85829d2111191bf1189731473c92c79dca753831b90385dbfc74ca9926534a9c123b695cf3293daf40d473c352fadeb11a396644444440890111111bfd108ce324b78cbb851eb4744447c888888e002182881083a202282821b8c30b7b2ce2649ca8d45f0257b1c4b529f5de08622522b29f545e60f174d129118aca28e6468d9498830c9951bcea40fc106f78ebe53e1af3943983bc6f0cb715943ab42ac194c74555642dc97c49caac4ea5d1f2570631097b9a7b7d6d439981c411843365f2af55236b91b81a0f76dac345c47b713841b80d0c34c0c99b12a4d8b37fe60051355337bf4132be5861f0cd62979a30fb977dda9598977b26ef0a1a09737932495a9ee59e0c61e7297f7144a92daa7bda1874e2d54e8709a3c34a53a3ddebf9d27fd811b78304e48bd38b5ffa4e91d30efcd697ff6ccf26687d56f23a3d74d4a19a3c08d3a5cbe6252c48c454b2e1dd09872f213bc4d8a4bc111dc984327c9a1e3493f8e8888d8c92139a5849eab2a3199e53c3043880a44444870230e7daae499354e0a1665dd8003965dd3d87d9f74de82f80807512f23c890324444d048e30d8d788a1a522674b4714444e40c1f68f88f3480b01b2e31f54d8a1e5d2efe23222213b8d186cd652f2e58fadf15b8c1862449ea655f3d26c95e47988cbab186a36dbf68728b55de8ef810628688881ace496cb5ce711355e9110d520311911b69c8d6ff4fe674d3b765881032ca0d34145ebf336f759d27cb3983270699dbb592536c123383494e492871b6c385923565e8e6427ede7349a95d91c1d7b13e212ea6dd932c18b83186ecf3679fb4e630961422864376aa70673567a11d1111313e7cd4408df50e435de29ee267799f1d70030ca7acf36ad279b9bb9b811b5f2846c787f31ca5f33337bc7050539334c91deaf3d9054ef3494f7f8c046e70c1b06b1f3a5dcc6b6e6f6c814a7aebb6b7221de388160a62daa97b7a046e648113b4ff92381525e583d4c0350d1191338200e1c0b98185a3a59c3449d89c0e5651c3c79033be0137aea0b4d5797a8d9bbcb753c30ac927c6da98734e315670a40a7b099b6a362a7f3e01ff27d881082b681253c5f9dcfda6043b0e51e88a7269c13e9ce88f327618a2db3c6ae3be2b62f911da5108265ccddd24e1525f4d883b5c8aee9de4259e8338d48798daf350f2753b04518ce253d91e0d44dd9ae3e54d5226bd0920d012c552c578d993e5ecf88327dce6fd916e871fecf4f94b446673febf0f56bc1d89554f25fccf07b3d5f34eaf7c9df4ef21694a2eab884b0f89a9673f3b595b2c3f0f27d984ade8498eb1c9c78392739b67919549f377783e7fdafdc5e45ede0ebeb69bf5e6a94ec275a03ec77b55db648fd2e16cf249aa7b4c56e61caa888de1934fec8972d8345344e3a73b11e35074ebfef7f18e29091cb6f0276bc7bc9eb97b032b96eb3b93609ee4b8e1dce97c71f3477bd606b604efacf3e621326783614f1efb5a0b361b5f4376326f935631a68cab8134d1ec644ff13418d6e34e8c9f33d9e468d0f273a77df7c53f833e9feea6375dbe6f06c6539b6ca7d191be0cb966ccddb993e124b758ba27d964ea31945bb118cc95ce3e49a25979360c7c95785134f8c53bc1600a267bce14d25ec42f6c3b9d5137a7248d1748dd5469b31fbc4aba50d25aa9a909ddec7181d1d8bef47b8d790b5bc57831598e1612634ab2e96f9d85e753bab069692c785df75d2f265c4b7d85d379de24fdabe510b5158e49923ba6fdb16abb0a7cd0cb35b950fe692a785765d2b74cbbc5532886de314deb2697ac14e8b1d6581dcb4e0e4621e12e6c664fcfca83c2295610ddcaadb98e9fd0dc6a74a818cc530e3bc1a4270731dbb8099cbb6b92847e5bd79809553c29c5e4f9931dc34b30ba3786be27c9c40a2bc15b8dd32496247b8c380966c8595dbd3b217f2428b2fa79953388fd473855bcc41c1b411d97be57cd9cb92fc2929a1d1795af4e9a08a5b05e13bede4d1f4239ca97ddd49b70ef10c22786ec7d8783610ae229bef92df60223b17ffe426cecd67e714b8692afcef35fbed8c2ea925ba70e53f6e23c17bb37ffe64ff202edaaece33d3627bb704ea8d3121fcfe874f147079d4ae7e230268afb6b7051ea9c72fe5a7b0b5ab3539e4bdc05696d61e5dc4963fdb5f8849feb08cfe0e3d382cccfe2d7fa2cd8928394944cd254715998ac4df43049febc3716790adfaf8cc242bdf0f770e9dbd97c451fa6844a226fd749572453e25b8c9d52b4b115ae67ee1c3c64051329b69aea41ee641579729d552bbffe5461996eee58a26d45960ad2dc37ff532ca651c1e5e46219253f87cd2968893869f2320593214c9c4b29b864da27b9caab5b9414a47cd8ca61d251a85a5144343b67f3a2f8f250240613e6777c6b64507452e8e4abb273b29fa8faf5eac3f786cc3d615d0a9a254fc54ede09d3c51852f5c9d7643981fa59d491751349a51a544e90be3435f1c61bcf7ac94cb02596f44957bc7a4c9c2439bd2fdafec92597e8dd673ae62496b8aa24793a9d28b7b94aec49453a5b0e25e8101e756df59a633989663ebe438949491c25d1bed9f725fc48709b62c8543b4282ceacbb78b2c78f1c1fd1addf45cf15cf90d111e6b5dc20935dae2e36a2d0c9a36c9eacd82932629352d8492fdb12525c84a922730e27acef9ca888b2fc87cf7b1ba3879888223675a6fe10115f92fd32ddc894393cc46671c53dc7f0da0f1ae2987bdae25f83c4070bb1d526df9cbff2e59f1087f68b2dc12a79ec83484afa1cd3c6106b5d1085ff145fa399f01a0f84b1e1c3b9c9e7967140bc96fdc25a389fb1ffd0c589d8a8bbcc20fba1f3b5b14de920baf661cbb127f9a6d142e5031e2fe67d13937cbf3d24ff68d8fe4c0f87b5dbcb8f5af29e07b3da892f711f2d67f090985573e2a58917ac3be496f2b9a7c323b74362a2c6988325a903baf369c344091dcc1f36a273cda7e81cfa93e266a530afa51cb4942c2513af4b361e07272bfc9c6c9d4eb6e1a06d36936fdf6027b1c4c8ac1bced1e71fa9b6a1f658f2b2e3217db2c17cb27ff2728bfbc935e09eb1b963a206db4e4c92fc792fbe240d476b6ace17cbf649d0f08d79fd7592b37c770656aa9377940bb239339c52ce16dd634e9d396538790a699f3a64b862f49cad3709f9da18e84ddedb2e26496bc4d076ba99be6138e87cce7c0586538812ecd3745fc063ec1c9d72ce0b66ea66e554a7b7772131c9c5b9f95cc8e492f33edc42a2d9b46eccfb1d6a2db81b37e8c75825bb59b0deffe23aff525c2c14dbeec249ea15b4bc612cbfb702e39d692c5aa5f0c92a986babf5764285c4f817532521a64f92292425b229573149fe914262a45b8685d5b72d0ae62b394ed6e4933b83c2f5d229845e650f3fc1abfdf78f9fe973c6099ad8594a3e26d13ca6095e4c6db934c304b358b6ac193ca7642da16f17cb31b9625b394a40ebf6d4b37292609a9871bbd6c48c3a12d0a4a1261d3f827d9249a58c2f795e23fc6e92278b3326ce45c032625c2f2529bc4a84b38879772cad0043a052caa9fd74e993a015400806b124d26296a6420b8626a5f6e7fd28164203c6a639473dedbff524f60b4ccc716eb423263ef3c57562c624a6d4294e65bd70df344a7079c1ba6587cb9a68ea2ece4ff163e518d3a5baa84b43cc76ba4966c945319f9b86b0b3b28ae1024b5791a133774c21bbc562792d7d496c796f8b2431c4b767febcefb578364edc940d930fd342b7b8fb98c4dfe466a1fc6e8ada494aaa595918dc53c692b7355f3616caa78d7ec989ca24b0206db4a444556fcd79c51ee3ff65e6b8628ff79d2f9338fb9b5694a3a3ef6f6358a179a456b08ade99625691bc743a9d5d15a57b6b864f7f5b792ade15136345e65f6d541c2e94bc9b4992a2d9a7b865f26226ff0a9335459bc4306f952ffb6c4a6168a53561a4d29f982285769eb396e5248b5e350acc92b49636748c9f52a2e892fa79aeec963ea542516d9e381792d75583e2ef4ddf1f3f55d0f0278a2b71c3fceb89c3b27f4e679dfc643b81a72c2f57a2b11ee5c45a312116532594b98963c678b59e68e23fdd8c5199d5c9cbc4fa9eab6636986863f82c93ab5cc2a8dadee999f4de2a96d04e34395ff68aa256a9447fa268f84c51a24d51369e245f7ca54ce272eb1c4e0e7af22f093f787cb1126d7e8f841ad72f6ffafc39a10289f604effff89d3b43e51106cf5117e592688a8a23bca898bf6752f8f91b41ff49be2e651e52678439cfa4df9038f37a11874b2594bcb2224c31174573f050262742910de939bb344b8a0873f05ce3413c84f7ef71fd3d64fca021f46472febe39da868528b483bbc554a2614208376b08f124fe27758338aab44e3aab98334710bd4986d63fd953cc0944de31e58b099fa93a80d033739cbe6693f33f6039887c4e597396fd60ce0e8debab9d34d6072d67aa4a6597878c0f5dcec9234bd436647be8b2c925486425a9337ab037e7e7d3649e43260fad9c341ed84f7973aef6e37e8793c792e358f4bc69b143b1daf5929670624d24461d0cd3413aea058d93480c3aa47d276eb6a68c963d0cc498c371f27e9de6d870398a21874ebedccd6f611b88118772b6f06da9a3e498bc1870489a8e5142f6cf4fd4dfe0f45e55a5b4adf0dd709afd94c4f57781186d40e376f4fb13546a13830d584ed2dfca5944df34b286f43679874b82aa873f440d35984bb8bea9ebc8a4c1ecd6de94c4764444040d7da68a56527af9ab943350eb59e9c4b40831c3d1b782bf5c4c79cdcbe0af497267064f5d715286186430f44fa5ae92cb85950131c6f0c97beeb270d37f9a18b8c82bf14ba368441e0634f36fcca6100cad8af44cf8fea507bf9027952cc993e861d50b81185e7063def679f11cf3b206c4e8429e849bb7f8b7185c20f3dbb45aa675a41b8980185b10430b5a0c3f9a6db17e4d3a0bbb546ce7bb8b9bc4f4471a677c600d0c0811a226488188c81a2b0616cc74d19318a23ea48d66fcafe0031ce000903384a80e7ce8408c2becddb79e7f63f2133e31ace06eab9c68b3f761f31c10a30a955f0a796252b2e980185448d7abcc240b8b0e6f4444e44e4de1f9f3e49d63ae4c948c8888882185ea3f3696661eaba39068d9d473e518282426334f8da811f5f488880809c47802faa6329943addd3410c309d7c797374f5f52b83b2222d284da240bbb1b96651381184c380308100188b18446c30c1da0a1868f2142d40882430c251cd3a7b8f1cf4aa32ae487fb0802448c24b0f1a4e96c4d9b75d6213fd410e23f7e98110211112066d8400c2460b12124f5bd1111110f8871847bb7e41ce34ec66a1903621801c99471662ecea7ae5d204611b293dcef18b171e329440c2224172d6b3cb743b0336b4ed4244f20861092d5e32c674ca9966215e808c6e9e4b34faff3836e26d0018ca4b3c9efa169d3ef2c43c72f3eb1638e94db2ed0e18b240dcf9d8b0d053a7ae186a70553b30feb8c0ee8e08511275d7cf41236edfa1bd0b18b3b2f23e72f96acd57d40872e7e910a3a25a7c85fc8a311048819b978b32cdb32fd3a706177df59dc6c62cc8debb885739ed184fd75dd7175d8c29230796b57530be62cc7247bab9898355a1ca2ac2f83aac59beb023a6661eeadac3757fff0ff888808058e0e5998e1f2463b3974ccf263414dc963bbfd76594f74c0c2d0b1194e427f855292106d67154e664b230ded810e57bcb1be1ae3e48e76d20a36ce3e5494d5fc9c658527ba2693d3774db75c859664bbbb8b1fcc6da38adeb64f08e96e1d7f5361bafd09ed0e1585aa4c173f29f5e5764f03080bc1808e53f479254ece57fdd0610a3b7dc8aad09dece32ec5258cc514c6626acd4b99fe00902169ac1184c900810e52f09a6286556d2da9b4818e511453eaffccc977554e14866ccbbf6852a120f3ad75c4745e8c221a3a409194b539cb9d6bbdfe37d0f189cf2ba7a3895727a5ee89f3987c6296ef982a7b9d304f5069cd948f8888e8e00469a92ca79c3b9da6a88e4d6c259cfef589d18429a364edfb64c238fe71ec7b4cf4f91aca6b4e5b4dea12476c97e7181225776c894e4c2efdb43811a556827fcdb152f7319fe628919af4d134454b9a6312d03189aad246d5909b924453126ace55256bd98493a521407e9401e4879033744422bf34b9d4ff556b4e20514d6f4ec254ca23b28ff2ff733aebd63a42cf2475ca24c6281735a2b4f924379c3023ccb623956eab3e797411897be8d4a9c45453393a1441c997cb72068d7d972411e64a9fb06e16222e937b2e79da0f91777f5855c8e57cad21f6ac21a71f55bda25d0863e7cbeba371421cbe73cc99b404490d07c15ce7599244abcfc408829693a4b00caba49d0500117404c2e496e2e5b0e1d1f406820e40b0ab162c54aa2322223930a343a0e30f6c49d56929f3524eb61f0cab127b9a6dc3d4a50f46dd4abffc2e37f7f0e1b06a317b707c5e63902be95297e8e194b5a32e588bf8f6192003043af2c05d8eafff77425d2e011fd08187a35a8e231f6ad2a4cd471a7a860674dce128b7d934c9c9d51963031d76289467ee6459a2d9363f74d421f936a8bb98594a93a58362bb96deb4e4380b75ccc1f610265f888833880741810574c8c1246f7a52bb0fe9e5eb8883e2b91b3eaf09ee81c325e62485ed5fc71baecb5b5992be232222427e9831440dd4e186d3bf9598859282c62e08101f413c033ada502529b75412adb5f2c8867a2b5fc99dfba47f53c71a0a974bd2c57e71cb87d7200890171171810e3598e3edea4b1849f5ca888888331aae060ebc8c770dd440471af849b3f6d098921ce3bb063c8d3552a03ad060dc8c31dc25693fcf6730e5dff4e73cb1d2351d6620b3c7cbd096b12f2b26206388193fd22823c89019e82883569a61bf3e26c3b75993181d3578d456c718ce2dbf9da3d679b7a5430ceee534b144cbedc8d41aca02203a4881047484a1ef64296df767c7d0c1e0ad49497252ccdc717d612dc1bb6673a2a4cc18b0002fe416839ba7544ad7f12e1c2fe56029ecc685a473aa7c9eb7a5e2da022776a7ca56eaec29a3430b7ea8ce27189ab0a39d5ec92dc2a047263e4bab3979e55abec4053d3061cc92d2ea4bf54bcf31e8710944ff355dc8fc3bd944063d2c51de6fa864694aa9251a6684a07a5482b63c6269c2ab18f4a0448f49b449c81e134bbe073d24a146946add4659ded5231294c5a0a72f24b80aa64912f6526e973c024d62ce5d922f7e25e78863b01822b252ee4bb74723941a8fe26326ef9697831e8ca0caa35fe8d3d03f961df4588422d62eafbf4b2b655f2722e1eef3e5ab1c172f051122e87108ed2d7ad49eb81e9949410f431c7ef139beddb37363418f421473c58f2ac943ec920c28a3860316d0841e8430091716cb5532c9fe73e0083d06915b6868d48c3e811e8230ccf2524f651e11cd0a7a042259cd95c964c369d0fb043d0071db58264943b48c883dfe702ab14cbe3c9a64eb68444484020030420f3f68d9c40a4d962fa674e94321be63213f57499be483965f525c99cc6a7029d0630fd6e651afffcbb4b0871e4a26eb9960a2aba798cc4392dc932fe2153456aa84f4c0c391f193d059610f7adcc12485750edf99f4b0fbf8a1660ca140072981097ad8c1cce4e13977f7a843a2e61727b535acc54ed0830efa260f969b265d5d3e073c5a7b5add57a6a81cb8ec7f726b7eab198e839ec2d9a5b471ebee8483b7a5c1244dca7b71f2864db33294c99750296e821e6ef0a29fece1de825f5c6dc82aa55e935c4dfc90d8605ebdaa19cfc9c3e21e6ba84eb613e62f27497f570317a96ec96298d608d3e07c8e095792d7ed5fd05065bcf736e1f2a63491a0c719bafc1f5bb74983a7d60c8b75d77fb89d184f2d03dafa669ae332a53de94106ed2b8cb497c67bfd7a8cc14a6da279d2eb97971e62a82baadb9e38b6d12a3dc2c0ca676ab890f7e1ca203dc070491bc42ad4c7b612b51e5f684ab8cc7f3d9d9218eee1055dad53c7cf3143da6b1f11a2461a19e8d1855d3f58ca4c93bb4fba064174880f332ad0830befe55d6bf47831b264444444023db6f0c5d4b952c80ae1d96424013db4b0091f56937bfaf3d887a04716588b6ee92ed9e1b1c1423147bcd626297b7ddce30a9705abbf577b5881cc71fa42b377cce955d8e63cd55ada958f692a189e2c945637054b336ef2bda0199e49a12973cf26a78fa1a61e51c0b2b59ec87fac1c61550f281caea652affffee4b9c7132a315347a9b1fcbd5c0f277cea19a22a2549a9cef76802529ef97b9218ea840b0c7a30c1ae64a323135b13795d8f25e81fa3c4649bbbcb2aa1686207135f630c9ee49370fe5dedf20d09a51337ad97981bf4c21e4730c592d63c09aed1626f0483b8c5b974f0e41e17610be295a35f8aca8c8df4208266612c991c5efd4e8c0d7a0c818d9fa6d1358344da55407a08e14c399f18e521ab91a96000830e3d7a77fd1dbe2b1a18e317fec63c9344ceab4ece17f9fe27b9fa2f499e62bd38cc53cc163e965d5e2363f0a2cf6d96c92e4ea6e663eca299cfb235977d8ca10bb63dae591c3d0463e4623d2d395456ca857c845cc00463e0a2ca3cd138ad6f719277b57e7f330264081a1f40c118b6b0d2fa52864c4b31f932c6a8856321c3a6333976773c32823168b18bc8a5cb363fdee905c698051adba33ed32d847863c8a2b6d4f9d762b2ed0dc1608c5898aa3379c9eb39bae30a8c010bbaff2dc99d293418e315c6c9703b761a1dc315c6eb44c9e6a2513a2a83315a91ecd1571219ff79362b8ca6b94eb0165791c9b126da3d9fd8aa5105dd725596632615d44e8f759267e74234062a123a8625b92f3ba3ca639ca234c1c61153245bcc26393534c7fc4b6169f4b1b426947d4e2105979ba67e33bedf854681497159df3b7566dc65308628be9cc1de84c88bf1eb506c19375b6b0c9afe44c70005276f2ec964cfce18e3135be578f299ace3cc4b630c4f7472b935f53a6b5749e1608c4edcf1a5c3fe6bca593c6370e2944f316ccebaa174b309ccaf84ecbcbd8db13561aa5d1342aad26c1202c118993849b2aa5ac9276cfc1513c7903e97e4205136f307c6b844665296b09b56e65b6389f2a4063b304625d4542d9a19b2312871a8c91b35cab649e1e3c11893c03366dcdb6d2aee07c2189248ca936e376fc2c5cad6182312e5cb9b425afae8a6fa1890284b8ec12f2d589eff117f8e9539f3625279473c1e5a4b3fd8559cbf11580c66ebe2f3979567c47a82c737f1b2f6a65f84317c7687f8a7f2d6156166ad285bcf492acf134144c4a4fd5aaf6e8e089394638249b92fa6ef4324f8f4bea63d51ce6e4350769656dbd3e55bbb10d964f2f4a1d7a37942747ee6c935b6b2ce4138979f42bfc882305d3532ca5db2f87120bace7721fab7536f0c886aee73e52cfe031b9b295686589273fdd0c8892605354bd370fb50d88744a8da446b960fb6ac6b28c1b27bc0a7b76c4f12aa2429ab073aebabaf3ddc9f9e07f344d4df05cbf8613ca097b2b99571714bf40e7985cd477759c8246a074b2a39c8c50922b1691d9e753531d69c9c45523a781a9ec494dc3fb83e872dac8f8713e6c2ee94c3a3c9457a3264fdcd38e47d9e333ac7ed94241c4e6255deb426266f946f703f054f16ac24f7de0da7ebcf7173c54fdeb72131e325d124660356d31d3bd6b377b8063da488745ac9ac4f0d653c8b4f62be5062d260494f88864d6c7734f5331c5132973ac6ec122766c8d3493195d67e25d9a40c5aba60d1764912a34bc870e7133a567c0ce6798e0f5d766ec227063d63cb76fecc8bebc2504c973956670f188ccd29a6d464b29d79be90bbfb7ac70bc789d7259de0f13673ba8079aaddcb1734749c0b677dcf2477e8a8b26ea1245d06af282569fab4a056f659d1fff1edb260a51ecd7163c1750fb9bf1a2e4cbb426b53625638865cddb831358916ab8296b269a97b4385b7e4b8cd72b192c4cd1496eb0fdf38492117a9be68a28d495b14fcb8d8fb8f5262590c140c374fc172c2f3869f909c65ef8d962b5cd409c5bd6fb2144b8a39d904c4f493899614133ca9435a9242b5843d2e84e57025bea6450925b1c39325714aa3a42481bed18ba651a22a8f84cefd3b4ccc116a4d6d319ee4d0941be114958c6e3a164f2e42c9eb77ff04a9382e118c39324d6cb31c323a84a4149e9ee6c153c73184d09a94df346c82917021912749b95d53808147ad8d7db6454bc4e3179925a9a49c2429a45e43de8c11213e78f8e272cdd1cdeb96f1f368e80f13141ebd70b72ed57c6d23c983172635213fe6b978ece2934cb4309d4fb4d2cd431759308f8cd6ecdd4cc981a3404424071ee479e4629feee89e9faa1d3767140f5c78f27db0347f7ded3203282083330e5800091270051eb7c0c2ebc9a19379268bb638d9c660a2786c945cb5a02c464649258e494994164a6f6a3439cd7a65c40132d0e0c0194082704044048df721c439881a4084f09805f91ae735bfa24468467e68101eb238dd684a937a737e5b4644447cc4e298bbbdb19a27060bb580072cdab7b7941ea3274909d11f3ed20012a463c0e315a7186ff7c2a7d624b20a78b8e218bfadb37b48bbbe69888850807d0568ac80472bd05025c48cd5edca344281324810a40442d0f8c18315d97ddcc8089393ab5a060928008027f05845319fef848c3f22441587d8a7f69897e42679018f5464e2a7187aec2c8634e0810acf4bb0e095d35b127418f03885df596632c7254d160772c6194058081accc31468582c51ffae7fe4322222e2a30c120841c37994e212d43b756be80c518f888850a00c12f8e0410a4ac7cbec2fa90320191011d1019020419ec7289c9dd64aa12d654d8ae27709994728d2a9ff8fa1975c36ba021ea0d042eebe47dd8b96f74f9c2a49ee26baff46e99e4810bf88f0146c7c2c9d28b6fb5778797f4b17275ccbb265e269f8bed026b4bc3975ead568139326bcfe11d9d0e998bb92094cdeb2fce0e1d5828d892f5890b1ec308f4b54d23f17ca24b3ac938725d69cf34c6c8e173b2c3c2a51c5e66efe9c82aea77950226bb1fc415388c72490b390fa75dfb2aefd808724d6f0a8b99f19f6fa241257feb8b9cc4ca33ac6073c20715b5a7c0aab24dedb232222affe03063c1e41598a56597684d93ba678deaa11495797aa7b2b8cd0cc24b977ed7783942cc2133a76bce335e3731461ea4d4a8925e45a341351485e38b1527b07f910f167b1dbdb7f0fc15ae69eb06d2d6fa67fa07f983192010af03084ba69cdbf3cbe935816a21436afb8e64f884f744f5e92b40dc2dd98b9713324085676de84aa2df36492021e8130d8abb7fc8cfa5e0b88deccbf438fe769a778fca18d148fd694f656563fd492f934f95a65657d24880f41c3fbe0ca578e279778e247950f5f583a8df3d5d7b266c0630fbec85db6cacf3cb5ea21f9f7b793899d24ac1f031e79e8fa6a93209aaed633233cf040adc60ae366df215318f0b84339a92629c778b5b6a81d724d29ae6c7e3aa9322ae05107ad32bbdc93cc021e74f0efc2a764528979cd27e03187e246ea52a8932a653ce4604c9ac724ba92bc9f9080471c92e4987fbcc23a5d7b3ce060dc2dd378f3144f533cde90d47de222a269366a3cdcb025c9ed62f0df183a4683471bb8eb247d0e29ad6e260f36943bd542498579d0fc1abc5029a7d0687e4249101fd1818f20ea3f98871a924f6234cfaa72fe9606cd4fcac173667da5063cd050184fd227fa7966133c835eb2b776e64bf5ab98011d93afdbc4d08888081a3ad034944719feeeaecf9c27145801192ee041862d3eb947efcd18ec1befa97c5fea278918f254bd34a7a21bc5e31186cd2c68ccb82d7d82c53cc0704cb9ee841ad58edba5c1e30b45bb87763df77457f18217962c5aff8dc68646ba80e6a87a123ba63cf672c1305776966ae9fc4db6708a64d89cc653a69403021e5a704ae4bfdb2ccebb9a85a7734b8d761093e7c4c259a9debe711bc2e30aa5f3287965b61b256c052c6e09dea2d1a6e239448d1104f1211fc0018f2aa86bc26dc5477950618d88fbed9c08784c219d7a93db36e7957746ccf0b1061a1a04880f15f090c2d5ff7143ee1a1111a1008f28ece13a9cffabae668e88080f28501ff31fd77f29a65f011e4f38e5f3184caa9f8713ce592e67fceb2412274d78a3dfbcac6bf37bcd04473fa5628e39a658138f25bcf16a763c09f9b5110f25587ae9b21fd4e4bf78242113a5c4fb2a5645077920e1918e276f3ef3f114e77184f3a74bd918133af6f1435730071e46e8fa3ac5d49898370a1e45705f4353bdc37664368d34d420425963578e25775aa6b0a7b1460aa8c06308264fb79356631e42d0d25e8c21f6234f5283a18e669cb9247d97653b8051562b7fb1e8a794a72f61624d3b7cb1cd96c5e6dd0b3fe5b826e5acb361072fce1be2630e99e46ee45d68b2ba5b4f94f40e5d741e4ddeceecf839b45c94fdf7557ec258540d0d76e0c294349d10255e7aadfd16e5f850e28907831db6e0534fbd3556ce227b2daa94b564b9c717eca045a52597a85333b5a2cfe2122d8ae9b454c79295459b7d233cbe5daae0c582a812cf8f269c94f3b603167bd9a7948e5ef3b7ec784571624b555acf73ddef70855e314931b49dea698a0686b0a315cba5d92f49d5c081ff503570b0811bec60455966a24d6f8c821f3b56c1bee624e86e09ae25ef63872a70add2f7e8add17cdd808888067115ec484531e7f08c16a152198eec4005258dd44a675b933ede710a4fcaed4c826bfaf0cfef300573529e1d0b1fcd9f77604729caa01d5a627693e6e40fbdc10e52245dee381373a8778c22f172078d1b9ff1624a0d768882db8b3e2968deee706b4728cc72e17ee77f92580a0ad2df82eb5bf213eb5ff68fff1fa6e2c4135baa4d29da2529dda8067674a20bab1cf2e7dbfda43871564e9159cff176ddb10993b84fc9f1a652256687264a99ebe432694c8ab265c2db9c4b2569282979da8189535d92d2efd4878ab7e3128c76dadcb4f6a5656508d11d96206e4c37a664bbb5cb48258a5d67413cc6acf8eb0e4a281156e7b9aa1d9370ffeb35b3c7ec90c4fdf1ef3bb77589e1b223124db50d095364e6f920635b6d3ee28ddd71849dadec5f3c654723f0ba8a9e4e12991d69072374b377c722084d9a3b994c47c38c3d03088b8894b1431167b69db75cff99c96447224a96de9aec4b9a328e98a8942d63c1816191482010880381204094dd6d00d31308004850200d062391348e75710f148001582c1c3e36201c2212102010100e8743e15020140c04028130281012868261f9340d73690dc7e268ecc132f3ab7d08f69452903d13b378a85e883ec2f838dc758fa538096129cb4c33b99130036d228a701aff7e3b5d9689839ce908156e8876a96dacd63238a23ce81642259a5cf88c489fbc12573add7200e1019a7ed01bee7423020455f781a8b7cc0d6847d600a6c4705e5f2c8b78547e7340787a67180d25522367d229c3932dfa50e7e91cf38aae701f368ed0b804d6db7f001da6ff706179da85ce187f75f620017e6823aab5d87a13afda80f56b7a7db383f7770d6eb43024a1ac5105a687d458c79a614f2291751048bc07d81158221db1df52e851d2365d249c1aacae71662010ccea19ee3ce33dc8eb9329ba276c6b02e4f5ee949b4ecc74862dfe2714855681643c08b820edffe8a187b308bd74cfe8d6acde7af658757aa927618eb6ffbc5fd44aeb62181d5650eb1b084c4ac89b7ff9671251e315491945a05b43a247002e416c6315cf4126967456b8c5f57b96ae042d85a83cbf74c75dc0c1aaa0d27c15e3da4dbe4e979e6afcd9a1ed0e76f79ee74e90b765097f38e0871874b53800012c19784fb6323090530079b2a3d8510fb64e3d25529497bd80edaaacc7796a665aec52aa1575c5d556bfc61809c2bef74c2ce8adf722aa24da578cf08602d71ab8e1024eab0014924a2b5b756a9d513fc9c9d64e26f2ec22108e177d7454fa59959bcab05eadf1b40f7029ee0265b2979a7aec564347abd3c4c215b72f5a772df5022f4a9c1dc24d7e43e35c87fb18a909616f227f1ce9407812b974224e6d45b3293454674e6c616ea8ea3cdb52b4df969e571711431df1e71f50acc95e85d84c28f5afb66f78a3b7f8e7a6a5a254bf41d082fb031c92a3f5d69c91072a90b2c081af32e660dae5b2d27fc07804a7fbd48b0749691cf42ac9a9b1aa3773864734e31b509d79980376459fe0fb0ad375f269f36ebbc58dcc25b083363feb323b838591210d99046f0da9f93acc6adaec55355e7a5cc4e2c88c1962b3253a64f29dc432d68ecf0b6585bac46c33bd55f6184ee8e53747189a55e9b84c2e5fbe274c56c3a84909e86c23a6a824cf083e9788ad7d66bf107f45d5748d27db6ee87e71b61da306051d21aa0b3c4b44983695bf884c9fc6ed6e9480e05023abaac13f7ddd31a7f7376b2bf47310ea45eacc788696eaedd5e3128052e2d25abab83780425951710469c3ce1f76570d1fd93ead37b00e3d1ea9c5df25de31e9cd6d6e15a5feb34165b585a81c5c90eb84a4a2296cfc4620d1169c4f099457f765b3fbaf8d6330239817b449e2d601641fe524a61b8ca6e3cb0e270316330a803327775d41e721ee24d097c8738348064230b5d53aa8d88e3585b78a7c10a889d6b4f7414edae07c077ee0b6222bb03fd035b670722a3915367380747b7dabd01513dcaee369715254f5ca17503d4658da4b846e8c5706faea66b16be2a8ad0ec466ffdae459d4465da9ae219c6bed6becda642f146a1f35728bb2a774c947116b656fbdda92b2170ef82173dc6a4a400280c9e250ec4a24f62522dddc96fd835b18f7aaaa3100183d8a06edca3d9bb13dd280edb94e9fd3b4c3130c12078f221aaf99a0f0a081ab14e87dcb35e0961997e05135fb82d6f2c2fe140348da95e252639f7e1d8efa439510176f44324ccf59d64a39c78f0b21005402ee3ac1b0502912665538e4cdabdf9302ba6162133fb79df65c422437d0064f00796e189404d09d687946eb4b175bb87ea45bd952bf352372e91d50079ec6acb342758dd5aad4496ab11b8b492936506f5b9ef5d856b1497a8bdec396d6134b1fd349a2377d304cfc1120dd1eb560853496ee75feb4ebcb05cc54fa331d1e0c00b12d0d3a8a5a33c9e79ed2727ad59358c779e75f6c8fbdddb1a2107397fe7d4d68d3c9c99d117dd262a1deb6fd7e09a9a228de768be1800abbd3455b22ee856cd9392565bec08a7fa65d67a763d912bd7b38c8b0e9b23eb6b2a4d2beed3305fe029dd3f81ef0c9975846264cbfefddfd9399eee3ffb78d98f41896c5fb8036943969b017789aa33cc5ec0dcf7098a693468b4d572403eab50601c55540f2c834d57dacdc41813e538d3e8d8d1259d2ee98fd605b782740aab1d4f1c44c8adc260d09bf870ae04d50e9ed4cd433527d2c07bdbd3a04a3cae1a492acb767e178b0ac026ae88fb37275d9964e429a9bfa0e86ea9c33cc27632c10a288245f2cda988c35475c04f16cd7ad6968396109120f286eb6e3e8329c1ba03254782d679f1a255a74bb2199bc4ba480ec94f723e338a5aa06427332ebe6092c40220b17546d0d041f1a2c71efa8cee5b412af78e8fc484948a6513c037fa2827ee66c3bbf70aa869c02172828638acf52deb1c9dc432f06f32a75781e8f38a22b9c0eecb925a4b26f5eeeddb4d21d8ae6be7684b7e4e8487914c797fb639f879d578cb91a2860a8565e705209b6841da23a1932e3c9df221fb1441e10fc70c852d7da8c24a22238570c43c08c66e4c6e6f752b501888c76094ab83c82cf164a1c9930423b5ea3ab8fea16c2c1abf648c3f2e922cba67dfeb394096c4f96de262ca294c2b0baa1c180de6630c7e887268836a3eb7245dc58c029dbb80284fb2a82462b4e39e8b16a7ba9d370e91b5758034ab818da4acf783503f9dc01cbf61d7326601e25cf2e68a9a8d98bd4a1af400fcf3d70869f630a312fc5a0f10f1e83c49bf2bf14a72ba8142b3420857017b07ce3243e219ac64256acc6bc989840608db4b7baae85a93c07845ed7e8c6cd484a57da27cb2f421f42f9f04e70299ef3865947c3345ba4ec3dda370374659d6919dc6a0ccc7ec08daf58b6b2f200dc01cd21a9f46ba6118a14dff40f5188182ae2ce902defa845fc20e06d06e28e9869188caaa07e2c86b5f87a83bc3a824448005b07e358c9cfc361a86709be5a13e25cd2bd4992711c363b574bbed0939f980de5a584b135b0e2ba36a33de483982a2501642034b1de6d76bf5ece0f99c9931b37656866fa058ac8e38c351ebca5ae68ade244d5580f160f9fcc00fdd2468a854857378cf3c51a307f03053da813f732c2c5a7b0161b95c7d4a4e92b465e7e87d9c64facf724068fa161594e51abb945b480f498ead4fe08847f8de607d80691886bc8c357c773c17f0ccae9bc34f9f267312b4de020b843f4fcb0e81cec429d623f12914215b39c27218b006a47fa6346003bc6359011b923251c8ca478cfaa70d89f864e22526710d457c17b704556be2a4f599e13dc2caa571ffa4006f1b56875d5a40b028d3e7570cd174a163a15b36eb2ad57ca718714787ca2eb9477b4151bf23fa3adfbcefec9d1df47efe6e1286b1d7594d09fe7d3367420ac838e4883547a29da12edd7af1c2e6786450c2a9786439f4436a2a7482e7fae0c2d659f11b8c092abe78bd6d6148f57bdb63f6c164ccb3d9350883fa88b84108108220525b0304d9f24310b7635e59be0221683215097f940b0b6689a808a0d2355378fe534c26ac9fd7acf9807dc66ae05fd66d60330bfd8a0ca2ec3abbb63301bc89ea5e55cef0fbed7b61f5cdedf578b2b35375405532f4cc132e0927511bce9baf519e527fcdf4f6018e8dbfb0b8777de88817f29b0a1800534573981c844de1706bfbd02265ad88656f6412579894da351251c1266152a1c6874aade6fda516c036d53df166cb7821a6e09aced76520d7c3aacc309e665c94ba16780caaa4a78915e8f74d67bfa82379a7fcfe64787d7f553b623c3c5dcfa7e0a656c76eda0dfe3ded62f19b830c3dfd88f1fa0893711c1db92f3345ec6c1582375543a94be26ddec6ee9a895ea789a85026044fecf6ed0756d32296d290355aec97fb772c8e05b927f4c9b9c4e1371c00fa6cbcb92d0e0611ef7bdb2e5d895af9de6e9664d035c1259eb2af33ba656ddce58d5baca57c4c49e0bb57d63b904a7d25777d6e43a9337dc09200e058f76e32e6d8aeb095805fd9befd8a8b4b640887282c09a550285559982cd7ddb24733c956f13ee9ee0597ab2ff4123ba5e2b40c0a02d50fd9aa900162969950bf28756e489718d908d47af00f660afd19712df237d6788db0e78bb29cc0f3f2044bc25781a8536e076c4012beada05ec124e9af42a312a1b106b1e12fa7f817daf99d9cb0cd782124b8c4cfce1c0a4597016985b9470aedc2bbeb2b5ba92ca33d4081e42533efd7904e3fa4225d6483b4b3168b95c87b390b7652415119b12b54d735218c0071834965d8d0aa14132ce344411640c1694aa5f35a250d06dca606f1e38a26579996d2d2459d047dcd4d2912c94be5325007431be517a92d46b47bb573c23b3ce081eb5aefb8faee23a4b609d7b50f5afc64403f1e54892fc66d40a9c92c76099513521fb60c8b8bd434dc56fc6057dda0b384842d904ab9039d9b82b72edc1922cdde0f49b15f33e7a0c6f614ea064d6143c0e16070031b9db9568f146d55be675f291801083d52990d45ab7e49e65121414aaf3148edfdf193250d121c7a36c5221984b7042a338db8d1937eb73a6d79bb7a406becf34f2f9133e71531de7d8d4158c0def9bb41c4f32c83598e83740535a2de6bfa613942559dab3052dc303d6b01f1dd56996f946c917f18313e936cb167b4ad8c15f6a479c713d6726c318ac1fb86835b373de711d3e052a43c6dc48fc4047576164be55965bdc5ca07e07e8dd9ba4f7c09d55e6d803e97c7a2c297cab9b4ddb3993faea11b35e66c2dfd2a67d9e6c3aecb42825a374d9ec925c971c5931299c0f8324d519f2a7db30a2ec8294b4ab684f14cd399c63a5b7b333e7dec2c0f4fbda5d4b8e1cebf535a1841f27b05f68ed1afa97274ab85859b431841ef88172a787b3f3695494348d3e4d9aa9c8d95e3e62214977ef99fb114472fef40e8b825206d7553cb90b04ea6b044bd1ce42ba7f42f96d740e50ab664ec39fb8405c87142ffd401558d17b63252eff15ab68b3707b579f971c31b98e4c73f16cc99e21b8ca3da1abc8ca9b9b2858a2def295a8974fa598baf7e3ffa111af5f39f9d46888db008bb46cf9ec7b85fa1d1c058777dca7aa5be535bbf6e0e7c8b715a9e5894dae7ce40ae9aca2ebd8003681faf4de01bc652f9a50313376fbd02bbc81315a7e82304c960d39c275841882f5bc724b9cb084c786a705a9772c4dccae8aa19a7c5aa6154c1cb7a82b955841e5dbb0549e9aa16161b33d2be9587267ade345aa7539e8ba23d53ab6c4263413b52161edd6476a5e1f8e86100de2bc090e9a0088284f23f2d6d10a73ccf90d4c723c5e694af9cccd73d844f141bdb8b29c347f82b44eca642d18354ef760be430efc65c2313fd75ea656ed429d66198ee85a5048956dc6c9e08c807ada15ac7b114c0ddf24a859b50b91b5abb01a149201d3669d2e1428a260f99c7e9712f9c9ee4b0e1eb4ae29facfb967bd21e76b0618f813633ef44c0404b57953851444d6a729084c98518fe0f9b8a4bd8ff083648ac792ce9abde5030c4b27d3a1f22ba0186177c843157553d6369da316fb20592320f400a4abb5828037f0b858d14d70ec87fd10e9acf2b4619468c94eb7cae75352cc4bcc7a60f649e908fec31ca64ff4e16b5897ebca61209fe4e3b39b8cb56a576cd60c9365764dadcd4c9f87c5ace17f7a3e493d9f72fd9498785e959c2f0c179b83f29cc9b5f69aae832d840a6c036a0d9b5ab3523b6f6c26e08dad66a93a675b85eb5940e76a53fa9aae3245825fd2ca391fc4dac4ee6ec249ed77e7a8ecb073d0070ccd378d6b7531992582e8f66ffebf2e30d543b16a24b3e8ed2d9c86d4e8302cbba1d7ac79c10d81d3ce748efdd9fc68de951a00cad7a9eccbe125002bd3ff9c64c3369722745d3c96146d0c6efd37d97e34deeeadd6d68ee0fb6c2a810d58ee3f83a20094664d166349ccd24565c56d3666288c9131d6b9e1bed72b378ff3979ec83b57f6309851cb29e0c7acf1420e231ac387e7c20c2fea925d9e45345fe7c00d38f8a2c08b5ce24d14a1beaca2523d44d0d17293a6c8ec93348bce1cfba834bee3ef1d0b9d1549e7fab5a05aa6d59fa14a68259b53faf6c82d9bab4a5648a26a6a53be095b0eb3c6414f24c0bf0a62ba8880bf67fc37dc81c29f91abd24438de0559178563ea13ee0d69992ae7fedeec5590280d2c04f236b3ee40047417d63d1b436850bcec390915d87dd07008e22548aa353fbeec4730bae0969effec2d77a988c523e809336820e891f24d13dcd09a59750fc2ce3eb4898d1e59090272dc265ebbe6817615178f2b3ec69594e9c4de25f0bbe749a09961aeed4e049d3cabc6a1d3058a172d033811597fb1c0d8f9a67602b6a5838f0e604b60b1e36a5d37fe8f56c96c1d47b42f2afc3ac56c5c8efc5eb234da9a097b6429e30ac44c160b6c8ec9dc8888cd57a92c464d822e50c9818b2cf5e18afac24c66101ccd1e585f208d59c91ce44ed80c5c3b7ec5dc6a945f401bcf0fa2245d5cf477a2161c0dc56e3ffabaefe5a5c8deef03fdbeb36cbf2e2d5d30ee1483e82636eec94d2bf8ba06c0604a4e20c88990d96891df4f80720d8e381cfca79de8e34aead3fc39344f6d9c865d2ecd3a104ac0f906415c6cdcf588f81dec29e60c5ecae2508eba603733dacf4c3ba008eec25e0494844a5a6455ba99aa3e02e32224d62e8ea7c96f9da05f7c54e24263cdebba508dd163cc739bd86d0f2122f8f14903866e127c3f7b9839be7bd90a23b28615de6987f1a5c9926e10582c9ce1f0eb5a967750ccd8b0f264dc0e3805545d420d24c927ca3f7f00abae8e6f1571eea75eb09cb288debfc3b12d59eaa65262990ca4a98492b9cf7bf659cd9d892ee1ec314a0dac1d669e70e13ce8339824e9f95bcf6954aa41f620c8f59fad235f23c40b4c6efb499f25a10dd60a84cabe00e6f6517b72c66206983abc642ce3135845fe84ecb5f930fad10c91a5b82bad14e1205d80d514241431f468373b6516af35b69012e2faf84f31bc0a6d37940c2bcdb03b63dcd681e42148bb6448087c4ddb408b826ebd534c8b25b0110d1091b923bbbf811bdebf921305e4620da205057b32682a1354a22d49acd421ec37b5345edc689cc4ab35cf11c1e359cb4119dac631d07b8bae92f75446bcbb08fdc6ef779c356ffedbf2bab52f10011c717224d1391011d217a2f7cd2f0896be8020c47471ff4dc9a7486597eccc7aa4650838fb03911a0f3e0d151846ba5e2cd82a676fb20dbe5f03d7fa825d01263cd9970a102934885e2f73b8501d613939b89d515a8ad21a3f7693abac1a1f095d57e501872955f4408f4dc24145459ac08b714c4413d27d4982eaec3508475837b723645161ba786ba9697697d65212d0c7c64083e0fab52772cf582d005c4e56f8737edeae0963b8c0a2688a7f3d97a5d675c8587618fe9e2aa336d2a8df27dd4dde9be8544f0c1a699cbb7fece84af8f564ed60516412075457fbbeeb6f840a47b6ca5dca91af241f871fef4c57eff6b8d6dd88f59b583585655270ac26011aee76482def00f49272ab0d8b0c4430c1bcd5a27d9f4a34bd5636a152945fbff8e2d7cd8157e349c404d77dc30ca3b6a974c629723883e1712f2150e86acaaf790b2080ce14de9af3276c61990d0311abdf1153243770b8ea9592c69ae719514e43519b4dbe362463e9b01af21e04d8f5a6e897dad776a4babe510dfe66d275e1ed3d456e702b8add06aa55efcfdd1c4200896f195f6f6e768f192dbfa934bd76c31d629a2c8ef3a70998041014da29b2dd018ac2a512e3745cb3bcb4a75c535139caa28add76763a16878d937c13ecf397e76f27151cf6a5442e61da72ce3793b5e484e3b0d7729cbba9422c8f7d25b53330ee28df39c94a40173d1ab35cc35e35676b3a4322ce41ce64d71fcac4f57c42e991eab288294abb159640c4087427f26e9cc9539d12b1886a731d8430515ff910f93e9a1ddb74cade1a484f42e6225a6aed443f027f06a37237c1e1f07d51f5f567e56ab52fb8a9cea3ea5ceb61f4813edb06f0e3645c393e86c1d211a2356ec10f2e5e97c3224c37659379dd4416ca4f22ce6dd991047679695791fe9359c8aa240c6aff3ee2d3b2b81b88e10c21eade7416d0ea4fcbde42eb07e467a723f1d8269711873996ff812601d9f53820d55a0e51ca80d311d9a0a0936df2e65bb581721d7af3c804a066331d10add05538a4375ef7354b01fb8de7f3b98bed82f7c58fd3994739181e22c1e4613912c6136f56dfabc53957baaaa5cd308519b7e0a48a68cd2636076dd31be58a4b7f69f2b1c15d863f77b3269a36393adfdeb38ac00ef78465f954dc7b77b94966ecff8f639df435eecb800707bfb01c41d609fcbf3894540de39093e1a5467dc15465db1643d245cb4d199cf2940e771f4929c1c7b41691fd0895340168399de58f1545aca2b974cdec287786860ea3d28909e912b78a44a411ed0a5c735113097037a65b05f500203da2328fc63b016bf4ae75cbf36b01c37ae2a01711ae56a8704121d30557c173a24bdd46b5263d18c81f3e89e5723bbb0840e5ebe22630ddd741a83883fc2665f2be005a935a3091317e02f44291c0c71ac384ac4077780d847a666bf311c533bace117e96442aa34554169c9da05840f9a76b87718c788d296ffad4a1103041ac90c69d3ba86bad06fe90d9c72bc5c5175f4fa3e80d3b4019a24425eb3036ef4cf38f3225dc32e842ef1f13b0cbb4b0aa1e9b1369815272db7459cec8a3561fd012eccb01017205f9fb41a7de0be8c8343e7cd7dd5e44daa88005192d84451d2d15f0469f7d4c2846f56dd1048dba5b2c6c6ad744501bc9f5748136d241454bd162c908c597c242979ebee80dc92944e3701caa966f2f490ba240b1a227cb8d0cd2be1b3868c1763acf1b1e078f8e3c570e3005851db89738311df1367871d34b17ab25b029d4d492e10325bd5a21a7f44fbb11407036169ecc6898a9ada69c41213708b86ffdc0c36b4c87b83dcebaf5e52e2c7f91b270962403e7cfb17253123ae779ee092f9c8f3ec4c3aa1b6ff381c8399115c7a261629db79673412abc04b09db09ab32742cd2c3c7715b1930098098329eb6a96ed32102a9e8df40c1ccc6a29ac1abd38806cd630ac40d4b6fcc4a5d9845c87ed77d433ccb11cdf01f75cdeb0a68771235560e963b14bd19d8067f3169bbc7ce958aab154a13a8658540b827b6402e40fbfbd04b023895a59b887122a71836802a18a2296dec88815d0f014ed3b8a8ee7bf3fc5c30c08fff6fbd50922e255407e2bcdf4aa0022533aeea150a49dbd24899c1823620a274b35745e3a3475b44cd2967f3d7ad96c0e5eb41076b823de0fda4397933bc4809a394f7ff2b5d67b8d2ea1ccdd7c37c3ebf78e0e92f96e6c556362c9745eaa31cd14d155c82853f52570ebf8dc80f29145434f46cefc54494f099c49c4d0f689ceb781e6672e882ea61904eed345236a2d9599d43c5d0b5896b42c16d1ccb2148fdea8e07c025bde498db5e4c7cba50d242593b4e31436e0ddac99043477e38d68d32c1c9c7338b4cfbb5268de88036c70cd09ffa545cc08d018c77901f8b329f7a22d60fed3b27d5a139919a8b46a74ba0052d61e1b338b3676a22284bc90cb2f4c716064d3fd2a3ca6f1b769210e5daa55c333b70b8a966e8cc9077727d88c32b5e5880f879f341a93bd7d24f00e5ed0a14e9d611f7ed2179caa2fdeb69571674e03d861bd4239619b9344bd181c304383776af09a59219d16423a37c5c5db4b954ff0814042d37e1194506ce4d24b7b01a11018f825b80de24ae9e59c7789d1bf74d5fd395a947672518389258954f02c12d0ff4c1ae6c6e17f9590ae9847068ed25693fb074bf69bad935d7a3f75b5d363288189fd90efdf29201903d08bdde3e3825b499a975f3588250cd486ef4755d1c6b385ca143d9f2495eb71330dac2da28d845c88494e495898a058044c059b90a0dfa806185ba20d39ade68111f119a976fa0849f675eb44b747e7fd66cc13b68325a7c8ef77d2c908bba748506b3e3b20c84784f1a2870589f1167c902e90b8741fcbcd87ac47aa0afb0358c262a780940c4bf50b276932e73e70f5974b93d90aeaa04326d071d0ca30e6fb7135f8469163429744829cde6c52dda2b8bbd62d9b515f2332d28f029828bd1147e9eee5c22cfc57a62006d70fa47b3e04a68114e74ec7889bade0779a19ce4e791022b4ae32f419345adc89b54163502f6c1229af576507cbf2099b78d2c2e0881360e2a1b4f7a72ca9410d4c0d0287a16d7155ebf167389da3b57e8712db89763537f32826f4ddb6d032e70da4e51ea75ff300598008fdec47e8b81d3d840bb180457a8d5576b269899d79db89f638664ae1c233458d9322a3630200e9d263825ee75d51d5e2975955278de9da08d3537d426f4732a1c94cb5b16c9b0c3567fbea61dc78f31952125e012acd322ab4ebe9fa14964dd9e64febeb547092f0a2eba650d0c685bf04e5c62fea63377131a270cb4d2cb37d6b483cb80b37930f04cabff0128428474091865989487cb62fa043297bf6b6d0e11c86dedb559321b2b11c3230dd75d2b07acee41559e73983289939a7fe14037fa9a3453e95fd9e670f6a35ad61f0ff7e8444b42b6c8da30adf3f62818ba1b9fc2c2744ccd14b44a8db1da2749624480c7c07d66b7f605c587b134ec08947c2cbf2eb9e42b58525d18e662941b7e2e343b0d4622ea31b59a35be70605abf5365028bf6cc4ad7d8cc95522a023a7fb15c339c8bf7e080a46b812abb24462adb069d0e7a7f20fff522dac70693115c33e4bcc47b359fa281b310f3b55f1b283e3ffa17e921ab477f13c1add4665601224c7ff0dfa92f3eb154320c84cf6dd8abf351427c95807458ad3b4bd7d82da5e9e90b4edc8223467b79cca28b83fb7dccf827a9c90eb0cbc0aeaa4a1a96d7aedb20af4611c13278aeb651a8e4858c7e56e9d1775fe034a84001a58c2cddd63a34a7b855733cf1b08197fae2bfc22a201b4017df6d83275eb99c1b0b02c2e34d848b661f8a8f35715c38659b5aba58bd86d055f9ff9c37d97eaaa1bf8e1532abc912bac8adabdf34f05456e61f8571ae9366757cc544ab5441dac95b530d20b178f42355b1552a1358e30e7d3eadea9b06a26ce4b704325dd22c7f0875fa538d33927bb4d2094badfa7a9d78c3a49e4a8772aaaf7b83f1ad3bb09915df6b4db2e324d5f24124f8bf814cbfd148a1114d889d02cdfea89ff28dab85a7f0919d59c896241be4fb1e73c81a3649014123f174fdbf0687b4e386783ab39757265dbaf38aea7de1508a730414766d1b54929e3f82b029961d51600e9ee30631df86f96ab2261908ce2bef1dc51ceec5f09e20104dba31b1fb0c7af2476fc24a249e459cc51823513027db973ac953bf905ddd7b0a04a99140f52796f4124dbb101672819f7b0485d631e51ba0b7fe4f41cf91ce65b82636ebb39d814eb754893efd55b840196d47212a9e202f6eecd7bbc00f2447e863490647bcadccb16d70dc0bdaa570879d3b8452262cfc7a7455cd1f20c96eed62e65618e30ad051ac973c34e23e17ab20702aba59dab0b19b263f95a828c430e6be16d4efb84692171f1efbb695d3a4bebb42181c283ce0410e44bd0fc6967b0d53e4cb0558340d27c26efcad2ac95541d3f817ce9a78b62e9fdf129000ed331c37e358df7ce85bdd7de32ec86e5722de7fd3f13e32249acc17d79afd760cde4fe121082f2f12b1f5b64c7c50e3ef8e3122e22ea5d0169e886e6023e47c780fa2b3816e949ceebba5ba51617e92f1aad221b02c3888cb243d8adeab19edeb07226bff5a15fa642499bf85c05fd9b5e8a0f441063706545e3f87642b6efeea696736ef3367ea054774429bb24f2237dccd86c4709695532ca97dee7a45f8c3d7b54b8c2a89d6e45cb98d2ac8399510cff3498dd6ae72d66f0c74014a6daf7809e552b4fda7da727c77372682b01210da4c8e278e83ee545b5756c481d3aab87e625ad5543dfa13395777976327fc7d462cf20a94908d0497c4ec3afcd940eb24c615da9f1c288a220f20f784cd21029e9fe35618535a1e30c170541b1f62c34b67555e10a352b01a6ef7ae1420c0a979e0506b6ac9e2587e2304f77d1bff386d8d7c16a743291154767cb44a1585dc378a1e18b395501e2eb2c12c7049363eec2408bf317fd5f1590bc1355c714bfe66562db820d89d709c0bc0d7b965bdab4d2cafd4adad771869e1344032079bdcec1ccdce9a72adafc6f5ef32e5c0b7ad0ee28585ce3ec9da7596a27e95e4069fa669a385ea67a68f035884c9331c477059bcd143f068a32acbe63597447134cd5a95d97cf2d31cbb62b47f35ebd465a66f3d2cb04f0fe4260eec6a9c662ecb460dc86b5c32b568bbe2e05d489b97513e6ed4b611f7d1a9b45eefe80fdd8e3e10c15c18854d5119b9ad06c8ddb3695a1d9d2d83dd873c980894e90b57ea45292ad5501f5454d158f6c19fdc9a8c316ede23af0d1d5eba28aea43c605462a4392d7923bb7d5d35525acff07967ac5b661aeaf7d19d60fac545362dd7225524c33ef810e4be8e49beb8625b48eafa3dad14ce0c2c1b1f3d6afc6138fa07de67169a055ab94e1dfc3ad9e43bb3450369e981a144a1e86c9af330a67e328d0e45ca7555932a068cb6722ca1127d5f11728e19fe1c4006c19976c8a68e9067ea0c33cfa3bc568ca39db8dac9e7bcc648ddee9e80e9680503ca55e4cbce0d2e105aa43f0d5904903a8e4b98d9d9efef8783cfd63d613c44e61c2c10d2eb3bdadac898ef75901811c7ee8e2815c4b567eba5efa285b176698c49e50edb357c8dbdde05712773b9707385fdec9440030f9a42bcd556b9bf3fbde367ffd4f6ffb1f905db4e56a53e1ed0bd14c0321874f00a4524072004de38f1d2f62952f6c3e4a2820711a52af68302c26297d23faf7df7022c83bdf569ca8e6c94efa7ffe9da9dc8b616564041aaea42815b40f6a303b42a85da92eae710de03e25e60037fdf8119fa167eddc3c8990fa479cf58997a01e707fcb8acd5a31a2470afd44c8c4413099ebea74034b8f4524a6116bc93403cb323ae14e6041785493656196c1f25bd08d67a74592227de5f1eadbaff438fb38f0546a58e69e3901de2e3f13b2c12e53be8f8858113a2cae40cb50c8eef3df98aabc429130136a3cf2f1bdaa1851957bd199898fd09741151e00c2820f9928b7112b8c1dc395d76b78a91d8f42a0ad9ffc291b10a8fb1e28450922056cb10eb4c5e27fe0de5879883c7816ca59c232feedcd0501fcb349e3b23a47756fb4153d3c3daa2d882a8c45a32b0c4f6bcf0e1661af8d9fcdca1c84ebc232a3f97cb4a4a0055b6d2df0d181c39026aaf511619759e6c803e7aed6b8eeb593ab796b1fd0a8ed78ee8521a5c8d498fa9e038a5cfae23226eb690ed8ff2d2281541b4523361f04607e1ace934f7cd425c5f0ed559276390d7933810716493f6553c5a34c179d9c294201fe5d22055dac0bb4d4eaa7e475319aa13b12a581b860b7d2bab77985892729da53200cd5fff03006cea3a3af7db8f1dd26ad604f44905400f84721fa3438c4e377e0adc78132761f471ff83f4dcde072933357a6032ef2305f43db18ca40ea34618f51738a382d38e5c88b4dffa8379f75cb1fac4b644fb3b996a1f345150f911d182a20c6a514b988ada207bb69e93867e1652216baab23f5219525cff85197d33cece76f55f45a9aad407ab5589ee01c654aa3e00c21f6f2559ad32c2ce53e8575ed9da92876bbfd4f4e1b8f7fa31f17571a45b3bf187b1f106da34262fa06d7900f27031637557d7e8d4a9ffa0b07b8b012bef6023bc457ec29e7bffb03a94d0c3ea035089f22a6f19107354fbd0d484f5042cd462e865e3a09ab19b62e0016e2c1221c83c675652311b1d9038ddf29ff28cde51de2f8d2e416a96ca9f8861536ec5456f83854b2f15ce02b622f95bccb0c3021ded31529010be97a93d0ca23dcfe7dc2c25537b76f3ccefcc86cfe8c1a80dcfe695aa10d867010273ab3e8675bfa7d437899165c6f627be19b8beb69ae74f21adae163d853488507c4426f44a6521060d10c2f69aee0b6c16f67258d3eb3f1c2ef3088596d3bb3d5833f0c7a4e09cb3553e5fe4f09de40b05e1636d72b67230a6b3bdc1c320552c7a1f4b165e2639e0d4fcbbe6bffb5d58ab49cf0ae5006318ffe0a01a3262ee5caadf50ae62e3a42d41332f3a7729096f9459da8466fc1d7b8157c31bd089b1e84a20491483473af4197173e46056f1da8d60f11fbc9ff1bf8fcc31a610fae15f8973d03d2442a9f81d5ad64d75457b7833d4243f026a3aacd4fce548808c0658f54a68b56f8e9a29141d0405a6e7d8166bf0e1231bfe26b0f5808672484b5e8d2111efe00f89fb801e347bcd2099d20d46b4836535c081d5e00faa5fc1b3770b62c0730c30c33cc30c30c33a29919f7be1721246bee4da44c4969c7b596edb4d3ef764a524a999240693d1954ce89468ee4d8a18806a0019f01a30168375d4b6b195388de4e69ca53777afcd92f1341e0628abaff6bc97621a5aa9202975254dfe76e42d9f6a195490a5bbb0fd5efa35aaa758ec2a92bf1a974a5a2a0d695f059a263a7cd194a5a972c95613284c192050b4c8dcccb0b284cb9978f8c2be5274c29b63019534c4f96abc584ad29353be15fa93e9fbf26279bcb9da9324c8ded5a8ccb26287dbe53aa5b7b885aa626aad131946e25f7cc709de0928923bff597acb4a5afa52e982c86ff5855e2bd049b173e46e930bdf6a3c0c512c53cad2be4dc964a29b5381a2d2f2f15b85442892b62aeba73e8fed19bc4b96dabdbac9c2ae55e12e46648edab6d91786a469b50396aca932151bf8c9357225695703db21ef1faf6ccfa1263e54837dab4b870d1f342aa91b72ef755634bae9f26465ca27a4a29f412f63f5d04a5f7927d9d430eada78abcda96ea5a2f64970d1351eb5272083977db768e886b4fee8adba5e794b6439adb9696dda5fee5685d0c69ce5e6d3f628bf97b2ac4356eaf9e6ddbe7d5d08510f75a33db7e8c2e8330c594317c9bc92db7ba08b22b35d56b978e25bf0fc125905cdc542ab692556bd802faf09f9e285e4ab9decf1e3e64fda0604c2ead2ee2a5ac9944b872d308394fa0c5a86cadbfd4d8a172423569e6ba479e1e4b29e645066692d3c463eba1a6fa6f6a55352616b6f4ed2cd15a3bb59e6a262b623e4a34f2eee708bde4905a7492788a6b3b3da2f6d59e0709f56891f13fd7ec1bf11cf16f392564e59e7afb7c8ce0ed5ed99bfa35f49ea788d7d6f7a3fe7c69dd5a2292b79f3e2e2b6384ea0cf1305f39aeacc122d33632cd81022f2f6d1c21823840a0eebb84dcbecbcddb0fbcb29be93f571f4e0febd6a3f3c69c0756a74ef51bdab3435a8ea1a67ead362d6a1d9e6b6eda4e71be707260e9acb9d65edfc141e92ac7fa11b9754277835ac7122ee5cd8c9bd686d41efa956835e73e2d756a489816e1f2732bb56f7e68588fdbd2f6127247ddfccce0accd99a12a27034a64cb2f975373678be19d2274cc9a5aeafd0b437737853879526cfc795e7075c4d9a8a999c70584ab519fa96ecc35f6cc2da705dc964a977b4b21f6ce84c342b35dc4dee5a6b7a8a915547fb9a74a4ebd4b5d128e0a6f3dbd742c9be13b849934278544fbad0dbd9669f9699670500888a42bb23f7a6eab90fb9055ad794beaeccbbdcc8f210857aeb57cb93a57ca0bd1a569dfe32e3c843063a59463f6f8dd433b487a9ed2fb7f65b7e854104eacb63587abbbcf5920cf9e6e7b8e8800596e5fa9d55efaa7bbffc7db84bf7a5d55d3c42de6f8a11cffc36feafe82d3c726b35ab7de3d5d73e6e0f0c19c164be547dc6cea299c3d1e5f37132db5f4689b182ff54636270f4cd6e494eb6288f8a9110e1e6f2de62869729bde6276eeb0a38ee6c91a312355f4bf4ec71cdc1edf39ea46de7cc859a1711a9470e478cfa7fa9531fae6d44e1c2b39565dce95fb630d0e6cd9da69b364d74bbd377c21775fe833b5f7946ef06aeaa36ca58b5caed3061b6bec42ec595d73cc9b4cd5c0d4303194de63efa479d248d79c1af3c6d934d1a1a13261bee688dfed0d9d334038669471c838638811861e30bef0a23d57bd4af868a1c4ee621f1763daefa59a080e17c898af4d9adfd05276b6d0e264b190a94f5f2a25858c0f168ba915afb49aa91c6a57b4550b93b97c6ec1b1c23d7fac1691da53853b53c5aadf43878a44f6dc987b48995bc6678a67c9937233d4478aa6cf6bbb17030da4acc2b04a26a6020c28b0e58a4d8c15192c55d081b41c400bb84dcd0d0d01016c6955de2500804dcd4d070304e0acf48d152c7de500016a689cdf7c161a2d087c00003685ee9d2a778e7dad984d74f3f232e36860489079980ca8c8a0652606858139800a0c5bb4f80b36ae66c6cd1440c50595b1b91253d32da88ccd67996141656c4890f91b181804a8aca0323634313557606010a0a282015452786f396efc902e46ad65860a0a895f5b7deb1875a77f35a7f4f36689fd191536d6794c61cd1eb3de978ab9ce16f3f2b2458bcfb81a2c323657646e1a0c8dbb22a365268ba353ca3edec490e1727cc67f4871d5985d23b68c72193aa32026a76ca93551d0a1ee44b9eddf555ba1f05a9ee83dc51e35720914c76dcead39aee568a54f3a5d51ff4becfaac1e83e3095b9bedae30f9f5bb9b49a713e4c5b05573cf9dbd95312f2feda6e681c38932550df17adaad0cc1d90493f17bf4b4cfb5df044793865c3dccc7d50ed51d9f4cf43df4c997bdf6edf1635e5ef660d24bfb29e512bd941853129c4b1ecbc6fa9e127137af2595503289249140f28823ce0ba9cee6bada9ef21827427b701a79b8ae5fa3cbf5369b3b8c783fdda51a6acd385d3b8b30538b9962ff1cbb6fbbc15184a52a3fe794ef6e313c8928c7effcfd63557cbe4e819aa85121228410420811110992064907b1848410861032ca3154c4071240e02886c430084328086210846128062186106408318e10438891e68c1b0568dbe63fec3b345591660751ffd0cb56f3c31031ada541c1e54e424b92b4af1c60776732c20296489808140bf3eac5b2d039b03a957d29282ae0dc89889ea26101ab60e5db59c42fe97b1fe32e6e459a7d7ab7e967da2da762f68d3b5bb9fd61b05effd0be64d33abc26baa4be23a10350e5797228e080315f143a1f9fb1b0e7a5a5e51615ec277c97c52500e0a1e216be80167d685881f03a01fde39ce4e6f8c83628d0cbcd85d6b45bbe8898a2515dafe720a168b753c31c64d4beab923ae69554e722febabe8f7f0f7f93c6b030ac568ce830d678fa75132072e20fb98478e99ec8cfa74a30d2ad42c692eafb64981a293ebdfdd6efe10e46321b377a78f248810e2a5074b8608da9368441af6374bc3a8de7dce0326d86160bf4287268037ebfecd4826e5479faa0d633dc530619b89488bb62b85ef7b430249b39d3838d884c3b3cb3b3228371193cd43e1cfa06db38320e2998c15f503b3125547e38fae4884ce8db9f69fb24b9f3b19a8464f0f8458b30449e659f5ce544d44a151a154762026d4bd1527525c18349f805b105afa35ff993cb736a066e7f9e9d89839bdb89d992d7ab46ea656a6c7ede706b87e59cc97873844b3fbd56093364826d1faaa3825b64cffd5836776138e0ba9ff22151b7037162fa753feb99a8bcdf9471739b0f56db1e63cb5e5c285d86c75497ff5d491e1cce377c87bbf2b8b6f481ac9eecb4d12174618f906b741eec42f7757227ef08cd00817889529aa96290bc6bddc9cb1462f6731682e40565afbf6cfbff8529ed0a4e5675987132ed74a25d81869ac94e90219a6568574073e6385209e64b91f6018d5b39ff245abe917f336e944d620b8a260aa030231e89d1edc2e72d0ee0f60affcea8c5447460d9705609b93067c86129534a58c250421e09d84c5f107a9fd344f66b0ee5b6e95866e0e60704fab7e41eeb5734650492cd8a678203b77ce8e8face5807efb6a6489da3cc87804ba600163100933ffe9dbde4b02301838a349322cb300a079b646143f1a618f186f170200db9df5a5049f08b99928a978f0bf71e02a3eb8891a496517cd3b6878be5060c1830432a1138e287991248ac1ab7034a7ccbf6b5d7523001f5d1649b9f75e107984204a6f05a50b4a4c5650c817cc88817133f1eacc11a49de61c09084c18673631a0a92a15deaf2352455d930c9fb9c132a1acfb061608632a69cd9c350fe8ab32a6be956ea4ed87618ba47844116e02424e325b23f412e43355c56e6e87fcfd745f36752408f3a1cea16e8fb433f8bd6bc22ba08c8baeb85ec7699b080d882999521fad304c2eece0eba6744159300f9928315e23872885190c80e9653107a17bb1e1efc8d0ddf63f178cc3b34a967b3a61f864890159ab75e7124bc358e72603fc35b233b4d94188a4eb3b554334bffc638bdb4e34a75596f1c2103ef48bf7ec1680ccc2bd3420a9479e189017d9a4e27a5d6b2eac599d35a4ed93a0e93dfea054ebeb81d847f29c4868bbecfec752cbed85b2f0f7b6ceff12b1c6335739cf76a7834f5c148a0df3594dc7894ad0b68fbade8899c78fc86d52ddeaca43d815d85593ba13689342f448973bed68bb84c860a7f2ebac906fe85de8d0d", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", + "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0x52bc71c1eca5353749542dfdf0af97bf764f9c2f44e860cd485f1cd86400f649", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x88fbb13c02428a6ba0e3c362f503d78c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x9ba1b78972885c5d3fc221d6771e8ba20f4cf0917788d791142ff6c1f216e7b3": "0x01", + "0x9ba1b78972885c5d3fc221d6771e8ba24e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000100000000000000000", + "0xcd5c1f6df63bc97f4a8ce37f14a50ca74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3401bcd1e9f3885b9b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34": "0xb0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb381d03c816fe51e89926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227": "0x926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3ae9e7a6969af6726a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20": "0xa8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3d6668b8260aeead3b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575": "0xb8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19500952b0337fcbf1d46175726180926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227": "0x926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195017c489719c28aa986175726180a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20": "0xa8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195026ed82a0e5bfb6c76175726180b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34": "0xb0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19502ac2136394fc85866175726180b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575": "0xb8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x03000000", + "0xe81713b6b40972bbcd298d67597a495f0f4cf0917788d791142ff6c1f216e7b3": "0x01", + "0xe81713b6b40972bbcd298d67597a495f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf7327be699d4ca1e710c5cb7cfa19d3c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file From c0c1c1e2a4255905d2d281afd478903adfcc0b7e Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 18 Oct 2022 15:51:30 +0200 Subject: [PATCH 77/91] [BridgeHub] Type Bride -> Bridge --- parachains/chain-specs/bridge-hub-rococo.json | 4 ++-- parachains/chain-specs/bridge-hub-wococo.json | 4 ++-- polkadot-parachain/src/chain_spec/bridge_hubs.rs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/parachains/chain-specs/bridge-hub-rococo.json b/parachains/chain-specs/bridge-hub-rococo.json index 97bacbbf22c..e94373a11f2 100644 --- a/parachains/chain-specs/bridge-hub-rococo.json +++ b/parachains/chain-specs/bridge-hub-rococo.json @@ -1,5 +1,5 @@ { - "name": "Rococo BrideHub", + "name": "Rococo BridgeHub", "id": "bridge-hub-rococo", "chainType": "Live", "bootNodes": [ @@ -82,4 +82,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/parachains/chain-specs/bridge-hub-wococo.json b/parachains/chain-specs/bridge-hub-wococo.json index 9d354ad11c7..0affa8b71a8 100644 --- a/parachains/chain-specs/bridge-hub-wococo.json +++ b/parachains/chain-specs/bridge-hub-wococo.json @@ -1,5 +1,5 @@ { - "name": "Wococo BrideHub", + "name": "Wococo BridgeHub", "id": "bridge-hub-wococo", "chainType": "Live", "bootNodes": [ @@ -82,4 +82,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index ce1705822a6..ea69c27903f 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -68,7 +68,7 @@ impl BridgeHubRuntimeType { if *default_config { Ok(Box::new(rococo::default_config( rococo::BRIDGE_HUB_ROCOCO, - "Rococo BrideHub", + "Rococo BridgeHub", ChainType::Live, "rococo", ParaId::new(1013), @@ -82,7 +82,7 @@ impl BridgeHubRuntimeType { }, BridgeHubRuntimeType::RococoLocal => Ok(Box::new(rococo::default_config( rococo::BRIDGE_HUB_ROCOCO_LOCAL, - "Rococo BrideHub Local", + "Rococo BridgeHub Local", ChainType::Local, "rococo-local", ParaId::new(1013), @@ -93,7 +93,7 @@ impl BridgeHubRuntimeType { if *default_config { Ok(Box::new(wococo::default_config( wococo::BRIDGE_HUB_WOCOCO, - "Wococo BrideHub", + "Wococo BridgeHub", ChainType::Live, "wococo", ParaId::new(1013), @@ -107,7 +107,7 @@ impl BridgeHubRuntimeType { }, BridgeHubRuntimeType::WococoLocal => Ok(Box::new(wococo::default_config( wococo::BRIDGE_HUB_WOCOCO_LOCAL, - "Wococo BrideHub Local", + "Wococo BridgeHub Local", ChainType::Local, "wococo-local", ParaId::new(1013), From e2731be50915b125305052b4beea502946a6347e Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 28 Sep 2022 13:40:33 +0200 Subject: [PATCH 78/91] [BridgeHub] Initial commit for XCM messaging support --- Cargo.lock | 29 +- Cargo.toml | 4 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 6 + .../bridge-hub-rococo/src/bridge_config.rs | 263 ++++++++++++++++++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 71 ++++- .../bridge-hub-rococo/src/xcm_config.rs | 16 ++ 6 files changed, 382 insertions(+), 7 deletions(-) create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs diff --git a/Cargo.lock b/Cargo.lock index c0e22540ece..0a851684a5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -713,6 +713,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bp-bridge-hub-rococo" +version = "0.1.0" +dependencies = [ + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "sp-api", +] + +[[package]] +name = "bp-bridge-hub-wococo" +version = "0.1.0" +dependencies = [ + "bp-bridge-hub-rococo", + "bp-runtime", + "sp-api", +] + [[package]] name = "bp-header-chain" version = "0.1.0" @@ -992,9 +1011,13 @@ dependencies = [ name = "bridge-hub-rococo-runtime" version = "0.1.0" dependencies = [ - "bp-polkadot-core 0.1.0", - "bp-rococo 0.1.0", - "bp-wococo 0.1.0", + "bp-bridge-hub-rococo", + "bp-bridge-hub-wococo", + "bp-messages", + "bp-polkadot-core", + "bp-rococo", + "bp-runtime", + "bp-wococo", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", diff --git a/Cargo.toml b/Cargo.toml index 4ba7acb605c..7e3eb2d88c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ members = [ "bridges/modules/shift-session-manager", "bridges/primitives/polkadot-core", "bridges/primitives/runtime", - "bridges/primitives/chain-rococo", - "bridges/primitives/chain-wococo", + "bridges/primitives/chain-bridge-hub-rococo", + "bridges/primitives/chain-bridge-hub-wococo", "client/cli", "client/consensus/aura", "client/consensus/common", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index db023747414..1c7ab7a2546 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -68,6 +68,9 @@ parachain-info = { path = "../../../../parachains/pallets/parachain-info", defau parachains-common = { path = "../../../../parachains/common", default-features = false } # Bridges +bp-bridge-hub-rococo = { path = "../../../../bridges/primitives/chain-bridge-hub-rococo", default-features = false } +bp-bridge-hub-wococo = { path = "../../../../bridges/primitives/chain-bridge-hub-wococo", default-features = false } +bp-messages = { path = "../../../../bridges/primitives/messages", default-features = false } bp-polkadot-core = { path = "../../../../bridges/primitives/polkadot-core", default-features = false } bp-runtime = { path = "../../../../bridges/primitives/runtime", default-features = false } bp-rococo = { path = "../../../../bridges/primitives/chain-rococo", default-features = false } @@ -83,7 +86,10 @@ default = [ "std", ] std = [ + "bp-bridge-hub-rococo/std", + "bp-bridge-hub-wococo/std", "bp-polkadot-core/std", + "bp-messages/std", "bp-runtime/std", "bp-rococo/std", "bp-wococo/std", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs new file mode 100644 index 00000000000..3a62bd829d3 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs @@ -0,0 +1,263 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +use bp_messages::{ + source_chain::{LaneMessageVerifier, TargetHeaderChain}, + target_chain::{DispatchMessage, MessageDispatch, ProvedMessages, SourceHeaderChain}, + InboundLaneData, LaneId, Message, OutboundLaneData, +}; +use bp_polkadot_core::Balance; +use bp_runtime::{messages::MessageDispatchResult, AccountIdOf, BalanceOf, Chain}; +use codec::Decode; +use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; + +parameter_types! { + // TODO:check-parameter + pub const BridgeHubRococoMaxMessagesToPruneAtOnce: bp_messages::MessageNonce = 8; + pub const BridgeHubWococoMaxMessagesToPruneAtOnce: bp_messages::MessageNonce = 8; + // TODO:check-parameter + pub const BridgeHubRococoMaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + pub const BridgeHubWococoMaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_wococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + // TODO:check-parameter + pub const BridgeHubRococoMaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + pub const BridgeHubWococoMaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_wococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + + pub const BridgeHubRococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; + pub const BridgeHubWococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; +} + +// TODO:check-parameter - when integration XCMv3 change this to struct +pub type PlainXcmPayload = sp_std::prelude::Vec; + +// TODO:check-parameter - when integration XCMv3 change this to struct +pub type FromBridgeHubRococoMessagePayload = PlainXcmPayload; +pub type FromBridgeHubWococoMessagePayload = PlainXcmPayload; +pub type ToBridgeHubRococoMessagePayload = PlainXcmPayload; +pub type ToBridgeHubWococoMessagePayload = PlainXcmPayload; + +// TODO:check-parameter - when integrating XCMv3 change this to FromBridgedChainMessagePayload +pub struct FromBridgeHubRococoMessageDispatch { + _marker: sp_std::marker::PhantomData<(SourceBridgeHubChain, TargetBridgeHubChain)>, +} +pub struct FromBridgeHubWococoMessageDispatch { + _marker: sp_std::marker::PhantomData<(SourceBridgeHubChain, TargetBridgeHubChain)>, +} + +impl + MessageDispatch, BalanceOf> + for FromBridgeHubRococoMessageDispatch +{ + type DispatchPayload = FromBridgeHubRococoMessagePayload; + + fn dispatch_weight( + message: &mut DispatchMessage>, + ) -> Weight { + log::error!("[FromBridgeHubRococoMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor"); + 0 + } + + fn dispatch( + relayer_account: &AccountIdOf, + message: DispatchMessage>, + ) -> MessageDispatchResult { + log::error!("[FromBridgeHubRococoMessageDispatch] TODO: change here to XCMv3 dispatch with XcmExecutor"); + todo!("TODO: implement XCMv3 dispatch") + } +} + +impl + MessageDispatch, BalanceOf> + for FromBridgeHubWococoMessageDispatch +{ + type DispatchPayload = FromBridgeHubWococoMessagePayload; + + fn dispatch_weight( + message: &mut DispatchMessage>, + ) -> Weight { + log::error!("[FromBridgeHubWococoMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor"); + 0 + } + + fn dispatch( + relayer_account: &AccountIdOf, + message: DispatchMessage>, + ) -> MessageDispatchResult { + log::error!("[FromBridgeHubWococoMessageDispatch] TODO: change here to XCMv3 dispatch with XcmExecutor"); + todo!("TODO: implement XCMv3 dispatch") + } +} + +pub struct ToBridgeHubRococoMessageVerifier { + _marker: sp_std::marker::PhantomData<(Origin, Sender)>, +} +pub struct ToBridgeHubWococoMessageVerifier { + _marker: sp_std::marker::PhantomData<(Origin, Sender)>, +} + +impl + LaneMessageVerifier< + Origin, + AccountIdOf, + ToBridgeHubRococoMessagePayload, + BalanceOf, + > for ToBridgeHubRococoMessageVerifier +{ + type Error = &'static str; + + fn verify_message( + submitter: &Origin, + delivery_and_dispatch_fee: &BalanceOf, + lane: &LaneId, + outbound_data: &OutboundLaneData, + payload: &ToBridgeHubRococoMessagePayload, + ) -> Result<(), Self::Error> { + todo!("TODO: ToBridgeHubRococoMessageVerifier - fix verify_message - at the begining to allow all") + } +} + +impl + LaneMessageVerifier< + Origin, + AccountIdOf, + ToBridgeHubWococoMessagePayload, + BalanceOf, + > for ToBridgeHubWococoMessageVerifier +{ + type Error = &'static str; + + fn verify_message( + submitter: &Origin, + delivery_and_dispatch_fee: &BalanceOf, + lane: &LaneId, + outbound_data: &OutboundLaneData, + payload: &ToBridgeHubWococoMessagePayload, + ) -> Result<(), Self::Error> { + todo!("TODO: ToBridgeHubWococoMessageVerifier - fix verify_message - at the begining to allow all") + } +} + +/// BridgeHubRococo chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct BridgeHubRococoMessagingSupport; +/// BridgeHubWococo chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct BridgeHubWococoMessagingSupport; + +impl SourceHeaderChain + for BridgeHubRococoMessagingSupport +{ + type Error = &'static str; + type MessagesProof = (); + + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result>, Self::Error> { + // TODO: need to add, bridges-runtime-common and refactor out of bin + // messages::target::verify_messages_proof_from_parachain::< + // WithRialtoParachainMessageBridge, + // bp_bridge_hub_rococo::Header, + // crate::Runtime, + // crate::WithRialtoParachainsInstance, + // >(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof, messages_count) + todo!("TODO: fix and implement SourceHeaderChain::verify_messages_proof") + } +} + +impl + TargetHeaderChain< + ToBridgeHubRococoMessagePayload, + crate::AccountId, /* bp_bridge_hub_wococo::AccountId */ + > for BridgeHubRococoMessagingSupport +{ + type Error = &'static str; + type MessagesDeliveryProof = (); + + fn verify_message(payload: &ToBridgeHubRococoMessagePayload) -> Result<(), Self::Error> { + // messages::source::verify_chain_message::(payload) + todo!("TODO: fix implementation: TargetHeaderChain::verify_message") + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result< + (LaneId, InboundLaneData), + Self::Error, + > { + // messages::source::verify_messages_delivery_proof_from_parachain::< + // WithRialtoParachainMessageBridge, + // bp_rialto_parachain::Header, + // Runtime, + // crate::WithRialtoParachainsInstance, + // >(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof) + todo!("TODO: fix implementation: TargetHeaderChain::verify_messages_delivery_proof") + } +} + +impl SourceHeaderChain + for BridgeHubWococoMessagingSupport +{ + type Error = &'static str; + type MessagesProof = (); + + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result>, Self::Error> { + // TODO: need to add, bridges-runtime-common and refactor out of bin + // messages::target::verify_messages_proof_from_parachain::< + // WithMIllauParachainMessageBridge, + // bp_bridge_hub_wococo::Header, + // crate::Runtime, + // crate::WithRialtoParachainsInstance, + // >(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof, messages_count) + todo!("TODO: fix and implement SourceHeaderChain::verify_messages_proof") + } +} + +impl + TargetHeaderChain< + ToBridgeHubWococoMessagePayload, + crate::AccountId, /* bp_bridge_hub_rococo::AccountId */ + > for BridgeHubWococoMessagingSupport +{ + type Error = &'static str; + type MessagesDeliveryProof = (); + + fn verify_message(payload: &ToBridgeHubWococoMessagePayload) -> Result<(), Self::Error> { + // messages::source::verify_chain_message::(payload) + todo!("TODO: fix implementation: TargetHeaderChain::verify_message") + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result< + (LaneId, InboundLaneData), + Self::Error, + > { + // messages::source::verify_messages_delivery_proof_from_parachain::< + // WithRialtoParachainMessageBridge, + // bp_rialto_parachain::Header, + // Runtime, + // crate::WithRialtoParachainsInstance, + // >(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof) + todo!("TODO: fix implementation: TargetHeaderChain::verify_messages_delivery_proof") + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index bb5b4f55aed..9f4271d139e 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// Copyright 2022 Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod weights; pub mod xcm_config; +mod bridge_config; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use smallvec::smallvec; @@ -491,7 +492,7 @@ parameter_types! { pub const HeadersToKeep: u32 = 1024; } -/// Add granda bridge pallet to track Wococo relay chain +/// Add granda bridge pallet to track Wococo relay chain on Rococo BridgeHub pub type BridgeGrandpaWococoInstance = pallet_bridge_grandpa::Instance1; impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_wococo::Wococo; @@ -543,6 +544,70 @@ impl pallet_bridge_parachains::Config for Runtime type HeadsToKeep = ParachainHeadsToKeep; } +/// Add XCM messages support for BrigdeHubRococo to support Wococo->Rococo XCM messages +pub type WithBridgeHubWococoMessagesInstance = pallet_bridge_messages::Instance1; +impl pallet_bridge_messages::Config for Runtime { + type Event = Event; + // TODO:check-parameter - copy of MillauWeigth + refactor + type WeightInfo = (); + type BridgedChainId = bridge_config::BridgeHubWococoChainId; + // TODO:check-parameter - do we need any conversion rate or what ever? + type Parameter = (); + type MaxMessagesToPruneAtOnce = bridge_config::BridgeHubWococoMaxMessagesToPruneAtOnce; + type MaxUnrewardedRelayerEntriesAtInboundLane = bridge_config::BridgeHubWococoMaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = bridge_config::BridgeHubWococoMaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = (); + type OutboundPayload = bridge_config::ToBridgeHubWococoMessagePayload; + type OutboundMessageFee = crate::Balance; /* bp_bridge_hub_rococo::Balance */ + + type InboundPayload = bridge_config::FromBridgeHubWococoMessagePayload; + type InboundMessageFee = crate::Balance; /* bp_bridge_hub_wococo::Balance */ + type InboundRelayer = crate::AccountId; /* bp_bridge_hub_wococo::AccountId */ + + type TargetHeaderChain = bridge_config::BridgeHubWococoMessagingSupport; + type LaneMessageVerifier = bridge_config::ToBridgeHubWococoMessageVerifier; + // TODO:check-parameter - add because of rewards? + type MessageDeliveryAndDispatchPayment = (); + type OnMessageAccepted = (); + type OnDeliveryConfirmed = (); + + type SourceHeaderChain = bridge_config::BridgeHubWococoMessagingSupport; + type MessageDispatch = bridge_config::FromBridgeHubWococoMessageDispatch; +} + +/// Add XCM messages support for BrigdeHubWococo to support Rococo->Wococo XCM messages +pub type WithBridgeHubRococoMessagesInstance = pallet_bridge_messages::Instance2; +impl pallet_bridge_messages::Config for Runtime { + type Event = Event; + // TODO:check-parameter - copy of MillauWeigth + refactor + type WeightInfo = (); + type BridgedChainId = bridge_config::BridgeHubRococoChainId; + // TODO:check-parameter - do we need any conversion rate or what ever? + type Parameter = (); + type MaxMessagesToPruneAtOnce = bridge_config::BridgeHubRococoMaxMessagesToPruneAtOnce; + type MaxUnrewardedRelayerEntriesAtInboundLane = bridge_config::BridgeHubRococoMaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = bridge_config::BridgeHubRococoMaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = (); + type OutboundPayload = bridge_config::ToBridgeHubRococoMessagePayload; + type OutboundMessageFee = crate::Balance; /* bp_bridge_hub_wococo::Balance */ + + type InboundPayload = bridge_config::FromBridgeHubRococoMessagePayload; + type InboundMessageFee = crate::Balance; /* bp_bridge_hub_rococo::Balance */ + type InboundRelayer = crate::AccountId; /* bp_bridge_hub_rococo::AccountId */ + + type TargetHeaderChain = bridge_config::BridgeHubRococoMessagingSupport; + type LaneMessageVerifier = bridge_config::ToBridgeHubRococoMessageVerifier; + // TODO:check-parameter - add because of rewards? + type MessageDeliveryAndDispatchPayment = (); + type OnMessageAccepted = (); + type OnDeliveryConfirmed = (); + + type SourceHeaderChain = bridge_config::BridgeHubRococoMessagingSupport; + type MessageDispatch = bridge_config::FromBridgeHubRococoMessageDispatch; +} + /// Add shift session manager impl pallet_shift_session_manager::Config for Runtime {} @@ -584,10 +649,12 @@ construct_runtime!( // Wococo bridge modules BridgeWococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Config} = 41, BridgeWococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage, Event} = 42, + BridgeWococoMessages: pallet_bridge_messages::::{Pallet, Call, Storage, Event, Config} = 46, // Rococo bridge modules BridgeRococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Config} = 43, BridgeRococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage, Event} = 44, + BridgeRococoMessages: pallet_bridge_messages::::{Pallet, Call, Storage, Event, Config} = 45, // Sudo Sudo: pallet_sudo::{Pallet, Call, Config, Event, Storage} = 100, diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index 543962807d3..84a2aeb60d7 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -1,3 +1,19 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + use super::{ AccountId, Balance, Balances, Call, Event, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, XcmpQueue, From 990041a299bc1fa5eba1c965d1db04e6d1704e37 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 6 Oct 2022 12:15:41 +0200 Subject: [PATCH 79/91] Fix compilation after subtree update --- Cargo.lock | 56 ++----------------- .../bridge-hub-rococo/src/bridge_config.rs | 2 - .../src/chain_spec/bridge_hubs.rs | 8 +++ 3 files changed, 13 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a851684a5f..e1cb2bffe34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -788,7 +788,6 @@ dependencies = [ "bitvec", "bp-runtime 0.1.0", "frame-support", - "frame-system", "hex", "hex-literal", "impl-trait-for-tuples", @@ -825,7 +824,6 @@ dependencies = [ "frame-support", "parity-scale-codec", "scale-info", - "serde", "sp-core", ] @@ -842,11 +840,9 @@ dependencies = [ "parity-util-mem", "scale-info", "serde", - "sp-api", "sp-core", "sp-runtime", "sp-std", - "sp-version", ] [[package]] @@ -882,33 +878,10 @@ dependencies = [ name = "bp-rococo" version = "0.1.0" dependencies = [ - "bp-messages 0.1.0", - "bp-polkadot-core 0.1.0", - "bp-runtime 0.1.0", + "bp-polkadot-core", + "bp-runtime", "frame-support", - "parity-scale-codec", - "smallvec", "sp-api", - "sp-runtime", - "sp-std", - "sp-version", -] - -[[package]] -name = "bp-rococo" -version = "0.1.0" -source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" -dependencies = [ - "bp-messages 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-polkadot-core 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "frame-support", - "parity-scale-codec", - "smallvec", - "sp-api", - "sp-runtime", - "sp-std", - "sp-version", ] [[package]] @@ -982,29 +955,10 @@ dependencies = [ name = "bp-wococo" version = "0.1.0" dependencies = [ - "bp-messages 0.1.0", - "bp-polkadot-core 0.1.0", - "bp-rococo 0.1.0", - "bp-runtime 0.1.0", - "parity-scale-codec", - "sp-api", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "bp-wococo" -version = "0.1.0" -source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" -dependencies = [ - "bp-messages 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-polkadot-core 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-rococo 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "parity-scale-codec", + "bp-polkadot-core", + "bp-rococo", + "bp-runtime", "sp-api", - "sp-runtime", - "sp-std", ] [[package]] diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs index 3a62bd829d3..36a9658cf27 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs @@ -114,7 +114,6 @@ pub struct ToBridgeHubWococoMessageVerifier { impl LaneMessageVerifier< Origin, - AccountIdOf, ToBridgeHubRococoMessagePayload, BalanceOf, > for ToBridgeHubRococoMessageVerifier @@ -135,7 +134,6 @@ impl impl LaneMessageVerifier< Origin, - AccountIdOf, ToBridgeHubWococoMessagePayload, BalanceOf, > for ToBridgeHubWococoMessageVerifier diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index ea69c27903f..bdb1326f0f0 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -274,6 +274,14 @@ pub mod rococo { ..Default::default() }, bridge_rococo_grandpa: bridge_hub_rococo_runtime::BridgeRococoGrandpaConfig { + owner: bridges_pallet_owner.clone(), + ..Default::default() + }, + bridge_rococo_messages: bridge_hub_rococo_runtime::BridgeRococoMessagesConfig { + owner: bridges_pallet_owner.clone(), + ..Default::default() + }, + bridge_wococo_messages: bridge_hub_rococo_runtime::BridgeWococoMessagesConfig { owner: bridges_pallet_owner, ..Default::default() }, From cc2a65369ea7acc74f3ae500179b2e0a5a82f55e Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 25 Oct 2022 16:41:10 +0200 Subject: [PATCH 80/91] [BridgeHub] Init stuff for Haul/Dispatch xcm blobs --- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 + .../src/bridge_common_config.rs | 142 ++++++++++ .../bridge-hub-rococo/src/bridge_config.rs | 261 ------------------ .../src/bridge_hub_rococo_config.rs | 58 ++++ .../src/bridge_hub_wococo_config.rs | 59 ++++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 124 ++++++--- .../bridge-hub-rococo/src/xcm_config.rs | 72 +++-- 7 files changed, 393 insertions(+), 325 deletions(-) create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs delete mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 1c7ab7a2546..000db6e66b5 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -80,6 +80,7 @@ pallet-bridge-messages = { path = "../../../../bridges/modules/messages", defaul pallet-bridge-parachains = { path = "../../../../bridges/modules/parachains", default-features = false } pallet-bridge-relayers = { path = "../../../../bridges/modules/relayers", default-features = false } pallet-shift-session-manager = { path = "../../../../bridges/modules/shift-session-manager", default-features = false } +bridge-runtime-common = { path = "../../../../bridges/bin/runtime-common", default-features = false } [features] default = [ @@ -93,6 +94,7 @@ std = [ "bp-runtime/std", "bp-rococo/std", "bp-wococo/std", + "bridge-runtime-common/std", "codec/std", "log/std", "scale-info/std", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs new file mode 100644 index 00000000000..608193fd148 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -0,0 +1,142 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +use crate::universal_exports::HaulBlob; +use bp_messages::{ + source_chain::MessagesBridge, + target_chain::{DispatchMessage, MessageDispatch}, + LaneId, +}; +use bp_runtime::{messages::MessageDispatchResult, AccountIdOf, BalanceOf, Chain}; +use codec::Encode; +use frame_support::{dispatch::Weight, parameter_types}; +use xcm::latest::prelude::*; + +// TODO:check-parameter - we could possibly use BridgeMessage from xcm:v3 stuff +/// PLain "XCM" payload, which we transfer through bridge +pub type XcmAsPlainPayload = sp_std::prelude::Vec; + +// TODO:check-parameter +parameter_types! { + pub const MaxMessagesToPruneAtOnce: bp_messages::MessageNonce = 8; + pub const MaxRequests: u32 = 64; + pub const HeadersToKeep: u32 = 1024; +} + +/// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages from other BridgeHub +pub struct XcmBlobMessageDispatch { + _marker: + sp_std::marker::PhantomData<(SourceBridgeHubChain, TargetBridgeHubChain, DispatchBlob)>, +} + +impl< + SourceBridgeHubChain: Chain, + TargetBridgeHubChain: Chain, + DispatchBlob: crate::universal_exports::DispatchBlob, + > MessageDispatch, BalanceOf> + for XcmBlobMessageDispatch +{ + type DispatchPayload = XcmAsPlainPayload; + + fn dispatch_weight( + message: &mut DispatchMessage>, + ) -> Weight { + log::error!( + "[XcmBlobMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor" + ); + 0 + } + + fn dispatch( + _relayer_account: &AccountIdOf, + message: DispatchMessage>, + ) -> MessageDispatchResult { + log::warn!("[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob triggering"); + let payload = match message.data.payload { + Ok(payload) => payload, + Err(e) => { + log::error!("[XcmBlobMessageDispatch] payload error: {:?}", e); + return MessageDispatchResult { + dispatch_result: false, + unspent_weight: 0, + dispatch_fee_paid_during_dispatch: false, + } + }, + }; + let dispatch_result = match DispatchBlob::dispatch_blob(payload) { + Ok(_) => true, + Err(e) => { + log::error!( + "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?}", + e + ); + false + }, + }; + MessageDispatchResult { + dispatch_result, + dispatch_fee_paid_during_dispatch: false, + unspent_weight: 0, + } + } +} + +/// [`XcmBlobHauler`] is responsible for sending messages to the bridge "point-to-point link" from one side, +/// where on the other it can be dispatched by [`XcmBlobMessageDispatch`]. +pub trait XcmBlobHauler { + /// Which chain is sending + type SenderChain: Chain; + + /// Runtime message sender adapter. + type MessageSender: MessagesBridge< + super::Origin, + AccountIdOf, + BalanceOf, + XcmAsPlainPayload, + >; + + /// Our location within the Consensus Universe. + fn message_sender_origin() -> InteriorMultiLocation; + + /// Return message lane (as "point-to-point link") used to deliver XCM messages. + fn xcm_lane() -> LaneId; +} + +impl HaulBlob for T { + fn haul_blob(blob: sp_std::prelude::Vec) { + let lane = T::xcm_lane(); + // TODO:check-parameter - fee could be taken from BridgeMessage - or add as optional fo send_message + // TODO:check-parameter - or add here something like PriceForSiblingDelivery + let fee = ::Balance::from(0u8); + + let result = T::MessageSender::send_message( + pallet_xcm::Origin::from(MultiLocation::from(T::message_sender_origin())).into(), + lane, + blob, + fee, + ); + let result = result + .map(|artifacts| { + let hash = (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256); + hash + }) + .map_err(|e| { + e + }); + log::info!(target: "runtime::bridge-hub", "haul_blob result: {:?} on lane: {:?}", result, lane); + result.expect("failed to process: TODO:check-parameter - wait for origin/gav-xcm-v3, there is a comment about handliing errors for HaulBlob"); + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs deleted file mode 100644 index 36a9658cf27..00000000000 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_config.rs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. -// This file is part of Cumulus. - -// Cumulus 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. - -// Cumulus 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 Cumulus. If not, see . - -use bp_messages::{ - source_chain::{LaneMessageVerifier, TargetHeaderChain}, - target_chain::{DispatchMessage, MessageDispatch, ProvedMessages, SourceHeaderChain}, - InboundLaneData, LaneId, Message, OutboundLaneData, -}; -use bp_polkadot_core::Balance; -use bp_runtime::{messages::MessageDispatchResult, AccountIdOf, BalanceOf, Chain}; -use codec::Decode; -use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; - -parameter_types! { - // TODO:check-parameter - pub const BridgeHubRococoMaxMessagesToPruneAtOnce: bp_messages::MessageNonce = 8; - pub const BridgeHubWococoMaxMessagesToPruneAtOnce: bp_messages::MessageNonce = 8; - // TODO:check-parameter - pub const BridgeHubRococoMaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = - bp_bridge_hub_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; - pub const BridgeHubWococoMaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = - bp_bridge_hub_wococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; - // TODO:check-parameter - pub const BridgeHubRococoMaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = - bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; - pub const BridgeHubWococoMaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = - bp_bridge_hub_wococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; - - pub const BridgeHubRococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; - pub const BridgeHubWococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; -} - -// TODO:check-parameter - when integration XCMv3 change this to struct -pub type PlainXcmPayload = sp_std::prelude::Vec; - -// TODO:check-parameter - when integration XCMv3 change this to struct -pub type FromBridgeHubRococoMessagePayload = PlainXcmPayload; -pub type FromBridgeHubWococoMessagePayload = PlainXcmPayload; -pub type ToBridgeHubRococoMessagePayload = PlainXcmPayload; -pub type ToBridgeHubWococoMessagePayload = PlainXcmPayload; - -// TODO:check-parameter - when integrating XCMv3 change this to FromBridgedChainMessagePayload -pub struct FromBridgeHubRococoMessageDispatch { - _marker: sp_std::marker::PhantomData<(SourceBridgeHubChain, TargetBridgeHubChain)>, -} -pub struct FromBridgeHubWococoMessageDispatch { - _marker: sp_std::marker::PhantomData<(SourceBridgeHubChain, TargetBridgeHubChain)>, -} - -impl - MessageDispatch, BalanceOf> - for FromBridgeHubRococoMessageDispatch -{ - type DispatchPayload = FromBridgeHubRococoMessagePayload; - - fn dispatch_weight( - message: &mut DispatchMessage>, - ) -> Weight { - log::error!("[FromBridgeHubRococoMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor"); - 0 - } - - fn dispatch( - relayer_account: &AccountIdOf, - message: DispatchMessage>, - ) -> MessageDispatchResult { - log::error!("[FromBridgeHubRococoMessageDispatch] TODO: change here to XCMv3 dispatch with XcmExecutor"); - todo!("TODO: implement XCMv3 dispatch") - } -} - -impl - MessageDispatch, BalanceOf> - for FromBridgeHubWococoMessageDispatch -{ - type DispatchPayload = FromBridgeHubWococoMessagePayload; - - fn dispatch_weight( - message: &mut DispatchMessage>, - ) -> Weight { - log::error!("[FromBridgeHubWococoMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor"); - 0 - } - - fn dispatch( - relayer_account: &AccountIdOf, - message: DispatchMessage>, - ) -> MessageDispatchResult { - log::error!("[FromBridgeHubWococoMessageDispatch] TODO: change here to XCMv3 dispatch with XcmExecutor"); - todo!("TODO: implement XCMv3 dispatch") - } -} - -pub struct ToBridgeHubRococoMessageVerifier { - _marker: sp_std::marker::PhantomData<(Origin, Sender)>, -} -pub struct ToBridgeHubWococoMessageVerifier { - _marker: sp_std::marker::PhantomData<(Origin, Sender)>, -} - -impl - LaneMessageVerifier< - Origin, - ToBridgeHubRococoMessagePayload, - BalanceOf, - > for ToBridgeHubRococoMessageVerifier -{ - type Error = &'static str; - - fn verify_message( - submitter: &Origin, - delivery_and_dispatch_fee: &BalanceOf, - lane: &LaneId, - outbound_data: &OutboundLaneData, - payload: &ToBridgeHubRococoMessagePayload, - ) -> Result<(), Self::Error> { - todo!("TODO: ToBridgeHubRococoMessageVerifier - fix verify_message - at the begining to allow all") - } -} - -impl - LaneMessageVerifier< - Origin, - ToBridgeHubWococoMessagePayload, - BalanceOf, - > for ToBridgeHubWococoMessageVerifier -{ - type Error = &'static str; - - fn verify_message( - submitter: &Origin, - delivery_and_dispatch_fee: &BalanceOf, - lane: &LaneId, - outbound_data: &OutboundLaneData, - payload: &ToBridgeHubWococoMessagePayload, - ) -> Result<(), Self::Error> { - todo!("TODO: ToBridgeHubWococoMessageVerifier - fix verify_message - at the begining to allow all") - } -} - -/// BridgeHubRococo chain from message lane point of view. -#[derive(RuntimeDebug, Clone, Copy)] -pub struct BridgeHubRococoMessagingSupport; -/// BridgeHubWococo chain from message lane point of view. -#[derive(RuntimeDebug, Clone, Copy)] -pub struct BridgeHubWococoMessagingSupport; - -impl SourceHeaderChain - for BridgeHubRococoMessagingSupport -{ - type Error = &'static str; - type MessagesProof = (); - - fn verify_messages_proof( - proof: Self::MessagesProof, - messages_count: u32, - ) -> Result>, Self::Error> { - // TODO: need to add, bridges-runtime-common and refactor out of bin - // messages::target::verify_messages_proof_from_parachain::< - // WithRialtoParachainMessageBridge, - // bp_bridge_hub_rococo::Header, - // crate::Runtime, - // crate::WithRialtoParachainsInstance, - // >(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof, messages_count) - todo!("TODO: fix and implement SourceHeaderChain::verify_messages_proof") - } -} - -impl - TargetHeaderChain< - ToBridgeHubRococoMessagePayload, - crate::AccountId, /* bp_bridge_hub_wococo::AccountId */ - > for BridgeHubRococoMessagingSupport -{ - type Error = &'static str; - type MessagesDeliveryProof = (); - - fn verify_message(payload: &ToBridgeHubRococoMessagePayload) -> Result<(), Self::Error> { - // messages::source::verify_chain_message::(payload) - todo!("TODO: fix implementation: TargetHeaderChain::verify_message") - } - - fn verify_messages_delivery_proof( - proof: Self::MessagesDeliveryProof, - ) -> Result< - (LaneId, InboundLaneData), - Self::Error, - > { - // messages::source::verify_messages_delivery_proof_from_parachain::< - // WithRialtoParachainMessageBridge, - // bp_rialto_parachain::Header, - // Runtime, - // crate::WithRialtoParachainsInstance, - // >(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof) - todo!("TODO: fix implementation: TargetHeaderChain::verify_messages_delivery_proof") - } -} - -impl SourceHeaderChain - for BridgeHubWococoMessagingSupport -{ - type Error = &'static str; - type MessagesProof = (); - - fn verify_messages_proof( - proof: Self::MessagesProof, - messages_count: u32, - ) -> Result>, Self::Error> { - // TODO: need to add, bridges-runtime-common and refactor out of bin - // messages::target::verify_messages_proof_from_parachain::< - // WithMIllauParachainMessageBridge, - // bp_bridge_hub_wococo::Header, - // crate::Runtime, - // crate::WithRialtoParachainsInstance, - // >(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof, messages_count) - todo!("TODO: fix and implement SourceHeaderChain::verify_messages_proof") - } -} - -impl - TargetHeaderChain< - ToBridgeHubWococoMessagePayload, - crate::AccountId, /* bp_bridge_hub_rococo::AccountId */ - > for BridgeHubWococoMessagingSupport -{ - type Error = &'static str; - type MessagesDeliveryProof = (); - - fn verify_message(payload: &ToBridgeHubWococoMessagePayload) -> Result<(), Self::Error> { - // messages::source::verify_chain_message::(payload) - todo!("TODO: fix implementation: TargetHeaderChain::verify_message") - } - - fn verify_messages_delivery_proof( - proof: Self::MessagesDeliveryProof, - ) -> Result< - (LaneId, InboundLaneData), - Self::Error, - > { - // messages::source::verify_messages_delivery_proof_from_parachain::< - // WithRialtoParachainMessageBridge, - // bp_rialto_parachain::Header, - // Runtime, - // crate::WithRialtoParachainsInstance, - // >(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof) - todo!("TODO: fix implementation: TargetHeaderChain::verify_messages_delivery_proof") - } -} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs new file mode 100644 index 00000000000..a44e9540ccf --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -0,0 +1,58 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +use bp_messages::{source_chain::{LaneMessageVerifier, TargetHeaderChain}, target_chain::{ProvedMessages, SourceHeaderChain}, InboundLaneData, LaneId, Message, OutboundLaneData, MessageKey, MessageData}; +use bp_runtime::{BalanceOf, Chain}; +use frame_support::{parameter_types, RuntimeDebug}; +use xcm::prelude::{InteriorMultiLocation, NetworkId}; +use bp_messages::target_chain::ProvedLaneMessages; +use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; +use crate::universal_exports::{BridgeBlobDispatcher, HaulBlobExporter}; +use crate::{WithBridgeHubWococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, XcmRouter}; +use crate::Runtime; +use crate::ParachainInfo; +use xcm::latest::prelude::*; + +// TODO:check-parameter +parameter_types! { + pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + pub const BridgeHubWococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; + pub BridgeHubRococoUniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(ParachainInfo::parachain_id().into())); + pub WococoGlobalConsensusNetwork: NetworkId = NetworkId::Wococo; +} + +/// Dispatches received XCM messages from other bridge +pub type OnBridgeHubRococoBlobDispatcher = BridgeBlobDispatcher; + +/// Export XCM messages to be relayed to the otherside +pub type ToBridgeHubWococoHaulBlobExporter = HaulBlobExporter; +pub struct ToBridgeHubWococoXcmBlobHauler; +pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO: LaneId = [0, 0, 0, 1]; +impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { + type SenderChain = bp_bridge_hub_rococo::BridgeHubRococo; + type MessageSender = pallet_bridge_messages::Pallet; + + fn message_sender_origin() -> InteriorMultiLocation { + crate::xcm_config::UniversalLocation::get() + } + + fn xcm_lane() -> LaneId { + DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs new file mode 100644 index 00000000000..2a521405db8 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -0,0 +1,59 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +use bp_messages::{source_chain::{LaneMessageVerifier, TargetHeaderChain}, target_chain::{ProvedMessages, SourceHeaderChain}, InboundLaneData, LaneId, Message, OutboundLaneData, MessageKey, MessageData}; +use bp_runtime::{BalanceOf, Chain}; +use frame_support::{parameter_types, RuntimeDebug}; +use xcm::prelude::{InteriorMultiLocation, NetworkId}; +use bp_messages::target_chain::ProvedLaneMessages; +use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; +use crate::universal_exports::{BridgeBlobDispatcher, HaulBlobExporter}; +use crate::{WithBridgeHubRococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, XcmRouter}; +use crate::Runtime; +use crate::ParachainInfo; +use xcm::latest::prelude::*; + +// TODO:check-parameter +parameter_types! { + pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_wococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_wococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + pub const BridgeHubRococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; + pub BridgeHubWococoUniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Wococo), Parachain(ParachainInfo::parachain_id().into())); + pub RococoGlobalConsensusNetwork: NetworkId = NetworkId::Rococo; +} + +/// Dispatches received XCM messages from other bridge +pub type OnBridgeHubWococoBlobDispatcher = BridgeBlobDispatcher; + +/// Export XCM messages to be relayed to the otherside +pub type ToBridgeHubRococoHaulBlobExporter = HaulBlobExporter; +pub struct ToBridgeHubRococoXcmBlobHauler; +pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO: LaneId = [0, 0, 0, 1]; +impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { + type SenderChain = bp_bridge_hub_wococo::BridgeHubWococo; + type MessageSender = pallet_bridge_messages::Pallet; + + fn message_sender_origin() -> InteriorMultiLocation { + crate::xcm_config::UniversalLocation::get() + } + + fn xcm_lane() -> LaneId { + DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO + } +} + diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 9f4271d139e..97a5d88ec2c 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -22,10 +22,13 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +pub mod bridge_common_config; +pub mod bridge_hub_rococo_config; +pub mod bridge_hub_wococo_config; mod weights; pub mod xcm_config; -mod bridge_config; +use bridge_common_config::*; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use smallvec::smallvec; use sp_api::impl_runtime_apis; @@ -59,18 +62,20 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; use xcm_config::{XcmConfig, XcmOriginToTransactDispatchOrigin}; -use bp_polkadot_core::parachains::ParaId; use bp_runtime::{HeaderId, HeaderIdProvider}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; -// Polkadot imports use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use sp_runtime::traits::ConstU32; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; -// XCM Imports +use crate::{ + bridge_hub_rococo_config::OnBridgeHubRococoBlobDispatcher, + bridge_hub_wococo_config::OnBridgeHubWococoBlobDispatcher, xcm_config::XcmRouter, +}; use parachains_common::{AccountId, Signature}; use xcm::latest::prelude::BodyId; use xcm_executor::XcmExecutor; @@ -238,11 +243,18 @@ pub fn native_version() -> NativeVersion { } // TODO:check-parameter - move to bridges/primitives, once rebased and would compile with bp_bridge_hub_xyz dependencies -mod runtime_api { - use super::BlockNumber; - use super::Hash; +pub mod runtime_api { + use super::{BlockNumber, Hash}; bp_runtime::decl_bridge_finality_runtime_apis!(rococo); bp_runtime::decl_bridge_finality_runtime_apis!(wococo); + + use bp_messages::{ + InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, + }; + use frame_support::{sp_runtime::FixedU128, Parameter}; + use sp_std::prelude::Vec; + bp_runtime::decl_bridge_messages_runtime_apis!(bridge_hub_rococo); + bp_runtime::decl_bridge_messages_runtime_apis!(bridge_hub_wococo); } parameter_types! { @@ -412,11 +424,12 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type Event = Event; type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; - type VersionWrapper = (); + type VersionWrapper = PolkadotXcm; type ExecuteOverweightOrigin = EnsureRoot; type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; - type WeightInfo = (); + type PriceForSiblingDelivery = (); + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; } impl cumulus_pallet_dmp_queue::Config for Runtime { @@ -485,12 +498,6 @@ impl pallet_sudo::Config for Runtime { } // Add bridge pallets (GPA) -parameter_types! { - // TODO:check-parameter - pub const MaxRequests: u32 = 64; - // TODO:check-parameter - pub const HeadersToKeep: u32 = 1024; -} /// Add granda bridge pallet to track Wococo relay chain on Rococo BridgeHub pub type BridgeGrandpaWococoInstance = pallet_bridge_grandpa::Instance1; @@ -523,9 +530,9 @@ parameter_types! { /// Add parachain bridge pallet to track Wococo bridge hub parachain pub type BridgeParachainWococoInstance = pallet_bridge_parachains::Instance1; impl pallet_bridge_parachains::Config for Runtime { + type Event = Event; // TODO:check-parameter type WeightInfo = (); - type Event = Event; type BridgesGrandpaPalletInstance = BridgeGrandpaWococoInstance; type ParasPalletName = WococoBridgeParachainPalletName; type TrackedParachains = Everything; @@ -544,24 +551,25 @@ impl pallet_bridge_parachains::Config for Runtime type HeadsToKeep = ParachainHeadsToKeep; } -/// Add XCM messages support for BrigdeHubRococo to support Wococo->Rococo XCM messages +/// Add XCM messages support for BrigdeHubRococo to support Rococo->Wococo XCM messages pub type WithBridgeHubWococoMessagesInstance = pallet_bridge_messages::Instance1; impl pallet_bridge_messages::Config for Runtime { type Event = Event; // TODO:check-parameter - copy of MillauWeigth + refactor type WeightInfo = (); - type BridgedChainId = bridge_config::BridgeHubWococoChainId; - // TODO:check-parameter - do we need any conversion rate or what ever? + type BridgedChainId = bridge_hub_rococo_config::BridgeHubWococoChainId; type Parameter = (); - type MaxMessagesToPruneAtOnce = bridge_config::BridgeHubWococoMaxMessagesToPruneAtOnce; - type MaxUnrewardedRelayerEntriesAtInboundLane = bridge_config::BridgeHubWococoMaxUnrewardedRelayerEntriesAtInboundLane; - type MaxUnconfirmedMessagesAtInboundLane = bridge_config::BridgeHubWococoMaxUnconfirmedMessagesAtInboundLane; + type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; + type MaxUnrewardedRelayerEntriesAtInboundLane = + bridge_hub_rococo_config::MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = + bridge_hub_rococo_config::MaxUnconfirmedMessagesAtInboundLane; type MaximalOutboundPayloadSize = (); - type OutboundPayload = bridge_config::ToBridgeHubWococoMessagePayload; - type OutboundMessageFee = crate::Balance; /* bp_bridge_hub_rococo::Balance */ + type OutboundPayload = XcmAsPlainPayload; + type OutboundMessageFee = crate::Balance; /* bp_bridge_hub_rococo::Balance */ - type InboundPayload = bridge_config::FromBridgeHubWococoMessagePayload; + type InboundPayload = XcmAsPlainPayload; type InboundMessageFee = crate::Balance; /* bp_bridge_hub_wococo::Balance */ type InboundRelayer = crate::AccountId; /* bp_bridge_hub_wococo::AccountId */ @@ -573,27 +581,32 @@ impl pallet_bridge_messages::Config for Run type OnDeliveryConfirmed = (); type SourceHeaderChain = bridge_config::BridgeHubWococoMessagingSupport; - type MessageDispatch = bridge_config::FromBridgeHubWococoMessageDispatch; + type MessageDispatch = XcmBlobMessageDispatch< + bp_bridge_hub_wococo::BridgeHubWococo, + bp_bridge_hub_rococo::BridgeHubRococo, + OnBridgeHubRococoBlobDispatcher, + >; } -/// Add XCM messages support for BrigdeHubWococo to support Rococo->Wococo XCM messages +/// Add XCM messages support for BrigdeHubWococo to support Wococo->Rococo XCM messages pub type WithBridgeHubRococoMessagesInstance = pallet_bridge_messages::Instance2; impl pallet_bridge_messages::Config for Runtime { type Event = Event; // TODO:check-parameter - copy of MillauWeigth + refactor type WeightInfo = (); - type BridgedChainId = bridge_config::BridgeHubRococoChainId; - // TODO:check-parameter - do we need any conversion rate or what ever? + type BridgedChainId = bridge_hub_wococo_config::BridgeHubRococoChainId; type Parameter = (); - type MaxMessagesToPruneAtOnce = bridge_config::BridgeHubRococoMaxMessagesToPruneAtOnce; - type MaxUnrewardedRelayerEntriesAtInboundLane = bridge_config::BridgeHubRococoMaxUnrewardedRelayerEntriesAtInboundLane; - type MaxUnconfirmedMessagesAtInboundLane = bridge_config::BridgeHubRococoMaxUnconfirmedMessagesAtInboundLane; + type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; + type MaxUnrewardedRelayerEntriesAtInboundLane = + bridge_hub_wococo_config::MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = + bridge_hub_wococo_config::MaxUnconfirmedMessagesAtInboundLane; type MaximalOutboundPayloadSize = (); - type OutboundPayload = bridge_config::ToBridgeHubRococoMessagePayload; - type OutboundMessageFee = crate::Balance; /* bp_bridge_hub_wococo::Balance */ + type OutboundPayload = XcmAsPlainPayload; + type OutboundMessageFee = crate::Balance; /* bp_bridge_hub_wococo::Balance */ - type InboundPayload = bridge_config::FromBridgeHubRococoMessagePayload; + type InboundPayload = XcmAsPlainPayload; type InboundMessageFee = crate::Balance; /* bp_bridge_hub_rococo::Balance */ type InboundRelayer = crate::AccountId; /* bp_bridge_hub_rococo::AccountId */ @@ -605,7 +618,11 @@ impl pallet_bridge_messages::Config for Run type OnDeliveryConfirmed = (); type SourceHeaderChain = bridge_config::BridgeHubRococoMessagingSupport; - type MessageDispatch = bridge_config::FromBridgeHubRococoMessageDispatch; + type MessageDispatch = XcmBlobMessageDispatch< + bp_bridge_hub_rococo::BridgeHubRococo, + bp_bridge_hub_wococo::BridgeHubWococo, + OnBridgeHubWococoBlobDispatcher, + >; } /// Add shift session manager @@ -796,6 +813,41 @@ impl_runtime_apis! { } } + // This exposed by BridgeHubRococo + impl runtime_api::ToBridgeHubWococoOutboundLaneApi for Runtime { + fn estimate_message_delivery_and_dispatch_fee( + _lane_id: bp_messages::LaneId, + payload: XcmAsPlainPayload, + _conversion_rate: Option, + ) -> Option { + None + } + + fn message_details( + lane: bp_messages::LaneId, + begin: bp_messages::MessageNonce, + end: bp_messages::MessageNonce, + ) -> Vec> { + bridge_runtime_common::messages_api::outbound_message_details::< + Runtime, + WithBridgeHubWococoMessagesInstance, + >(lane, begin, end) + } + } + + // This is exposed by BridgeHubWococo + impl runtime_api::FromBridgeHubRococoInboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, + ) -> Vec { + bridge_runtime_common::messages_api::inbound_message_details::< + Runtime, + WithBridgeHubRococoMessagesInstance, + >(lane, messages) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade() -> (Weight, Weight) { diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index 84a2aeb60d7..c6dead42d5a 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -15,15 +15,18 @@ // along with Cumulus. If not, see . use super::{ - AccountId, Balance, Balances, Call, Event, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, - XcmpQueue, + AccountId, Balance, Balances, Call, Event, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, + Runtime, XcmpQueue, +}; +use crate::{ + bridge_hub_rococo_config::ToBridgeHubWococoHaulBlobExporter, + bridge_hub_wococo_config::ToBridgeHubRococoHaulBlobExporter, }; use frame_support::{ match_types, parameter_types, traits::{Everything, Nothing}, - weights::Weight, + weights::{IdentityFee, Weight}, }; -use frame_support::weights::IdentityFee; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use xcm::latest::prelude::*; @@ -34,7 +37,7 @@ use xcm_builder::{ SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; -use xcm_executor::XcmExecutor; +use xcm_executor::{traits::ExportXcm, XcmExecutor}; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); @@ -192,6 +195,8 @@ pub type Barrier = ( TakeWeightCredit, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, + // TODO:check-parameter - supporting unpaid execution at first then SovereignPaid + AllowUnpaidExecutionFrom, // ^^^ Parent & its unit plurality gets free execution ); @@ -219,7 +224,7 @@ impl xcm_executor::Config for XcmConfig { type AssetLocker = (); type AssetExchanger = (); type FeeManager = (); - type MessageExporter = (); + type MessageExporter = BridgeHubRococoOrBridgeHubWococoSwitchExporter; type UniversalAliases = Nothing; type CallDispatcher = Call; } @@ -231,32 +236,11 @@ pub type LocalOriginToLocation = SignedToAccountId32, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, ); -// TODO: hacked -// impl pallet_xcm::Config for Runtime { -// type Event = Event; -// type SendXcmOrigin = EnsureXcmOrigin; -// type XcmRouter = XcmRouter; -// type ExecuteXcmOrigin = EnsureXcmOrigin; -// type XcmExecuteFilter = Nothing; -// // ^ Disable dispatchable execute on the XCM pallet. -// // Needs to be `Everything` for local testing. -// type XcmExecutor = XcmExecutor; -// type XcmTeleportFilter = Everything; -// type XcmReserveTransferFilter = Nothing; -// type Weigher = FixedWeightBounds; -// type LocationInverter = LocationInverter; -// type Origin = Origin; -// type Call = Call; -// -// const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; -// // ^ Override for AdvertisedXcmVersion default -// type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; -// } impl pallet_xcm::Config for Runtime { type Event = Event; type SendXcmOrigin = EnsureXcmOrigin; @@ -283,3 +267,35 @@ impl cumulus_pallet_xcm::Config for Runtime { type Event = Event; type XcmExecutor = XcmExecutor; } + +/// Hacky switch implementation, because we have just one runtime for Rococo and Wococo BridgeHub, so it means we have just one XcmConfig +pub struct BridgeHubRococoOrBridgeHubWococoSwitchExporter; +impl ExportXcm for BridgeHubRococoOrBridgeHubWococoSwitchExporter { + type Ticket = (NetworkId, (sp_std::prelude::Vec, XcmHash)); + + fn validate( + network: NetworkId, + channel: u32, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + match network { + Rococo => + ToBridgeHubRococoHaulBlobExporter::validate(network, channel, destination, message) + .map(|result| ((Rococo, result.0), result.1)), + Wococo => + ToBridgeHubWococoHaulBlobExporter::validate(network, channel, destination, message) + .map(|result| ((Wococo, result.0), result.1)), + _ => unimplemented!("Unsupported network: {:?}", network), + } + } + + fn deliver(ticket: Self::Ticket) -> Result { + let (network, ticket) = ticket; + match network { + Rococo => ToBridgeHubRococoHaulBlobExporter::deliver(ticket), + Wococo => ToBridgeHubWococoHaulBlobExporter::deliver(ticket), + _ => unimplemented!("Unsupported network: {:?}", network), + } + } +} From 04c67c4d6a1b85f000d04f4729f35da9b1ff38d5 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 31 Oct 2022 10:59:28 +0100 Subject: [PATCH 81/91] [BridgeHub] Init stuff for bridge messaging --- .../src/bridge_common_config.rs | 2 +- .../src/bridge_hub_rococo_config.rs | 227 ++++++++++++++++-- .../src/bridge_hub_wococo_config.rs | 224 +++++++++++++++-- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 34 ++- 4 files changed, 439 insertions(+), 48 deletions(-) diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 608193fd148..eb77346e09a 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -55,7 +55,7 @@ impl< message: &mut DispatchMessage>, ) -> Weight { log::error!( - "[XcmBlobMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor" + "[XcmBlobMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor - message: ?...?", ); 0 } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs index a44e9540ccf..8cb45f5cdf2 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -14,17 +14,32 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use bp_messages::{source_chain::{LaneMessageVerifier, TargetHeaderChain}, target_chain::{ProvedMessages, SourceHeaderChain}, InboundLaneData, LaneId, Message, OutboundLaneData, MessageKey, MessageData}; -use bp_runtime::{BalanceOf, Chain}; -use frame_support::{parameter_types, RuntimeDebug}; -use xcm::prelude::{InteriorMultiLocation, NetworkId}; -use bp_messages::target_chain::ProvedLaneMessages; -use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; -use crate::universal_exports::{BridgeBlobDispatcher, HaulBlobExporter}; -use crate::{WithBridgeHubWococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, XcmRouter}; -use crate::Runtime; -use crate::ParachainInfo; -use xcm::latest::prelude::*; +use crate::{ + universal_exports::{BridgeBlobDispatcher, HaulBlobExporter}, + ParachainInfo, Runtime, WithBridgeHubWococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, + XcmRouter, +}; +use bp_messages::{ + source_chain::TargetHeaderChain, + target_chain::{ProvedMessages, SourceHeaderChain}, + InboundLaneData, LaneId, Message, MessageNonce, +}; +use bp_polkadot_core::parachains::ParaId; +use bp_runtime::{Chain, ChainId}; +use bridge_runtime_common::{ + messages, + messages::{ + target::FromBridgedChainMessagesProof, BasicConfirmationTransactionEstimation, + BridgedChain, ChainWithMessages, MessageBridge, MessageTransaction, ThisChain, + ThisChainWithMessages, WeightOf, + }, +}; +use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; +use sp_runtime::FixedU128; +use xcm::{ + latest::prelude::*, + prelude::{InteriorMultiLocation, NetworkId}, +}; // TODO:check-parameter parameter_types! { @@ -38,15 +53,18 @@ parameter_types! { } /// Dispatches received XCM messages from other bridge -pub type OnBridgeHubRococoBlobDispatcher = BridgeBlobDispatcher; +pub type OnBridgeHubRococoBlobDispatcher = + BridgeBlobDispatcher; /// Export XCM messages to be relayed to the otherside -pub type ToBridgeHubWococoHaulBlobExporter = HaulBlobExporter; +pub type ToBridgeHubWococoHaulBlobExporter = + HaulBlobExporter; pub struct ToBridgeHubWococoXcmBlobHauler; -pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO: LaneId = [0, 0, 0, 1]; +pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO: LaneId = [0, 0, 0, 2]; impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { type SenderChain = bp_bridge_hub_rococo::BridgeHubRococo; - type MessageSender = pallet_bridge_messages::Pallet; + type MessageSender = + pallet_bridge_messages::Pallet; fn message_sender_origin() -> InteriorMultiLocation { crate::xcm_config::UniversalLocation::get() @@ -56,3 +74,182 @@ impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO } } + +/// Messaging Bridge configuration for BridgeHubRococo -> BridgeHubWococo +pub struct WithBridgeHubWococoMessageBridge; +impl MessageBridge for WithBridgeHubWococoMessageBridge { + // TODO:check-parameter - relayers rewards + const RELAYER_FEE_PERCENT: u32 = 0; + const THIS_CHAIN_ID: ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; + const BRIDGED_CHAIN_ID: ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = + bp_bridge_hub_rococo::WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME; + type ThisChain = BridgeHubRococo; + type BridgedChain = BridgeHubWococo; + + fn bridged_balance_to_this_balance( + bridged_balance: bridge_runtime_common::messages::BalanceOf>, + bridged_to_this_conversion_rate_override: Option, + ) -> bridge_runtime_common::messages::BalanceOf> { + log::info!("[WithBridgeHubWococoMessageBridge] bridged_balance_to_this_balance - bridged_balance: {:?}, bridged_to_this_conversion_rate_override: {:?}", bridged_balance, bridged_to_this_conversion_rate_override); + unimplemented!("TODO: WithBridgeHubWococoMessageBridge - bridged_balance_to_this_balance") + } +} + +/// Message verifier for BridgeHubWococo messages sent from BridgeHubRococo +pub type ToBridgeHubWococoMessageVerifier = + messages::source::FromThisChainMessageVerifier; + +/// Maximal outbound payload size of BridgeHubRococo -> BridgeHubWococo messages. +pub type ToBridgeHubWococoMaximalOutboundPayloadSize = + messages::source::FromThisChainMaximalOutboundPayloadSize; + +/// BridgeHubWococo chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct BridgeHubWococo; + +impl ChainWithMessages for BridgeHubWococo { + type Hash = bp_bridge_hub_wococo::Hash; + type AccountId = bp_bridge_hub_wococo::AccountId; + type Signer = bp_bridge_hub_wococo::AccountSigner; + type Signature = bp_bridge_hub_wococo::Signature; + type Weight = Weight; + type Balance = bp_bridge_hub_wococo::Balance; +} + +impl SourceHeaderChain for BridgeHubWococo { + type Error = &'static str; + type MessagesProof = FromBridgedChainMessagesProof; + + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result>, Self::Error> { + bridge_runtime_common::messages::target::verify_messages_proof_from_parachain::< + WithBridgeHubWococoMessageBridge, + bp_bridge_hub_wococo::Header, + crate::Runtime, + crate::BridgeParachainWococoInstance, + >(ParaId(bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID), proof, messages_count) + } +} + +impl TargetHeaderChain for BridgeHubWococo { + type Error = &'static str; + type MessagesDeliveryProof = + messages::source::FromBridgedChainMessagesDeliveryProof; + + fn verify_message(payload: &XcmAsPlainPayload) -> Result<(), Self::Error> { + messages::source::verify_chain_message::(payload) + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error> { + messages::source::verify_messages_delivery_proof_from_parachain::< + WithBridgeHubWococoMessageBridge, + bp_bridge_hub_wococo::Header, + crate::Runtime, + crate::BridgeParachainWococoInstance, + >(ParaId(bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID), proof) + } +} + +impl messages::BridgedChainWithMessages for BridgeHubWococo { + fn maximal_extrinsic_size() -> u32 { + bp_bridge_hub_wococo::BridgeHubWococo::max_extrinsic_size() + } + + fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { + true + } + + fn estimate_delivery_transaction( + message_payload: &[u8], + include_pay_dispatch_fee_cost: bool, + message_dispatch_weight: WeightOf, + ) -> MessageTransaction> { + let message_payload_len = u32::try_from(message_payload.len()).unwrap_or(u32::MAX); + let extra_bytes_in_payload = Weight::from(message_payload_len) + .saturating_sub(pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH.into()); + + MessageTransaction { + dispatch_weight: extra_bytes_in_payload + .saturating_mul(bp_bridge_hub_wococo::ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT) + .saturating_add(bp_bridge_hub_wococo::DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT) + .saturating_sub(if include_pay_dispatch_fee_cost { + 0 + } else { + bp_bridge_hub_wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT + }) + .saturating_add(message_dispatch_weight), + size: message_payload_len + .saturating_add(bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE) + .saturating_add(bp_bridge_hub_wococo::TX_EXTRA_BYTES), + } + } + + fn transaction_payment( + transaction: MessageTransaction>, + ) -> messages::BalanceOf { + log::info!( + "[BridgeHubWococo::BridgedChainWithMessages] transaction_payment - transaction: {:?}", + transaction + ); + // TODO:check-parameter - any payment? from sovereign account? + unimplemented!( + "[BridgeHubWococo/BridgedChainWithMessages] transaction_payment - transaction: {:?}", + transaction + ) + } +} + +/// BridgeHubRococo chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct BridgeHubRococo; + +impl ChainWithMessages for BridgeHubRococo { + type Hash = bp_bridge_hub_rococo::Hash; + type AccountId = bp_bridge_hub_rococo::AccountId; + type Signer = bp_bridge_hub_rococo::AccountSigner; + type Signature = bp_bridge_hub_rococo::Signature; + type Weight = Weight; + type Balance = bp_bridge_hub_rococo::Balance; +} + +impl ThisChainWithMessages for BridgeHubRococo { + type Origin = crate::Origin; + type Call = crate::Call; + type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation< + Self::AccountId, + { bp_bridge_hub_rococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT }, + { bp_bridge_hub_wococo::EXTRA_STORAGE_PROOF_SIZE }, + { bp_bridge_hub_rococo::TX_EXTRA_BYTES }, + >; + + fn is_message_accepted(origin: &Self::Origin, lane: &LaneId) -> bool { + log::info!("[BridgeHubRococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); + lane == &DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + log::info!( + "[BridgeHubRococo::ThisChainWithMessages] maximal_pending_messages_at_outbound_lane" + ); + MessageNonce::MAX / 2 + } + + fn transaction_payment( + transaction: MessageTransaction>, + ) -> messages::BalanceOf { + log::info!( + "[BridgeHubRococo::ThisChainWithMessages] transaction_payment - transaction: {:?}", + transaction + ); + // TODO:check-parameter - any payment? from sovereign account? + unimplemented!( + "[BridgeHubRococo/ThisChainWithMessages] transaction_payment - transaction: {:?}", + transaction + ) + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs index 2a521405db8..d8c874b0721 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -14,17 +14,32 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use bp_messages::{source_chain::{LaneMessageVerifier, TargetHeaderChain}, target_chain::{ProvedMessages, SourceHeaderChain}, InboundLaneData, LaneId, Message, OutboundLaneData, MessageKey, MessageData}; -use bp_runtime::{BalanceOf, Chain}; -use frame_support::{parameter_types, RuntimeDebug}; -use xcm::prelude::{InteriorMultiLocation, NetworkId}; -use bp_messages::target_chain::ProvedLaneMessages; -use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; -use crate::universal_exports::{BridgeBlobDispatcher, HaulBlobExporter}; -use crate::{WithBridgeHubRococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, XcmRouter}; -use crate::Runtime; -use crate::ParachainInfo; -use xcm::latest::prelude::*; +use crate::{ + universal_exports::{BridgeBlobDispatcher, HaulBlobExporter}, + ParachainInfo, Runtime, WithBridgeHubRococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, + XcmRouter, +}; +use bp_messages::{ + source_chain::TargetHeaderChain, + target_chain::{ProvedMessages, SourceHeaderChain}, + InboundLaneData, LaneId, Message, MessageNonce, +}; +use bp_polkadot_core::parachains::ParaId; +use bp_runtime::{Chain, ChainId}; +use bridge_runtime_common::{ + messages, + messages::{ + target::FromBridgedChainMessagesProof, BasicConfirmationTransactionEstimation, + BridgedChain, ChainWithMessages, MessageBridge, MessageTransaction, ThisChain, + ThisChainWithMessages, WeightOf, + }, +}; +use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; +use sp_runtime::FixedU128; +use xcm::{ + latest::prelude::*, + prelude::{InteriorMultiLocation, NetworkId}, +}; // TODO:check-parameter parameter_types! { @@ -38,15 +53,18 @@ parameter_types! { } /// Dispatches received XCM messages from other bridge -pub type OnBridgeHubWococoBlobDispatcher = BridgeBlobDispatcher; +pub type OnBridgeHubWococoBlobDispatcher = + BridgeBlobDispatcher; /// Export XCM messages to be relayed to the otherside -pub type ToBridgeHubRococoHaulBlobExporter = HaulBlobExporter; +pub type ToBridgeHubRococoHaulBlobExporter = + HaulBlobExporter; pub struct ToBridgeHubRococoXcmBlobHauler; pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO: LaneId = [0, 0, 0, 1]; impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { type SenderChain = bp_bridge_hub_wococo::BridgeHubWococo; - type MessageSender = pallet_bridge_messages::Pallet; + type MessageSender = + pallet_bridge_messages::Pallet; fn message_sender_origin() -> InteriorMultiLocation { crate::xcm_config::UniversalLocation::get() @@ -57,3 +75,181 @@ impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { } } +/// Messaging Bridge configuration for BridgeHubWococo -> BridgeHubRococo +pub struct WithBridgeHubRococoMessageBridge; +impl MessageBridge for WithBridgeHubRococoMessageBridge { + // TODO:check-parameter - relayers rewards + const RELAYER_FEE_PERCENT: u32 = 0; + const THIS_CHAIN_ID: ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; + const BRIDGED_CHAIN_ID: ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = + bp_bridge_hub_wococo::WITH_BRIDGE_HUB_WOCOCO_MESSAGES_PALLET_NAME; + type ThisChain = BridgeHubWococo; + type BridgedChain = BridgeHubRococo; + + fn bridged_balance_to_this_balance( + bridged_balance: bridge_runtime_common::messages::BalanceOf>, + bridged_to_this_conversion_rate_override: Option, + ) -> bridge_runtime_common::messages::BalanceOf> { + log::info!("[WithBridgeHubRococoMessageBridge] bridged_balance_to_this_balance - bridged_balance: {:?}, bridged_to_this_conversion_rate_override: {:?}", bridged_balance, bridged_to_this_conversion_rate_override); + unimplemented!("TODO: WithBridgeHubRococoMessageBridge - bridged_balance_to_this_balance") + } +} + +/// Message verifier for BridgeHubRococo messages sent from BridgeHubWococo +pub type ToBridgeHubRococoMessageVerifier = + messages::source::FromThisChainMessageVerifier; + +/// Maximal outbound payload size of BridgeHubWococo -> BridgeHubRococo messages. +pub type ToBridgeHubRococoMaximalOutboundPayloadSize = + messages::source::FromThisChainMaximalOutboundPayloadSize; + +/// BridgeHubRococo chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct BridgeHubRococo; + +impl ChainWithMessages for BridgeHubRococo { + type Hash = bp_bridge_hub_rococo::Hash; + type AccountId = bp_bridge_hub_rococo::AccountId; + type Signer = bp_bridge_hub_rococo::AccountSigner; + type Signature = bp_bridge_hub_rococo::Signature; + type Weight = Weight; + type Balance = bp_bridge_hub_rococo::Balance; +} + +impl SourceHeaderChain for BridgeHubRococo { + type Error = &'static str; + type MessagesProof = FromBridgedChainMessagesProof; + + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result>, Self::Error> { + bridge_runtime_common::messages::target::verify_messages_proof_from_parachain::< + WithBridgeHubRococoMessageBridge, + bp_bridge_hub_rococo::Header, + crate::Runtime, + crate::BridgeParachainRococoInstance, + >(ParaId(bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID), proof, messages_count) + } +} + +impl TargetHeaderChain for BridgeHubRococo { + type Error = &'static str; + type MessagesDeliveryProof = + messages::source::FromBridgedChainMessagesDeliveryProof; + + fn verify_message(payload: &XcmAsPlainPayload) -> Result<(), Self::Error> { + messages::source::verify_chain_message::(payload) + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error> { + messages::source::verify_messages_delivery_proof_from_parachain::< + WithBridgeHubRococoMessageBridge, + bp_bridge_hub_rococo::Header, + crate::Runtime, + crate::BridgeParachainRococoInstance, + >(ParaId(bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID), proof) + } +} + +impl messages::BridgedChainWithMessages for BridgeHubRococo { + fn maximal_extrinsic_size() -> u32 { + bp_bridge_hub_rococo::BridgeHubRococo::max_extrinsic_size() + } + + fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { + true + } + + fn estimate_delivery_transaction( + message_payload: &[u8], + include_pay_dispatch_fee_cost: bool, + message_dispatch_weight: WeightOf, + ) -> MessageTransaction> { + let message_payload_len = u32::try_from(message_payload.len()).unwrap_or(u32::MAX); + let extra_bytes_in_payload = Weight::from(message_payload_len) + .saturating_sub(pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH.into()); + + MessageTransaction { + dispatch_weight: extra_bytes_in_payload + .saturating_mul(bp_bridge_hub_rococo::ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT) + .saturating_add(bp_bridge_hub_rococo::DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT) + .saturating_sub(if include_pay_dispatch_fee_cost { + 0 + } else { + bp_bridge_hub_rococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT + }) + .saturating_add(message_dispatch_weight), + size: message_payload_len + .saturating_add(bp_bridge_hub_wococo::EXTRA_STORAGE_PROOF_SIZE) + .saturating_add(bp_bridge_hub_rococo::TX_EXTRA_BYTES), + } + } + + fn transaction_payment( + transaction: MessageTransaction>, + ) -> messages::BalanceOf { + log::info!( + "[BridgeHubRococo::BridgedChainWithMessages] transaction_payment - transaction: {:?}", + transaction + ); + // TODO:check-parameter - any payment? from sovereign account? + unimplemented!( + "[BridgeHubRococo/BridgedChainWithMessages] transaction_payment - transaction: {:?}", + transaction + ) + } +} + +/// BridgeHubWococo chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct BridgeHubWococo; + +impl ChainWithMessages for BridgeHubWococo { + type Hash = bp_bridge_hub_wococo::Hash; + type AccountId = bp_bridge_hub_wococo::AccountId; + type Signer = bp_bridge_hub_wococo::AccountSigner; + type Signature = bp_bridge_hub_wococo::Signature; + type Weight = Weight; + type Balance = bp_bridge_hub_wococo::Balance; +} + +impl ThisChainWithMessages for BridgeHubWococo { + type Origin = crate::Origin; + type Call = crate::Call; + type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation< + Self::AccountId, + { bp_bridge_hub_wococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT }, + { bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE }, + { bp_bridge_hub_wococo::TX_EXTRA_BYTES }, + >; + + fn is_message_accepted(origin: &Self::Origin, lane: &LaneId) -> bool { + log::info!("[BridgeHubWococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); + lane == &DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + log::info!( + "[BridgeHubWococo::ThisChainWithMessages] maximal_pending_messages_at_outbound_lane" + ); + MessageNonce::MAX / 2 + } + + fn transaction_payment( + transaction: MessageTransaction>, + ) -> messages::BalanceOf { + log::info!( + "[BridgeHubWococo::ThisChainWithMessages] transaction_payment - transaction: {:?}", + transaction + ); + // TODO:check-parameter - any payment? from sovereign account? + unimplemented!( + "[BridgeHubWococo/ThisChainWithMessages] transaction_payment - transaction: {:?}", + transaction + ) + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 97a5d88ec2c..66e3686cf6e 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -68,13 +68,13 @@ use bp_runtime::{HeaderId, HeaderIdProvider}; pub use sp_runtime::BuildStorage; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; -use sp_runtime::traits::ConstU32; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use crate::{ bridge_hub_rococo_config::OnBridgeHubRococoBlobDispatcher, - bridge_hub_wococo_config::OnBridgeHubWococoBlobDispatcher, xcm_config::XcmRouter, + bridge_hub_wococo_config::OnBridgeHubWococoBlobDispatcher, + xcm_config::XcmRouter, }; use parachains_common::{AccountId, Signature}; use xcm::latest::prelude::BodyId; @@ -565,22 +565,21 @@ impl pallet_bridge_messages::Config for Run type MaxUnconfirmedMessagesAtInboundLane = bridge_hub_rococo_config::MaxUnconfirmedMessagesAtInboundLane; - type MaximalOutboundPayloadSize = (); + type MaximalOutboundPayloadSize = bridge_hub_rococo_config::ToBridgeHubWococoMaximalOutboundPayloadSize; type OutboundPayload = XcmAsPlainPayload; - type OutboundMessageFee = crate::Balance; /* bp_bridge_hub_rococo::Balance */ + type OutboundMessageFee = Balance; type InboundPayload = XcmAsPlainPayload; - type InboundMessageFee = crate::Balance; /* bp_bridge_hub_wococo::Balance */ - type InboundRelayer = crate::AccountId; /* bp_bridge_hub_wococo::AccountId */ + type InboundMessageFee = Balance; + type InboundRelayer = AccountId; - type TargetHeaderChain = bridge_config::BridgeHubWococoMessagingSupport; - type LaneMessageVerifier = bridge_config::ToBridgeHubWococoMessageVerifier; - // TODO:check-parameter - add because of rewards? + type TargetHeaderChain = bridge_hub_rococo_config::BridgeHubWococo; + type LaneMessageVerifier = bridge_hub_rococo_config::ToBridgeHubWococoMessageVerifier; type MessageDeliveryAndDispatchPayment = (); type OnMessageAccepted = (); type OnDeliveryConfirmed = (); - type SourceHeaderChain = bridge_config::BridgeHubWococoMessagingSupport; + type SourceHeaderChain = bridge_hub_rococo_config::BridgeHubWococo; type MessageDispatch = XcmBlobMessageDispatch< bp_bridge_hub_wococo::BridgeHubWococo, bp_bridge_hub_rococo::BridgeHubRococo, @@ -602,22 +601,21 @@ impl pallet_bridge_messages::Config for Run type MaxUnconfirmedMessagesAtInboundLane = bridge_hub_wococo_config::MaxUnconfirmedMessagesAtInboundLane; - type MaximalOutboundPayloadSize = (); + type MaximalOutboundPayloadSize = bridge_hub_wococo_config::ToBridgeHubRococoMaximalOutboundPayloadSize; type OutboundPayload = XcmAsPlainPayload; - type OutboundMessageFee = crate::Balance; /* bp_bridge_hub_wococo::Balance */ + type OutboundMessageFee = Balance; type InboundPayload = XcmAsPlainPayload; - type InboundMessageFee = crate::Balance; /* bp_bridge_hub_rococo::Balance */ - type InboundRelayer = crate::AccountId; /* bp_bridge_hub_rococo::AccountId */ + type InboundMessageFee = Balance; + type InboundRelayer = AccountId; - type TargetHeaderChain = bridge_config::BridgeHubRococoMessagingSupport; - type LaneMessageVerifier = bridge_config::ToBridgeHubRococoMessageVerifier; - // TODO:check-parameter - add because of rewards? + type TargetHeaderChain = bridge_hub_wococo_config::BridgeHubRococo; + type LaneMessageVerifier = bridge_hub_wococo_config::ToBridgeHubRococoMessageVerifier; type MessageDeliveryAndDispatchPayment = (); type OnMessageAccepted = (); type OnDeliveryConfirmed = (); - type SourceHeaderChain = bridge_config::BridgeHubRococoMessagingSupport; + type SourceHeaderChain = bridge_hub_wococo_config::BridgeHubRococo; type MessageDispatch = XcmBlobMessageDispatch< bp_bridge_hub_rococo::BridgeHubRococo, bp_bridge_hub_wococo::BridgeHubWococo, From c886877a80e25c225c43a85887a9637c90bd19f5 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 7 Nov 2022 10:54:26 +0100 Subject: [PATCH 82/91] Updated scritp and readme --- parachains/runtimes/bridge-hubs/README.md | 14 +++++++------- scripts/bridges_rococo_wococo.sh | 21 +++++++++++++++++++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index 8aa4bbde04f..ea552d60a95 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -1,4 +1,4 @@ -# Bride-hubs Parachain +# Bridge-hubs Parachain Implementation of _BridgeHub_, a blockchain to support message passing between Substrate based chains like Polkadot and Kusama networks. @@ -72,7 +72,7 @@ or ``` # Rococo -> Wococo -RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay init-bridge rococo-to-bridge-hub-wococo \ --source-host localhost \ --source-port 48943 \ @@ -81,7 +81,7 @@ RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ --target-signer //Bob # Wococo -> Rococo -RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay init-bridge wococo-to-bridge-hub-rococo \ --source-host localhost \ --source-port 48945 \ @@ -97,7 +97,7 @@ RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ ``` # Rococo -> Wococo -RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay relay-headers rococo-to-bridge-hub-wococo \ --source-host localhost \ --source-port 48943 \ @@ -107,7 +107,7 @@ RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ --target-transactions-mortality=4 # Wococo -> Rococo -RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay relay-headers wococo-to-bridge-hub-rococo \ --source-host localhost \ --source-port 48945 \ @@ -131,7 +131,7 @@ RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ ``` # Rococo -> Wococo -RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay relay-parachains bridge-hub-rococo-to-bridge-hub-wococo \ --source-host localhost \ --source-port 48943 \ @@ -141,7 +141,7 @@ RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ --target-transactions-mortality=4 # Wococo -> Rococo -RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay relay-parachains bridge-hub-wococo-to-bridge-hub-rococo \ --source-host localhost \ --source-port 48945 \ diff --git a/scripts/bridges_rococo_wococo.sh b/scripts/bridges_rococo_wococo.sh index 8087769b57f..53d7dc974aa 100755 --- a/scripts/bridges_rococo_wococo.sh +++ b/scripts/bridges_rococo_wococo.sh @@ -41,6 +41,19 @@ function register_parachain() { # TODO: find the way to do it automatically } +function show_node_log_file() { + local NAME=$1 + local WS_PORT=$2 + local FILE_PATH=$3 + + echo "" + echo "" + echo " Node(${NAME}): ws-port: ${WS_PORT}" + echo " Logs: ${FILE_PATH}" + echo "" + echo "" +} + function check_parachain_collator() { local PORT=$1 local PALLET=$2 @@ -81,7 +94,9 @@ case "$1" in # Rococo ~/local_bridge_testing/bin/polkadot-parachain --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json --collator --alice --force-authoring --tmp --port 40333 --rpc-port 8933 --ws-port 8943 --no-mdns --node-key ${COLLATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/40334/p2p/${COLLATOR_KEY_BOB[0]} -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41333 --rpc-port 48933 --ws-port 48943 --no-mdns --node-key ${COLLATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30332/p2p/${VALIDATOR_KEY_ALICE[0]} &> ~/local_bridge_testing/logs/rococo_para_alice.log & + show_node_log_file alice 8943 ~/local_bridge_testing/logs/rococo_para_alice.log ~/local_bridge_testing/bin/polkadot-parachain --chain ~/local_bridge_testing/bridge-hub-rococo-local-raw.json --collator --bob --force-authoring --tmp --port 40334 --rpc-port 8934 --ws-port 8944 --no-mdns --node-key ${COLLATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/40333/p2p/${COLLATOR_KEY_ALICE[0]} -- --execution wasm --chain ~/local_bridge_testing/rococo-local-cfde.json --port 41334 --rpc-port 48934 --ws-port 48944 --no-mdns --node-key ${COLLATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/30333/p2p/${VALIDATOR_KEY_BOB[0]} &> ~/local_bridge_testing/logs/rococo_para_bob.log & + show_node_log_file bob 8944 ~/local_bridge_testing/logs/rococo_para_bob.log register_parachain 9942 1013 ~/local_bridge_testing/bridge-hub-rococo-local-genesis ~/local_bridge_testing/bridge-hub-rococo-local-genesis-wasm check_parachain_collator 8943 bridgeWococoGrandpa @@ -111,14 +126,16 @@ case "$1" in # Wococo ~/local_bridge_testing/bin/polkadot-parachain --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json --collator --alice --force-authoring --tmp --port 40335 --rpc-port 8935 --ws-port 8945 --no-mdns --node-key ${COLLATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/40336/p2p/${COLLATOR_KEY_BOB[0]} -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41335 --rpc-port 48935 --ws-port 48945 --no-mdns --node-key ${COLLATOR_KEY_ALICE[1]} --bootnodes=/ip4/127.0.0.1/tcp/30335/p2p/${VALIDATOR_KEY_ALICE[0]} &> ~/local_bridge_testing/logs/wococo_para_alice.log & + show_node_log_file alice 8945 ~/local_bridge_testing/logs/wococo_para_alice.log ~/local_bridge_testing/bin/polkadot-parachain --chain ~/local_bridge_testing/bridge-hub-wococo-local-raw.json --collator --bob --force-authoring --tmp --port 40336 --rpc-port 8936 --ws-port 8946 --no-mdns --node-key ${COLLATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/40335/p2p/${COLLATOR_KEY_ALICE[0]} -- --execution wasm --chain ~/local_bridge_testing/wococo-local-cfde.json --port 41336 --rpc-port 48936 --ws-port 48946 --no-mdns --node-key ${COLLATOR_KEY_BOB[1]} --bootnodes=/ip4/127.0.0.1/tcp/30336/p2p/${VALIDATOR_KEY_BOB[0]} &> ~/local_bridge_testing/logs/wococo_para_bob.log & + show_node_log_file bob 8946 ~/local_bridge_testing/logs/wococo_para_bob.log register_parachain 9945 1013 ~/local_bridge_testing/bridge-hub-wococo-local-genesis ~/local_bridge_testing/bridge-hub-wococo-local-genesis-wasm check_parachain_collator 8945 bridgeRococoGrandpa ;; init-ro-wo) # Init bridge Rococo->Wococo - RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay init-bridge rococo-to-bridge-hub-wococo \ --source-host localhost \ --source-port 48943 \ @@ -128,7 +145,7 @@ case "$1" in ;; init-wo-ro) # Init bridge Wococo->Rococo - RUST_LOG=runtime=trace,rpc=trace,runtime::bridge=trace \ + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay init-bridge wococo-to-bridge-hub-rococo \ --source-host localhost \ --source-port 48945 \ From 805615009be09f008504cc540d9360eb64b357aa Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 7 Nov 2022 10:57:35 +0100 Subject: [PATCH 83/91] XCM messaging fixes --- .github/workflows/release-30_create-draft.yml | 4 ++-- parachains/runtimes/bridge-hubs/README.md | 13 ++++++++++++ .../src/bridge_hub_rococo_config.rs | 19 +++++++----------- .../src/bridge_hub_wococo_config.rs | 19 +++++++----------- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 20 +++++++++++++++++++ 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/.github/workflows/release-30_create-draft.yml b/.github/workflows/release-30_create-draft.yml index 38c105d8b8e..6453dc1ac45 100644 --- a/.github/workflows/release-30_create-draft.yml +++ b/.github/workflows/release-30_create-draft.yml @@ -152,7 +152,7 @@ jobs: WESTMINT_DIGEST: ${{ github.workspace}}/westmint-srtool-json/westmint-srtool-digest.json STATEMINE_DIGEST: ${{ github.workspace}}/statemine-srtool-json/statemine-srtool-digest.json STATEMINT_DIGEST: ${{ github.workspace}}/statemint-srtool-json/statemint-srtool-digest.json - BRIDE_HUB_ROCOCO_DIGEST: ${{ github.workspace}}/bridge-hub-rococo-srtool-json/bridge-hub-rococo-srtool-digest.json + BRIDGE_HUB_ROCOCO_DIGEST: ${{ github.workspace}}/bridge-hub-rococo-srtool-json/bridge-hub-rococo-srtool-digest.json COLLECTIVES_POLKADOT_DIGEST: ${{ github.workspace}}/collectives-polkadot-srtool-json/collectives-polkadot-srtool-digest.json ROCOCO_PARA_DIGEST: ${{ github.workspace}}/rococo-parachain-srtool-json/rococo-parachain-srtool-digest.json CANVAS_KUSAMA_DIGEST: ${{ github.workspace}}/contracts-rococo-srtool-json/contracts-rococo-srtool-digest.json @@ -168,7 +168,7 @@ jobs: ls -al $WESTMINT_DIGEST || true ls -al $STATEMINE_DIGEST || true ls -al $STATEMINT_DIGEST || true - ls -al $BRIDE_HUB_ROCOCO_DIGEST || true + ls -al $BRIDGE_HUB_ROCOCO_DIGEST || true ls -al $COLLECTIVES_POLKADOT_DIGEST || true ls -al $ROCOCO_PARA_DIGEST || true ls -al $CANVAS_KUSAMA_DIGEST || true diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index ea552d60a95..b446c74b084 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -163,6 +163,19 @@ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ **4. Relay (XCM) messages** +``` +# Rococo -> Wococo +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay relay-messages bridge-hub-rococo-to-bridge-hub-wococo \ + --source-host localhost \ + --source-port 8943 \ + --source-signer //Charlie \ + --target-host localhost \ + --target-port 8945 \ + --target-signer //Charlie \ + --target-transactions-mortality=4 \ + --lane 00000002 +``` TODO: --- diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs index 8cb45f5cdf2..ff382c7f5e4 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -91,8 +91,9 @@ impl MessageBridge for WithBridgeHubWococoMessageBridge { bridged_balance: bridge_runtime_common::messages::BalanceOf>, bridged_to_this_conversion_rate_override: Option, ) -> bridge_runtime_common::messages::BalanceOf> { - log::info!("[WithBridgeHubWococoMessageBridge] bridged_balance_to_this_balance - bridged_balance: {:?}, bridged_to_this_conversion_rate_override: {:?}", bridged_balance, bridged_to_this_conversion_rate_override); - unimplemented!("TODO: WithBridgeHubWococoMessageBridge - bridged_balance_to_this_balance") + log::info!("[WithBridgeHubWococoMessageBridge] bridged_balance_to_this_balance (returns 0 balance, TODO: fix) - bridged_balance: {:?}, bridged_to_this_conversion_rate_override: {:?}", bridged_balance, bridged_to_this_conversion_rate_override); + // TODO:check-parameter - any payment? from sovereign account? + 0 } } @@ -193,14 +194,11 @@ impl messages::BridgedChainWithMessages for BridgeHubWococo { transaction: MessageTransaction>, ) -> messages::BalanceOf { log::info!( - "[BridgeHubWococo::BridgedChainWithMessages] transaction_payment - transaction: {:?}", + "[BridgeHubWococo::BridgedChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", transaction ); // TODO:check-parameter - any payment? from sovereign account? - unimplemented!( - "[BridgeHubWococo/BridgedChainWithMessages] transaction_payment - transaction: {:?}", - transaction - ) + 0 } } @@ -243,13 +241,10 @@ impl ThisChainWithMessages for BridgeHubRococo { transaction: MessageTransaction>, ) -> messages::BalanceOf { log::info!( - "[BridgeHubRococo::ThisChainWithMessages] transaction_payment - transaction: {:?}", + "[BridgeHubRococo::ThisChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", transaction ); // TODO:check-parameter - any payment? from sovereign account? - unimplemented!( - "[BridgeHubRococo/ThisChainWithMessages] transaction_payment - transaction: {:?}", - transaction - ) + 0 } } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs index d8c874b0721..02df9574583 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -91,8 +91,9 @@ impl MessageBridge for WithBridgeHubRococoMessageBridge { bridged_balance: bridge_runtime_common::messages::BalanceOf>, bridged_to_this_conversion_rate_override: Option, ) -> bridge_runtime_common::messages::BalanceOf> { - log::info!("[WithBridgeHubRococoMessageBridge] bridged_balance_to_this_balance - bridged_balance: {:?}, bridged_to_this_conversion_rate_override: {:?}", bridged_balance, bridged_to_this_conversion_rate_override); - unimplemented!("TODO: WithBridgeHubRococoMessageBridge - bridged_balance_to_this_balance") + log::info!("[WithBridgeHubRococoMessageBridge] bridged_balance_to_this_balance (returns 0 balance, TODO: fix) - bridged_balance: {:?}, bridged_to_this_conversion_rate_override: {:?}", bridged_balance, bridged_to_this_conversion_rate_override); + // TODO:check-parameter - any payment? from sovereign account? + 0 } } @@ -193,14 +194,11 @@ impl messages::BridgedChainWithMessages for BridgeHubRococo { transaction: MessageTransaction>, ) -> messages::BalanceOf { log::info!( - "[BridgeHubRococo::BridgedChainWithMessages] transaction_payment - transaction: {:?}", + "[BridgeHubRococo::BridgedChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", transaction ); // TODO:check-parameter - any payment? from sovereign account? - unimplemented!( - "[BridgeHubRococo/BridgedChainWithMessages] transaction_payment - transaction: {:?}", - transaction - ) + 0 } } @@ -243,13 +241,10 @@ impl ThisChainWithMessages for BridgeHubWococo { transaction: MessageTransaction>, ) -> messages::BalanceOf { log::info!( - "[BridgeHubWococo::ThisChainWithMessages] transaction_payment - transaction: {:?}", + "[BridgeHubWococo::ThisChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", transaction ); // TODO:check-parameter - any payment? from sovereign account? - unimplemented!( - "[BridgeHubWococo/ThisChainWithMessages] transaction_payment - transaction: {:?}", - transaction - ) + 0 } } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 66e3686cf6e..9e7f48b5653 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -28,6 +28,7 @@ pub mod bridge_hub_wococo_config; mod weights; pub mod xcm_config; +use codec::Decode; use bridge_common_config::*; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use smallvec::smallvec; @@ -247,6 +248,8 @@ pub mod runtime_api { use super::{BlockNumber, Hash}; bp_runtime::decl_bridge_finality_runtime_apis!(rococo); bp_runtime::decl_bridge_finality_runtime_apis!(wococo); + bp_runtime::decl_bridge_finality_runtime_apis!(bridge_hub_rococo); + bp_runtime::decl_bridge_finality_runtime_apis!(bridge_hub_wococo); use bp_messages::{ InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, @@ -811,6 +814,23 @@ impl_runtime_apis! { } } + + impl runtime_api::BridgeHubRococoFinalityApi for Runtime { + fn best_finalized() -> Option> { + let encoded_head = BridgeRococoParachain::best_parachain_head(bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID.into())?; + let head = bp_bridge_hub_rococo::Header::decode(&mut &encoded_head.0[..]).ok()?; + Some(head.id()) + } + } + + impl runtime_api::BridgeHubWococoFinalityApi for Runtime { + fn best_finalized() -> Option> { + let encoded_head = BridgeWococoParachain::best_parachain_head(bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID.into())?; + let head = bp_bridge_hub_wococo::Header::decode(&mut &encoded_head.0[..]).ok()?; + Some(head.id()) + } + } + // This exposed by BridgeHubRococo impl runtime_api::ToBridgeHubWococoOutboundLaneApi for Runtime { fn estimate_message_delivery_and_dispatch_fee( From 234d048c8d5a10597a2b67b38554c4814621d027 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 10 Nov 2022 13:04:12 +0100 Subject: [PATCH 84/91] BridgeHub fixes and cleaning --- Cargo.lock | 491 +++++++++++++----- Cargo.toml | 10 +- parachains/runtimes/bridge-hubs/README.md | 2 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 4 + .../src/bridge_common_config.rs | 37 +- .../src/bridge_hub_rococo_config.rs | 39 +- .../src/bridge_hub_wococo_config.rs | 43 +- .../bridge-hub-rococo/src/constants.rs | 64 +++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 100 ++-- .../src/weights/block_weights.rs | 2 +- .../src/weights/cumulus_pallet_xcmp_queue.rs | 61 +++ .../src/weights/extrinsic_weights.rs | 2 +- .../bridge-hub-rococo/src/weights/mod.rs | 3 + .../src/weights/pallet_balances.rs | 91 ++++ .../src/weights/paritydb_weights.rs | 4 +- .../src/weights/rocksdb_weights.rs | 4 +- .../bridge-hub-rococo/src/weights/xcm/mod.rs | 247 +++++++++ .../xcm/pallet_xcm_benchmarks_fungible.rs | 107 ++++ .../xcm/pallet_xcm_benchmarks_generic.rs | 189 +++++++ .../bridge-hub-rococo/src/xcm_config.rs | 58 +-- polkadot-parachain/src/command.rs | 2 - 21 files changed, 1272 insertions(+), 288 deletions(-) create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/constants.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_balances.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs create mode 100644 parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs diff --git a/Cargo.lock b/Cargo.lock index e1cb2bffe34..127f1a1c1cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,10 +717,12 @@ dependencies = [ name = "bp-bridge-hub-rococo" version = "0.1.0" dependencies = [ + "bp-messages", "bp-polkadot-core", "bp-runtime", "frame-support", "sp-api", + "sp-std", ] [[package]] @@ -728,17 +730,19 @@ name = "bp-bridge-hub-wococo" version = "0.1.0" dependencies = [ "bp-bridge-hub-rococo", + "bp-messages", "bp-runtime", + "frame-support", "sp-api", + "sp-std", ] [[package]] name = "bp-header-chain" version = "0.1.0" dependencies = [ - "assert_matches", - "bp-runtime 0.1.0", - "bp-test-utils 0.1.0", + "bp-runtime", + "bp-test-utils", "finality-grandpa", "frame-support", "hex", @@ -752,41 +756,12 @@ dependencies = [ "sp-std", ] -[[package]] -name = "bp-header-chain" -version = "0.1.0" -source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" -dependencies = [ - "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "finality-grandpa", - "frame-support", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-finality-grandpa", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "bp-message-dispatch" -version = "0.1.0" -source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" -dependencies = [ - "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "frame-support", - "parity-scale-codec", - "scale-info", - "sp-std", -] - [[package]] name = "bp-messages" version = "0.1.0" dependencies = [ "bitvec", - "bp-runtime 0.1.0", + "bp-runtime", "frame-support", "hex", "hex-literal", @@ -799,28 +774,34 @@ dependencies = [ ] [[package]] -name = "bp-messages" +name = "bp-millau" version = "0.1.0" -source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" dependencies = [ - "bitvec", - "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "bp-messages", + "bp-runtime", + "fixed-hash 0.7.0", "frame-support", "frame-system", - "impl-trait-for-tuples", - "parity-scale-codec", + "hash256-std-hasher", + "impl-codec", + "impl-serde 0.3.2", + "parity-util-mem", "scale-info", "serde", + "sp-api", "sp-core", + "sp-io", + "sp-runtime", "sp-std", + "sp-trie", ] [[package]] name = "bp-parachains" version = "0.1.0" dependencies = [ - "bp-polkadot-core 0.1.0", - "bp-runtime 0.1.0", + "bp-polkadot-core", + "bp-runtime", "frame-support", "parity-scale-codec", "scale-info", @@ -831,8 +812,8 @@ dependencies = [ name = "bp-polkadot-core" version = "0.1.0" dependencies = [ - "bp-messages 0.1.0", - "bp-runtime 0.1.0", + "bp-messages", + "bp-runtime", "frame-support", "frame-system", "hex", @@ -846,30 +827,40 @@ dependencies = [ ] [[package]] -name = "bp-polkadot-core" +name = "bp-relayers" version = "0.1.0" -source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" dependencies = [ - "bp-messages 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "frame-support", + "hex", + "hex-literal", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-rialto" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-runtime", "frame-support", "frame-system", - "parity-scale-codec", - "scale-info", "sp-api", "sp-core", "sp-runtime", "sp-std", - "sp-version", ] [[package]] -name = "bp-relayers" +name = "bp-rialto-parachain" version = "0.1.0" dependencies = [ + "bp-messages", + "bp-runtime", "frame-support", - "hex", - "hex-literal", + "frame-system", + "sp-api", + "sp-core", "sp-runtime", "sp-std", ] @@ -902,31 +893,14 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-trie", -] - -[[package]] -name = "bp-runtime" -version = "0.1.0" -source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" -dependencies = [ - "frame-support", - "hash-db", - "num-traits", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-state-machine", - "sp-std", - "sp-trie", + "trie-db", ] [[package]] name = "bp-test-utils" version = "0.1.0" dependencies = [ - "bp-header-chain 0.1.0", + "bp-header-chain", "ed25519-dalek", "finality-grandpa", "parity-scale-codec", @@ -937,18 +911,12 @@ dependencies = [ ] [[package]] -name = "bp-test-utils" +name = "bp-westend" version = "0.1.0" -source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#81128ab75395e256ae8ef50994d46101d0e67cea" dependencies = [ - "bp-header-chain 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "ed25519-dalek", - "finality-grandpa", - "parity-scale-codec", - "sp-application-crypto", - "sp-finality-grandpa", - "sp-runtime", - "sp-std", + "bp-polkadot-core", + "bp-runtime", + "sp-api", ] [[package]] @@ -972,6 +940,7 @@ dependencies = [ "bp-rococo", "bp-runtime", "bp-wococo", + "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", @@ -993,8 +962,13 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-bridge-relayers", "pallet-collator-selection", "pallet-session", + "pallet-shift-session-manager", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", @@ -1003,8 +977,10 @@ dependencies = [ "parachain-info", "parachains-common", "parity-scale-codec", - "polkadot-parachain 0.9.27", + "polkadot-core-primitives", + "polkadot-parachain 0.9.31", "polkadot-runtime-common", + "polkadot-runtime-constants", "scale-info", "serde", "smallvec", @@ -1026,6 +1002,38 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "bridge-runtime-common" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-parachains", + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "frame-system", + "hash-db", + "log", + "millau-runtime", + "pallet-balances", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-xcm", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", + "static_assertions", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "bs58" version = "0.4.0" @@ -2930,6 +2938,19 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.9.1" @@ -2970,6 +2991,33 @@ dependencies = [ "libc", ] +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash 0.8.0", + "impl-rlp", + "impl-serde 0.4.0", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81224dc661606574f5a0f28c9947d0ee1d93ff11c5f1c4e7272f52e8c0b5483c" +dependencies = [ + "ethbloom", + "fixed-hash 0.8.0", + "impl-rlp", + "impl-serde 0.4.0", + "primitive-types", + "uint", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -3081,7 +3129,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" dependencies = [ - "env_logger", + "env_logger 0.9.1", "log", ] @@ -3113,6 +3161,18 @@ dependencies = [ "scale-info", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -3990,6 +4050,24 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + [[package]] name = "impl-serde" version = "0.4.0" @@ -5101,6 +5179,68 @@ dependencies = [ "thrift", ] +[[package]] +name = "millau-runtime" +version = "0.1.0" +dependencies = [ + "beefy-primitives", + "bp-messages", + "bp-millau", + "bp-polkadot-core", + "bp-relayers", + "bp-rialto", + "bp-rialto-parachain", + "bp-runtime", + "bp-westend", + "bridge-runtime-common", + "env_logger 0.8.4", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "hex-literal", + "log", + "pallet-aura", + "pallet-balances", + "pallet-beefy", + "pallet-beefy-mmr", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-bridge-relayers", + "pallet-grandpa", + "pallet-mmr", + "pallet-randomness-collective-flip", + "pallet-session", + "pallet-shift-session-manager", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-xcm", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-io", + "sp-mmr-primitives", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", + "static_assertions", + "substrate-wasm-builder", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -5585,7 +5725,7 @@ checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" [[package]] name = "pallet-alliance" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "frame-benchmarking", @@ -5606,7 +5746,7 @@ dependencies = [ [[package]] name = "pallet-asset-tx-payment" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-support", "frame-system", @@ -5623,7 +5763,7 @@ dependencies = [ [[package]] name = "pallet-assets" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -5797,6 +5937,94 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-bridge-grandpa" +version = "0.1.0" +dependencies = [ + "bp-header-chain", + "bp-runtime", + "bp-test-utils", + "finality-grandpa", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-finality-grandpa", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-bridge-messages" +version = "0.1.0" +dependencies = [ + "bitvec", + "bp-messages", + "bp-runtime", + "bp-test-utils", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "num-traits", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-bridge-parachains" +version = "0.1.0" +dependencies = [ + "bp-header-chain", + "bp-parachains", + "bp-polkadot-core", + "bp-runtime", + "bp-test-utils", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-bridge-grandpa", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-bridge-relayers" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-relayers", + "bp-runtime", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-bridge-messages", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-child-bounties" version = "4.0.0-dev" @@ -6424,6 +6652,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-shift-session-manager" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-staking", + "sp-std", +] + [[package]] name = "pallet-society" version = "4.0.0-dev" @@ -6934,8 +7177,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" dependencies = [ "cfg-if 1.0.0", + "ethereum-types", "hashbrown", "impl-trait-for-tuples", + "lru 0.8.1", "parity-util-mem-derive", "parking_lot 0.12.1", "primitive-types", @@ -8072,7 +8317,7 @@ name = "polkadot-performance-test" version = "0.9.31" source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" dependencies = [ - "env_logger", + "env_logger 0.9.1", "kusama-runtime", "log", "polkadot-erasure-coding", @@ -8711,9 +8956,10 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cfd65aea0c5fa0bfcc7c9e7ca828c921ef778f43d325325ec84bda371bfa75a" dependencies = [ - "fixed-hash", + "fixed-hash 0.8.0", "impl-codec", - "impl-serde", + "impl-rlp", + "impl-serde 0.4.0", "scale-info", "uint", ] @@ -9148,7 +9394,7 @@ name = "remote-externalities" version = "0.10.0-dev" source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ - "env_logger", + "env_logger 0.9.1", "log", "parity-scale-codec", "serde", @@ -9205,6 +9451,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rocksdb" version = "0.19.0" @@ -9269,11 +9525,6 @@ source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm- dependencies = [ "beefy-merkle-tree", "beefy-primitives", - "bp-messages 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-rococo 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-runtime 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bp-wococo 0.1.0 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", - "bridge-runtime-common", "frame-benchmarking", "frame-executive", "frame-support", @@ -9901,7 +10152,6 @@ dependencies = [ "sc-allocator", "sp-maybe-compressed-blob", "sp-sandbox", - "sp-serializer 4.0.0-dev (git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges)", "sp-wasm-interface", "thiserror", "wasm-instrument", @@ -11296,7 +11546,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde", + "impl-serde 0.4.0", "lazy_static", "libsecp256k1", "log", @@ -11607,16 +11857,7 @@ dependencies = [ [[package]] name = "sp-serializer" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#4e1e17cccd499dfe49e8c1bed01957953aa4c839" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "sp-serializer" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#aa7520bd0a2094204a6c0b33865aa264e6d686a5" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "serde", "serde_json", @@ -11679,7 +11920,7 @@ name = "sp-storage" version = "6.0.0" source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", @@ -11781,7 +12022,7 @@ name = "sp-version" version = "5.0.0" source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "parity-wasm", "scale-info", @@ -12472,6 +12713,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -13464,7 +13714,7 @@ dependencies = [ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", - "westend-runtime-constants 0.9.31 (git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges)", + "westend-runtime-constants", "xcm", "xcm-builder", "xcm-executor", @@ -13482,18 +13732,6 @@ dependencies = [ "sp-runtime", ] -[[package]] -name = "westend-runtime-constants" -version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=master#40aefb4ac396bcd098755c6d57dac7b284a343e7" -dependencies = [ - "frame-support", - "polkadot-primitives", - "polkadot-runtime-common", - "smallvec", - "sp-runtime", -] - [[package]] name = "westmint-runtime" version = "1.0.0" @@ -13553,7 +13791,7 @@ dependencies = [ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", - "westend-runtime-constants 0.9.31 (git+https://github.com/paritytech/polkadot?branch=master)", + "westend-runtime-constants", "xcm", "xcm-builder", "xcm-executor", @@ -13851,8 +14089,3 @@ dependencies = [ "cc", "libc", ] - -[[patch.unused]] -name = "node-inspect" -version = "0.9.0-dev" -source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" diff --git a/Cargo.toml b/Cargo.toml index 7e3eb2d88c6..2939fd905eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,18 @@ [workspace] members = [ + "bridges/bin/runtime-common", "bridges/modules/grandpa", "bridges/modules/messages", "bridges/modules/parachains", "bridges/modules/relayers", "bridges/modules/shift-session-manager", + "bridges/primitives/messages", "bridges/primitives/polkadot-core", "bridges/primitives/runtime", "bridges/primitives/chain-bridge-hub-rococo", "bridges/primitives/chain-bridge-hub-wococo", + "bridges/primitives/chain-rococo", + "bridges/primitives/chain-wococo", "client/cli", "client/consensus/aura", "client/consensus/common", @@ -89,7 +93,9 @@ frame-system = { git = "https://github.com/paritytech//substrate", branch = "sv- frame-system-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } frame-system-rpc-runtime-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } frame-try-runtime = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } -node-inspect = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-alliance = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-assets = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-asset-tx-payment = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } pallet-aura = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } pallet-authority-discovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } pallet-authorship = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } @@ -221,6 +227,7 @@ sp-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked sp-runtime = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } sp-runtime-interface = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } sp-runtime-interface-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-serializer = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } sp-session = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } sp-staking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } sp-state-machine = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } @@ -296,6 +303,7 @@ polkadot-test-runtime = { git = "https://github.com/paritytech//polkadot", branc slot-range-helper = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } tracing-gum = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } tracing-gum-proc-macro = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +westend-runtime-constants = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } xcm = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } xcm-builder = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } xcm-executor = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index b446c74b084..1509df525b1 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -31,7 +31,7 @@ cp target/release/polkadot ~/local_bridge_testing/bin/polkadot # 2. Build cumulus polkadot-parachain binary cd -cargo build --release --locked -p polkadot-parachain@0.9.230 +cargo build --release --locked -p polkadot-parachain@0.9.300 cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-parachain # 3. Build substrate-relay binary diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 000db6e66b5..3845258ed6e 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -47,8 +47,10 @@ sp-version = { git = "https://github.com/paritytech/substrate", default-features # Polkadot pallet-xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +polkadot-runtime-constants = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } @@ -128,8 +130,10 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", + "polkadot-core-primitives/std", "polkadot-parachain/std", "polkadot-runtime-common/std", + "polkadot-runtime-constants/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index eb77346e09a..85414d09bef 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::universal_exports::HaulBlob; use bp_messages::{ source_chain::MessagesBridge, target_chain::{DispatchMessage, MessageDispatch}, @@ -24,6 +23,7 @@ use bp_runtime::{messages::MessageDispatchResult, AccountIdOf, BalanceOf, Chain} use codec::Encode; use frame_support::{dispatch::Weight, parameter_types}; use xcm::latest::prelude::*; +use xcm_builder::{DispatchBlob, DispatchBlobError, HaulBlob}; // TODO:check-parameter - we could possibly use BridgeMessage from xcm:v3 stuff /// PLain "XCM" payload, which we transfer through bridge @@ -45,9 +45,9 @@ pub struct XcmBlobMessageDispatch MessageDispatch, BalanceOf> - for XcmBlobMessageDispatch + for XcmBlobMessageDispatch { type DispatchPayload = XcmAsPlainPayload; @@ -57,7 +57,7 @@ impl< log::error!( "[XcmBlobMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor - message: ?...?", ); - 0 + Weight::zero() } fn dispatch( @@ -71,14 +71,23 @@ impl< log::error!("[XcmBlobMessageDispatch] payload error: {:?}", e); return MessageDispatchResult { dispatch_result: false, - unspent_weight: 0, + unspent_weight: Weight::zero(), dispatch_fee_paid_during_dispatch: false, } }, }; - let dispatch_result = match DispatchBlob::dispatch_blob(payload) { + let dispatch_result = match BlobDispatcher::dispatch_blob(payload) { Ok(_) => true, Err(e) => { + let e= match e { + DispatchBlobError::Unbridgable => "DispatchBlobError::Unbridgable", + DispatchBlobError::InvalidEncoding => "DispatchBlobError::InvalidEncoding", + DispatchBlobError::UnsupportedLocationVersion => "DispatchBlobError::UnsupportedLocationVersion", + DispatchBlobError::UnsupportedXcmVersion => "DispatchBlobError::UnsupportedXcmVersion", + DispatchBlobError::RoutingError => "DispatchBlobError::RoutingError", + DispatchBlobError::NonUniversalDestination => "DispatchBlobError::NonUniversalDestination", + DispatchBlobError::WrongGlobal => "DispatchBlobError::WrongGlobal", + }; log::error!( "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?}", e @@ -89,7 +98,7 @@ impl< MessageDispatchResult { dispatch_result, dispatch_fee_paid_during_dispatch: false, - unspent_weight: 0, + unspent_weight: Weight::zero(), } } } @@ -102,8 +111,7 @@ pub trait XcmBlobHauler { /// Runtime message sender adapter. type MessageSender: MessagesBridge< - super::Origin, - AccountIdOf, + super::RuntimeOrigin, BalanceOf, XcmAsPlainPayload, >; @@ -115,15 +123,16 @@ pub trait XcmBlobHauler { fn xcm_lane() -> LaneId; } -impl HaulBlob for T { +pub struct XcmBlobHaulerAdapter(sp_std::marker::PhantomData); +impl HaulBlob for XcmBlobHaulerAdapter { fn haul_blob(blob: sp_std::prelude::Vec) { - let lane = T::xcm_lane(); + let lane = H::xcm_lane(); // TODO:check-parameter - fee could be taken from BridgeMessage - or add as optional fo send_message // TODO:check-parameter - or add here something like PriceForSiblingDelivery - let fee = ::Balance::from(0u8); + let fee = ::Balance::from(0u8); - let result = T::MessageSender::send_message( - pallet_xcm::Origin::from(MultiLocation::from(T::message_sender_origin())).into(), + let result = H::MessageSender::send_message( + pallet_xcm::Origin::from(MultiLocation::from(H::message_sender_origin())).into(), lane, blob, fee, diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs index ff382c7f5e4..475adf13100 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -14,11 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::{ - universal_exports::{BridgeBlobDispatcher, HaulBlobExporter}, - ParachainInfo, Runtime, WithBridgeHubWococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, - XcmRouter, -}; +use crate::{ParachainInfo, Runtime, WithBridgeHubWococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmRouter}; use bp_messages::{ source_chain::TargetHeaderChain, target_chain::{ProvedMessages, SourceHeaderChain}, @@ -31,7 +27,7 @@ use bridge_runtime_common::{ messages::{ target::FromBridgedChainMessagesProof, BasicConfirmationTransactionEstimation, BridgedChain, ChainWithMessages, MessageBridge, MessageTransaction, ThisChain, - ThisChainWithMessages, WeightOf, + ThisChainWithMessages, }, }; use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; @@ -40,6 +36,7 @@ use xcm::{ latest::prelude::*, prelude::{InteriorMultiLocation, NetworkId}, }; +use xcm_builder::{BridgeBlobDispatcher, HaulBlobExporter}; // TODO:check-parameter parameter_types! { @@ -58,7 +55,7 @@ pub type OnBridgeHubRococoBlobDispatcher = /// Export XCM messages to be relayed to the otherside pub type ToBridgeHubWococoHaulBlobExporter = - HaulBlobExporter; + HaulBlobExporter, WococoGlobalConsensusNetwork, ()>; pub struct ToBridgeHubWococoXcmBlobHauler; pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO: LaneId = [0, 0, 0, 2]; impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { @@ -114,7 +111,6 @@ impl ChainWithMessages for BridgeHubWococo { type AccountId = bp_bridge_hub_wococo::AccountId; type Signer = bp_bridge_hub_wococo::AccountSigner; type Signature = bp_bridge_hub_wococo::Signature; - type Weight = Weight; type Balance = bp_bridge_hub_wococo::Balance; } @@ -168,18 +164,18 @@ impl messages::BridgedChainWithMessages for BridgeHubWococo { fn estimate_delivery_transaction( message_payload: &[u8], include_pay_dispatch_fee_cost: bool, - message_dispatch_weight: WeightOf, - ) -> MessageTransaction> { + message_dispatch_weight: Weight, + ) -> MessageTransaction { let message_payload_len = u32::try_from(message_payload.len()).unwrap_or(u32::MAX); - let extra_bytes_in_payload = Weight::from(message_payload_len) - .saturating_sub(pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH.into()); + let extra_bytes_in_payload = message_payload_len + .saturating_sub(pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH); MessageTransaction { - dispatch_weight: extra_bytes_in_payload - .saturating_mul(bp_bridge_hub_wococo::ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT) + dispatch_weight: bp_bridge_hub_wococo::ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT + .saturating_mul(extra_bytes_in_payload as u64) .saturating_add(bp_bridge_hub_wococo::DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT) .saturating_sub(if include_pay_dispatch_fee_cost { - 0 + Weight::from_ref_time(0) } else { bp_bridge_hub_wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT }) @@ -191,7 +187,7 @@ impl messages::BridgedChainWithMessages for BridgeHubWococo { } fn transaction_payment( - transaction: MessageTransaction>, + transaction: MessageTransaction, ) -> messages::BalanceOf { log::info!( "[BridgeHubWococo::BridgedChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", @@ -211,21 +207,20 @@ impl ChainWithMessages for BridgeHubRococo { type AccountId = bp_bridge_hub_rococo::AccountId; type Signer = bp_bridge_hub_rococo::AccountSigner; type Signature = bp_bridge_hub_rococo::Signature; - type Weight = Weight; type Balance = bp_bridge_hub_rococo::Balance; } impl ThisChainWithMessages for BridgeHubRococo { - type Origin = crate::Origin; - type Call = crate::Call; + type RuntimeOrigin = crate::RuntimeOrigin; + type RuntimeCall = crate::RuntimeCall; type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation< Self::AccountId, - { bp_bridge_hub_rococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT }, + { bp_bridge_hub_rococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() }, { bp_bridge_hub_wococo::EXTRA_STORAGE_PROOF_SIZE }, { bp_bridge_hub_rococo::TX_EXTRA_BYTES }, >; - fn is_message_accepted(origin: &Self::Origin, lane: &LaneId) -> bool { + fn is_message_accepted(origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool { log::info!("[BridgeHubRococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); lane == &DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO } @@ -238,7 +233,7 @@ impl ThisChainWithMessages for BridgeHubRococo { } fn transaction_payment( - transaction: MessageTransaction>, + transaction: MessageTransaction, ) -> messages::BalanceOf { log::info!( "[BridgeHubRococo::ThisChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs index 02df9574583..99dd24d6a29 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -14,11 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::{ - universal_exports::{BridgeBlobDispatcher, HaulBlobExporter}, - ParachainInfo, Runtime, WithBridgeHubRococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, - XcmRouter, -}; +use crate::{ParachainInfo, Runtime, WithBridgeHubRococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmRouter}; use bp_messages::{ source_chain::TargetHeaderChain, target_chain::{ProvedMessages, SourceHeaderChain}, @@ -31,7 +27,7 @@ use bridge_runtime_common::{ messages::{ target::FromBridgedChainMessagesProof, BasicConfirmationTransactionEstimation, BridgedChain, ChainWithMessages, MessageBridge, MessageTransaction, ThisChain, - ThisChainWithMessages, WeightOf, + ThisChainWithMessages, }, }; use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; @@ -40,6 +36,7 @@ use xcm::{ latest::prelude::*, prelude::{InteriorMultiLocation, NetworkId}, }; +use xcm_builder::{BridgeBlobDispatcher, HaulBlobExporter}; // TODO:check-parameter parameter_types! { @@ -58,7 +55,7 @@ pub type OnBridgeHubWococoBlobDispatcher = /// Export XCM messages to be relayed to the otherside pub type ToBridgeHubRococoHaulBlobExporter = - HaulBlobExporter; + HaulBlobExporter, RococoGlobalConsensusNetwork, ()>; pub struct ToBridgeHubRococoXcmBlobHauler; pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO: LaneId = [0, 0, 0, 1]; impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { @@ -114,7 +111,6 @@ impl ChainWithMessages for BridgeHubRococo { type AccountId = bp_bridge_hub_rococo::AccountId; type Signer = bp_bridge_hub_rococo::AccountSigner; type Signature = bp_bridge_hub_rococo::Signature; - type Weight = Weight; type Balance = bp_bridge_hub_rococo::Balance; } @@ -168,18 +164,18 @@ impl messages::BridgedChainWithMessages for BridgeHubRococo { fn estimate_delivery_transaction( message_payload: &[u8], include_pay_dispatch_fee_cost: bool, - message_dispatch_weight: WeightOf, - ) -> MessageTransaction> { + message_dispatch_weight: Weight, + ) -> MessageTransaction { let message_payload_len = u32::try_from(message_payload.len()).unwrap_or(u32::MAX); - let extra_bytes_in_payload = Weight::from(message_payload_len) - .saturating_sub(pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH.into()); + let extra_bytes_in_payload = message_payload_len + .saturating_sub(pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH); MessageTransaction { - dispatch_weight: extra_bytes_in_payload - .saturating_mul(bp_bridge_hub_rococo::ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT) + dispatch_weight: bp_bridge_hub_rococo::ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT + .saturating_mul(extra_bytes_in_payload as u64) .saturating_add(bp_bridge_hub_rococo::DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT) .saturating_sub(if include_pay_dispatch_fee_cost { - 0 + Weight::from_ref_time(0) } else { bp_bridge_hub_rococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT }) @@ -190,9 +186,7 @@ impl messages::BridgedChainWithMessages for BridgeHubRococo { } } - fn transaction_payment( - transaction: MessageTransaction>, - ) -> messages::BalanceOf { + fn transaction_payment(transaction: MessageTransaction) -> messages::BalanceOf { log::info!( "[BridgeHubRococo::BridgedChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", transaction @@ -211,21 +205,20 @@ impl ChainWithMessages for BridgeHubWococo { type AccountId = bp_bridge_hub_wococo::AccountId; type Signer = bp_bridge_hub_wococo::AccountSigner; type Signature = bp_bridge_hub_wococo::Signature; - type Weight = Weight; type Balance = bp_bridge_hub_wococo::Balance; } impl ThisChainWithMessages for BridgeHubWococo { - type Origin = crate::Origin; - type Call = crate::Call; + type RuntimeOrigin = crate::RuntimeOrigin; + type RuntimeCall = crate::RuntimeCall; type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation< Self::AccountId, - { bp_bridge_hub_wococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT }, + { bp_bridge_hub_wococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() }, { bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE }, { bp_bridge_hub_wococo::TX_EXTRA_BYTES }, >; - fn is_message_accepted(origin: &Self::Origin, lane: &LaneId) -> bool { + fn is_message_accepted(origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool { log::info!("[BridgeHubWococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); lane == &DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO } @@ -237,9 +230,7 @@ impl ThisChainWithMessages for BridgeHubWococo { MessageNonce::MAX / 2 } - fn transaction_payment( - transaction: MessageTransaction>, - ) -> messages::BalanceOf { + fn transaction_payment(transaction: MessageTransaction) -> messages::BalanceOf { log::info!( "[BridgeHubWococo::ThisChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", transaction diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/constants.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/constants.rs new file mode 100644 index 00000000000..4352e3ef554 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/constants.rs @@ -0,0 +1,64 @@ +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod currency { + use polkadot_core_primitives::Balance; + use polkadot_runtime_constants as constants; + + /// The existential deposit. Set to 1/10 of its parent Relay Chain (v9010). + pub const EXISTENTIAL_DEPOSIT: Balance = constants::currency::EXISTENTIAL_DEPOSIT / 10; + + pub const CENTS: Balance = constants::currency::CENTS; +} + +pub mod fee { + use frame_support::weights::{ + constants::ExtrinsicBaseWeight, WeightToFeeCoefficient, WeightToFeeCoefficients, + WeightToFeePolynomial, + }; + use polkadot_core_primitives::Balance; + use smallvec::smallvec; + pub use sp_runtime::Perbill; + + /// The block saturation level. Fees will be updates based on this value. + pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); + + /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the + /// node's balance type. + /// + /// This should typically create a mapping between the following ranges: + /// - [0, MAXIMUM_BLOCK_WEIGHT] + /// - [Balance::min, Balance::max] + /// + /// Yet, it can be used for any other sort of change to weight-fee. Some examples being: + /// - Setting it to `0` will essentially disable the weight fee. + /// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. + pub struct WeightToFee; + impl WeightToFeePolynomial for WeightToFee { + type Balance = Balance; + fn polynomial() -> WeightToFeeCoefficients { + // in Polkadot, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: + // in Statemint, we map to 1/10 of that, or 1/100 CENT + let p = super::currency::CENTS; + let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); + smallvec![WeightToFeeCoefficient { + degree: 1, + negative: false, + coeff_frac: Perbill::from_rational(p % q, q), + coeff_integer: p / q, + }] + } + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 9e7f48b5653..d154f9721d7 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -25,11 +25,13 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); pub mod bridge_common_config; pub mod bridge_hub_rococo_config; pub mod bridge_hub_wococo_config; +pub mod constants; mod weights; pub mod xcm_config; use codec::Decode; use bridge_common_config::*; +use constants::currency::*; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use smallvec::smallvec; use sp_api::impl_runtime_apis; @@ -48,9 +50,10 @@ use sp_version::RuntimeVersion; use frame_support::{ construct_runtime, parameter_types, + dispatch::DispatchClass, traits::Everything, weights::{ - constants::WEIGHT_PER_SECOND, ConstantMultiplier, DispatchClass, Weight, + ConstantMultiplier, Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, }, PalletId, @@ -77,7 +80,7 @@ use crate::{ bridge_hub_wococo_config::OnBridgeHubWococoBlobDispatcher, xcm_config::XcmRouter, }; -use parachains_common::{AccountId, Signature}; +use parachains_common::{AccountId, Signature, AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, MAXIMUM_BLOCK_WEIGHT}; use xcm::latest::prelude::BodyId; use xcm_executor::XcmExecutor; @@ -123,10 +126,10 @@ pub type SignedExtra = ( ); /// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Extrinsic type that has already been checked. -pub type CheckedExtrinsic = generic::CheckedExtrinsic; +pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -154,7 +157,7 @@ impl WeightToFeePolynomial for WeightToFee { // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1 MILLIUNIT: // in our template, we map to 1/10 of that, or 1/10 MILLIUNIT let p = MILLIUNIT / 10; - let q = 100 * Balance::from(ExtrinsicBaseWeight::get()); + let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); smallvec![WeightToFeeCoefficient { degree: 1, negative: false, @@ -223,19 +226,6 @@ pub const UNIT: Balance = 1_000_000_000_000; pub const MILLIUNIT: Balance = 1_000_000_000; pub const MICROUNIT: Balance = 1_000_000; -/// The existential deposit. Set to 1/10 of the Connected Relay Chain. -pub const EXISTENTIAL_DEPOSIT: Balance = MILLIUNIT; - -/// We assume that ~5% of the block weight is consumed by `on_initialize` handlers. This is -/// used to limit the maximal weight of a single extrinsic. -const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(5); - -/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used by -/// `Operational` extrinsics. -const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); - -/// We allow for 0.5 of a second of compute with a 12 second average block time. -const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2; /// The version information used to identify this runtime when compiled natively. #[cfg(feature = "std")] @@ -262,11 +252,6 @@ pub mod runtime_api { parameter_types! { pub const Version: RuntimeVersion = VERSION; - - // This part is copied from Substrate's `bin/node/runtime/src/lib.rs`. - // The `RuntimeBlockLength` and `RuntimeBlockWeights` exist here because the - // `DeletionWeightLimit` and `DeletionQueueDepth` depend on those to parameterize - // the lazy contract deletion. pub RuntimeBlockLength: BlockLength = BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() @@ -296,7 +281,7 @@ impl frame_system::Config for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; /// The aggregated dispatch type that is available for extrinsics. - type Call = Call; + type RuntimeCall = RuntimeCall; /// The lookup mechanism to get account ID from whatever is passed in dispatchers. type Lookup = AccountIdLookup; /// The index type for storing how many extrinsics an account has signed. @@ -310,9 +295,9 @@ impl frame_system::Config for Runtime { /// The header type. type Header = generic::Header; /// The ubiquitous event type. - type Event = Event; + type RuntimeEvent = RuntimeEvent; /// The ubiquitous origin type. - type Origin = Origin; + type RuntimeOrigin = RuntimeOrigin; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; /// Runtime version. @@ -365,7 +350,6 @@ impl pallet_authorship::Config for Runtime { type EventHandler = (CollatorSelection,); } -// TODO:check-parameter parameter_types! { pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; pub const MaxLocks: u32 = 50; @@ -377,11 +361,11 @@ impl pallet_balances::Config for Runtime { /// The type for recording an account's balance. type Balance = Balance; /// The ubiquitous event type. - type Event = Event; + type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; - type WeightInfo = pallet_balances::weights::SubstrateWeight; + type WeightInfo = weights::pallet_balances::WeightInfo; type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; } @@ -394,7 +378,7 @@ parameter_types! { } impl pallet_transaction_payment::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; type WeightToFee = WeightToFee; type LengthToFee = ConstantMultiplier; @@ -403,12 +387,12 @@ impl pallet_transaction_payment::Config for Runtime { } parameter_types! { - pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; - pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); } impl cumulus_pallet_parachain_system::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); type SelfParaId = parachain_info::Pallet; type OutboundXcmpMessageSource = XcmpQueue; @@ -424,7 +408,7 @@ impl parachain_info::Config for Runtime {} impl cumulus_pallet_aura_ext::Config for Runtime {} impl cumulus_pallet_xcmp_queue::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; @@ -436,7 +420,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { } impl cumulus_pallet_dmp_queue::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type XcmExecutor = XcmExecutor; type ExecuteOverweightOrigin = EnsureRoot; } @@ -448,7 +432,7 @@ parameter_types! { } impl pallet_session::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type ValidatorId = ::AccountId; // we don't have stash and controller, thus we don't need the convert as well. type ValidatorIdOf = pallet_collator_selection::IdentityCollator; @@ -480,7 +464,7 @@ parameter_types! { pub type CollatorSelectionUpdateOrigin = EnsureRoot; impl pallet_collator_selection::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type UpdateOrigin = CollatorSelectionUpdateOrigin; type PotId = PotId; @@ -496,8 +480,8 @@ impl pallet_collator_selection::Config for Runtime { } impl pallet_sudo::Config for Runtime { - type Call = Call; - type Event = Event; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; } // Add bridge pallets (GPA) @@ -508,8 +492,9 @@ impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_wococo::Wococo; type MaxRequests = MaxRequests; type HeadersToKeep = HeadersToKeep; - // TODO:check-parameter - type WeightInfo = (); + type MaxBridgedAuthorities = frame_support::traits::ConstU32<{bp_wococo::MAX_AUTHORITIES_COUNT}>; + type MaxBridgedHeaderSize = frame_support::traits::ConstU32<{bp_wococo::MAX_HEADER_SIZE}>; + type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; } /// Add granda bridge pallet to track Rococo relay chain @@ -518,8 +503,9 @@ impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_rococo::Rococo; type MaxRequests = MaxRequests; type HeadersToKeep = HeadersToKeep; - // TODO:check-parameter - type WeightInfo = (); + type MaxBridgedAuthorities = frame_support::traits::ConstU32<{bp_rococo::MAX_AUTHORITIES_COUNT}>; + type MaxBridgedHeaderSize = frame_support::traits::ConstU32<{bp_rococo::MAX_HEADER_SIZE}>; + type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; } pub const ROCOCO_BRIDGE_PARA_PALLET_NAME: &str = "Paras"; @@ -533,33 +519,32 @@ parameter_types! { /// Add parachain bridge pallet to track Wococo bridge hub parachain pub type BridgeParachainWococoInstance = pallet_bridge_parachains::Instance1; impl pallet_bridge_parachains::Config for Runtime { - type Event = Event; - // TODO:check-parameter - type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight; type BridgesGrandpaPalletInstance = BridgeGrandpaWococoInstance; type ParasPalletName = WococoBridgeParachainPalletName; type TrackedParachains = Everything; type HeadsToKeep = ParachainHeadsToKeep; + type MaxParaHeadSize = frame_support::traits::ConstU32<{bp_wococo::MAX_NESTED_PARACHAIN_HEAD_SIZE}>; } /// Add parachain bridge pallet to track Rococo bridge hub parachain pub type BridgeParachainRococoInstance = pallet_bridge_parachains::Instance2; impl pallet_bridge_parachains::Config for Runtime { - type Event = Event; - // TODO:check-parameter - type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight; type BridgesGrandpaPalletInstance = BridgeGrandpaRococoInstance; type ParasPalletName = RococoBridgeParachainPalletName; type TrackedParachains = Everything; type HeadsToKeep = ParachainHeadsToKeep; + type MaxParaHeadSize = frame_support::traits::ConstU32<{bp_rococo::MAX_NESTED_PARACHAIN_HEAD_SIZE}>; } /// Add XCM messages support for BrigdeHubRococo to support Rococo->Wococo XCM messages pub type WithBridgeHubWococoMessagesInstance = pallet_bridge_messages::Instance1; impl pallet_bridge_messages::Config for Runtime { - type Event = Event; - // TODO:check-parameter - copy of MillauWeigth + refactor - type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type BridgedChainId = bridge_hub_rococo_config::BridgeHubWococoChainId; type Parameter = (); type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; @@ -593,9 +578,8 @@ impl pallet_bridge_messages::Config for Run /// Add XCM messages support for BrigdeHubWococo to support Wococo->Rococo XCM messages pub type WithBridgeHubRococoMessagesInstance = pallet_bridge_messages::Instance2; impl pallet_bridge_messages::Config for Runtime { - type Event = Event; - // TODO:check-parameter - copy of MillauWeigth + refactor - type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type BridgedChainId = bridge_hub_wococo_config::BridgeHubRococoChainId; type Parameter = (); type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; @@ -692,6 +676,10 @@ mod benches { [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_xcmp_queue, XcmpQueue] + // XCM + // NOTE: Make sure you point to the individual modules below. + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] ); } @@ -836,7 +824,7 @@ impl_runtime_apis! { fn estimate_message_delivery_and_dispatch_fee( _lane_id: bp_messages::LaneId, payload: XcmAsPlainPayload, - _conversion_rate: Option, + conversion_rate: Option, ) -> Option { None } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs index 4db90f0c020..4eaa2cba639 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs @@ -23,7 +23,7 @@ pub mod constants { parameter_types! { /// Importing a block with 0 Extrinsics. - pub const BlockExecutionWeight: Weight = 5_000_000 * constants::WEIGHT_PER_NANOS; + pub const BlockExecutionWeight: Weight = constants::WEIGHT_PER_NANOS.saturating_mul(5_000_000); } #[cfg(test)] diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs new file mode 100644 index 00000000000..998f4f660ea --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,61 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_xcmp_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-08-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm6`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemint-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=statemint-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=cumulus_pallet_xcmp_queue +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./parachains/runtimes/assets/statemint/src/weights + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_xcmp_queue`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_xcmp_queue::WeightInfo for WeightInfo { + // Storage: XcmpQueue QueueConfig (r:1 w:1) + fn set_config_with_u32() -> Weight { + Weight::from_ref_time(5_192_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: XcmpQueue QueueConfig (r:1 w:1) + fn set_config_with_weight() -> Weight { + Weight::from_ref_time(5_363_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs index 158ba99c6a4..834374b6fad 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs @@ -23,7 +23,7 @@ pub mod constants { parameter_types! { /// Executing a NO-OP `System::remarks` Extrinsic. - pub const ExtrinsicBaseWeight: Weight = 125_000 * constants::WEIGHT_PER_NANOS; + pub const ExtrinsicBaseWeight: Weight = constants::WEIGHT_PER_NANOS.saturating_mul(125_000); } #[cfg(test)] diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs index ed0b4dbcd47..504a4280fcd 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -18,9 +18,12 @@ //! Expose the auto generated weight files. pub mod block_weights; +pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod paritydb_weights; +pub mod pallet_balances; pub mod rocksdb_weights; +pub mod xcm; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_balances.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_balances.rs new file mode 100644 index 00000000000..465247219dd --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_balances.rs @@ -0,0 +1,91 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! Autogenerated weights for `pallet_balances` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-21, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm6`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemint-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=statemint-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_balances +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./parachains/runtimes/assets/statemint/src/weights/pallet_balances.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight}}; +use sp_std::marker::PhantomData; + +/// Weight functions for `pallet_balances`. +pub struct WeightInfo(PhantomData); +impl pallet_balances::WeightInfo for WeightInfo { + // Storage: System Account (r:1 w:1) + fn transfer() -> Weight { + Weight::from_ref_time(48_009_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: System Account (r:1 w:1) + fn transfer_keep_alive() -> Weight { + Weight::from_ref_time(35_939_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: System Account (r:1 w:1) + fn set_balance_creating() -> Weight { + Weight::from_ref_time(26_600_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: System Account (r:1 w:1) + fn set_balance_killing() -> Weight { + Weight::from_ref_time(30_092_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: System Account (r:2 w:2) + fn force_transfer() -> Weight { + Weight::from_ref_time(47_435_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: System Account (r:1 w:1) + fn transfer_all() -> Weight { + Weight::from_ref_time(41_179_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: System Account (r:1 w:1) + fn force_unreserve() -> Weight { + Weight::from_ref_time(22_413_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs index 843823c1bf3..8083ccb4001 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs @@ -25,8 +25,8 @@ pub mod constants { /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights /// are available for brave runtime engineers who may want to try this out as default. pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 8_000 * constants::WEIGHT_PER_NANOS, - write: 50_000 * constants::WEIGHT_PER_NANOS, + read: 8_000 * constants::WEIGHT_PER_NANOS.ref_time(), + write: 50_000 * constants::WEIGHT_PER_NANOS.ref_time(), }; } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs index 05e06b0eabe..1db87f143f3 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs @@ -25,8 +25,8 @@ pub mod constants { /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout /// the runtime. pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 25_000 * constants::WEIGHT_PER_NANOS, - write: 100_000 * constants::WEIGHT_PER_NANOS, + read: 25_000 * constants::WEIGHT_PER_NANOS.ref_time(), + write: 100_000 * constants::WEIGHT_PER_NANOS.ref_time(), }; } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs new file mode 100644 index 00000000000..6706efe1757 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs @@ -0,0 +1,247 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +mod pallet_xcm_benchmarks_fungible; +mod pallet_xcm_benchmarks_generic; + +use crate::Runtime; +use frame_support::weights::Weight; +use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; +use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_std::{cmp, prelude::*}; +use xcm::{ + latest::{prelude::*, Weight as XCMWeight}, + DoubleEncoded, +}; + +trait WeighMultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> XCMWeight; +} + +const MAX_ASSETS: u32 = 100; + +impl WeighMultiAssets for MultiAssetFilter { + fn weigh_multi_assets(&self, weight: Weight) -> XCMWeight { + let weight = match self { + Self::Definite(assets) => + weight.saturating_mul(assets.inner().into_iter().count() as u64), + Self::Wild(_) => weight.saturating_mul(MAX_ASSETS as u64), + }; + weight.ref_time() + } +} + +impl WeighMultiAssets for MultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> XCMWeight { + weight.saturating_mul(self.inner().into_iter().count() as u64).ref_time() + } +} + +pub struct BridgeHubXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for BridgeHubXcmWeight { + fn withdraw_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) + } + // Currently there is no trusted reserve + fn reserve_asset_deposited(_assets: &MultiAssets) -> XCMWeight { + u64::MAX + } + fn receive_teleported_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + } + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &u64, + _querier: &Option, + ) -> XCMWeight { + XcmGeneric::::query_response().ref_time() + } + fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> XCMWeight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + } + fn transfer_reserve_asset( + assets: &MultiAssets, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> XCMWeight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + } + fn transact( + _origin_type: &OriginKind, + _require_weight_at_most: &u64, + _call: &DoubleEncoded, + ) -> XCMWeight { + XcmGeneric::::transact().ref_time() + } + fn hrmp_new_channel_open_request( + _sender: &u32, + _max_message_size: &u32, + _max_capacity: &u32, + ) -> XCMWeight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX.ref_time() + } + fn hrmp_channel_accepted(_recipient: &u32) -> XCMWeight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX.ref_time() + } + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> XCMWeight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX.ref_time() + } + fn clear_origin() -> XCMWeight { + XcmGeneric::::clear_origin().ref_time() + } + fn descend_origin(_who: &InteriorMultiLocation) -> XCMWeight { + XcmGeneric::::descend_origin().ref_time() + } + fn report_error(_query_response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::report_error().ref_time() + } + + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> XCMWeight { + // Hardcoded till the XCM pallet is fixed + let hardcoded_weight = Weight::from_ref_time(1_000_000_000 as u64).ref_time(); + let weight = assets.weigh_multi_assets(XcmFungibleWeight::::deposit_asset()); + cmp::min(hardcoded_weight, weight) + } + fn deposit_reserve_asset( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> XCMWeight { + assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + } + fn exchange_asset( + _give: &MultiAssetFilter, + _receive: &MultiAssets, + _maximal: &bool, + ) -> XCMWeight { + Weight::MAX.ref_time() + } + fn initiate_reserve_withdraw( + assets: &MultiAssetFilter, + _reserve: &MultiLocation, + _xcm: &Xcm<()>, + ) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::initiate_reserve_withdraw()) + } + fn initiate_teleport( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> XCMWeight { + // Hardcoded till the XCM pallet is fixed + let hardcoded_weight = Weight::from_ref_time(200_000_000 as u64).ref_time(); + let weight = assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()); + cmp::min(hardcoded_weight, weight) + } + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> XCMWeight { + XcmGeneric::::report_holding().ref_time() + } + fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> XCMWeight { + XcmGeneric::::buy_execution().ref_time() + } + fn refund_surplus() -> XCMWeight { + XcmGeneric::::refund_surplus().ref_time() + } + fn set_error_handler(_xcm: &Xcm) -> XCMWeight { + XcmGeneric::::set_error_handler().ref_time() + } + fn set_appendix(_xcm: &Xcm) -> XCMWeight { + XcmGeneric::::set_appendix().ref_time() + } + fn clear_error() -> XCMWeight { + XcmGeneric::::clear_error().ref_time() + } + fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> XCMWeight { + XcmGeneric::::claim_asset().ref_time() + } + fn trap(_code: &u64) -> XCMWeight { + XcmGeneric::::trap().ref_time() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &u64) -> XCMWeight { + XcmGeneric::::subscribe_version().ref_time() + } + fn unsubscribe_version() -> XCMWeight { + XcmGeneric::::unsubscribe_version().ref_time() + } + fn burn_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> XCMWeight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> XCMWeight { + XcmGeneric::::expect_origin().ref_time() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> XCMWeight { + XcmGeneric::::expect_error().ref_time() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::query_pallet().ref_time() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> XCMWeight { + XcmGeneric::::expect_pallet().ref_time() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> XCMWeight { + XcmGeneric::::report_transact_status().ref_time() + } + fn clear_transact_status() -> XCMWeight { + XcmGeneric::::clear_transact_status().ref_time() + } + fn universal_origin(_: &Junction) -> XCMWeight { + Weight::MAX.ref_time() + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> XCMWeight { + Weight::MAX.ref_time() + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { + Weight::MAX.ref_time() + } + fn set_fees_mode(_: &bool) -> XCMWeight { + XcmGeneric::::set_fees_mode().ref_time() + } + fn set_topic(_topic: &[u8; 32]) -> XCMWeight { + XcmGeneric::::set_topic().ref_time() + } + fn clear_topic() -> XCMWeight { + XcmGeneric::::clear_topic().ref_time() + } + fn alias_origin(_: &MultiLocation) -> XCMWeight { + // XCM Executor does not currently support alias origin operations + Weight::MAX.ref_time() + } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> XCMWeight { + XcmGeneric::::unpaid_execution().ref_time() + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs new file mode 100644 index 00000000000..900472b6c9d --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -0,0 +1,107 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + + +//! Autogenerated weights for `pallet_xcm_benchmarks::fungible` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-21, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm6`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemint-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --template=./templates/xcm-bench-template.hbs +// --chain=statemint-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm_benchmarks::fungible +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./parachains/runtimes/assets/statemint/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::fungible`. +pub struct WeightInfo(PhantomData); +impl WeightInfo { + // Storage: System Account (r:1 w:1) + pub(crate) fn withdraw_asset() -> Weight { + Weight::from_ref_time(33_878_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: System Account (r:2 w:2) + pub(crate) fn transfer_asset() -> Weight { + Weight::from_ref_time(39_130_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: System Account (r:2 w:2) + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn transfer_reserve_asset() -> Weight { + Weight::from_ref_time(54_404_000 as u64) + .saturating_add(T::DbWeight::get().reads(8 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + pub(crate) fn receive_teleported_asset() -> Weight { + Weight::from_ref_time(6_586_000 as u64) + } + // Storage: System Account (r:1 w:1) + pub(crate) fn deposit_asset() -> Weight { + Weight::from_ref_time(34_055_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: System Account (r:1 w:1) + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn deposit_reserve_asset() -> Weight { + Weight::from_ref_time(50_905_000 as u64) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn initiate_teleport() -> Weight { + Weight::from_ref_time(26_715_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs new file mode 100644 index 00000000000..79066a2b9e5 --- /dev/null +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -0,0 +1,189 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + + +//! Autogenerated weights for `pallet_xcm_benchmarks::generic` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemint-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --template=./templates/xcm-bench-template.hbs +// --chain=statemint-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm_benchmarks::generic +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --template=./templates/xcm-bench-template.hbs +// --output=./parachains/runtimes/assets/statemint/src/weights/xcm/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::generic`. +pub struct WeightInfo(PhantomData); +impl WeightInfo { + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn report_holding() -> Weight { + Weight::from_ref_time(1_305_689_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn buy_execution() -> Weight { + Weight::from_ref_time(8_843_000 as u64) + } + // Storage: PolkadotXcm Queries (r:1 w:0) + pub(crate) fn query_response() -> Weight { + Weight::from_ref_time(19_216_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + pub(crate) fn transact() -> Weight { + Weight::from_ref_time(22_708_000 as u64) + } + pub(crate) fn refund_surplus() -> Weight { + Weight::from_ref_time(9_040_000 as u64) + } + pub(crate) fn set_error_handler() -> Weight { + Weight::from_ref_time(6_222_000 as u64) + } + pub(crate) fn set_appendix() -> Weight { + Weight::from_ref_time(6_411_000 as u64) + } + pub(crate) fn clear_error() -> Weight { + Weight::from_ref_time(6_222_000 as u64) + } + pub(crate) fn descend_origin() -> Weight { + Weight::from_ref_time(7_112_000 as u64) + } + pub(crate) fn clear_origin() -> Weight { + Weight::from_ref_time(6_340_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn report_error() -> Weight { + Weight::from_ref_time(22_943_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: PolkadotXcm AssetTraps (r:1 w:1) + pub(crate) fn claim_asset() -> Weight { + Weight::from_ref_time(13_178_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + pub(crate) fn trap() -> Weight { + Weight::from_ref_time(6_333_000 as u64) + } + // Storage: PolkadotXcm VersionNotifyTargets (r:1 w:1) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn subscribe_version() -> Weight { + Weight::from_ref_time(31_798_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: PolkadotXcm VersionNotifyTargets (r:0 w:1) + pub(crate) fn unsubscribe_version() -> Weight { + Weight::from_ref_time(9_728_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn initiate_reserve_withdraw() -> Weight { + Weight::from_ref_time(1_583_652_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn burn_asset() -> Weight { + Weight::from_ref_time(497_448_000 as u64) + } + pub(crate) fn expect_asset() -> Weight { + Weight::from_ref_time(38_383_000 as u64) + } + pub(crate) fn expect_origin() -> Weight { + Weight::from_ref_time(6_308_000 as u64) + } + pub(crate) fn expect_error() -> Weight { + Weight::from_ref_time(6_327_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn query_pallet() -> Weight { + Weight::from_ref_time(26_011_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn expect_pallet() -> Weight { + Weight::from_ref_time(8_008_000 as u64) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Storage: ParachainSystem HostConfiguration (r:1 w:0) + // Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) + pub(crate) fn report_transact_status() -> Weight { + Weight::from_ref_time(22_963_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + pub(crate) fn clear_transact_status() -> Weight { + Weight::from_ref_time(6_378_000 as u64) + } + pub(crate) fn set_topic() -> Weight { + Weight::from_ref_time(6_313_000 as u64) + } + pub(crate) fn clear_topic() -> Weight { + Weight::from_ref_time(6_324_000 as u64) + } + pub(crate) fn set_fees_mode() -> Weight { + Weight::from_ref_time(6_336_000 as u64) + } + pub(crate) fn unpaid_execution() -> Weight { Weight::from_ref_time(3_111_000 as u64) } +} diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index c6dead42d5a..20e72bb3040 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -15,7 +15,7 @@ // along with Cumulus. If not, see . use super::{ - AccountId, Balance, Balances, Call, Event, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, + AccountId, Balance, Balances, RuntimeCall, RuntimeEvent, RuntimeOrigin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, XcmpQueue, }; use crate::{ @@ -25,25 +25,19 @@ use crate::{ use frame_support::{ match_types, parameter_types, traits::{Everything, Nothing}, - weights::{IdentityFee, Weight}, + weights::IdentityFee, }; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use xcm::latest::prelude::*; -use xcm_builder::{ - AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, -}; +use xcm_builder::{AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, WeightInfoBounds}; use xcm_executor::{traits::ExportXcm, XcmExecutor}; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); // TODO: hack: hardcoded Polkadot? pub const RelayNetwork: NetworkId = NetworkId::Rococo; - pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); } @@ -81,25 +75,23 @@ pub type XcmOriginToTransactDispatchOrigin = ( // Sovereign account converter; this attempts to derive an `AccountId` from the origin location // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for // foreign chains who want to have a local sovereign account on this chain which they control. - SovereignSignedViaLocation, + SovereignSignedViaLocation, // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when // recognized. - RelayChainAsNative, + RelayChainAsNative, // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when // recognized. - SiblingParachainAsNative, + SiblingParachainAsNative, // Native signed account converter; this just converts an `AccountId32` origin into a normal // `Origin::Signed` origin of the same 32-byte value. - SignedAccountId32AsNative, + SignedAccountId32AsNative, // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. - XcmPassthrough, + XcmPassthrough, ); parameter_types! { - // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. - pub UnitWeightCost: Weight = 1_000_000_000; pub const MaxInstructions: u32 = 100; - pub MaxAssetsIntoHolding: u32 = 64; + pub const MaxAssetsIntoHolding: u32 = 64; } match_types! { @@ -200,12 +192,15 @@ pub type Barrier = ( // ^^^ Parent & its unit plurality gets free execution ); -/// XCM weigher type. -pub type XcmWeigher = FixedWeightBounds; +type XcmWeigher = WeightInfoBounds< + crate::weights::xcm::BridgeHubXcmWeight, + RuntimeCall, + MaxInstructions, +>; pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { - type Call = Call; + type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; @@ -226,11 +221,11 @@ impl xcm_executor::Config for XcmConfig { type FeeManager = (); type MessageExporter = BridgeHubRococoOrBridgeHubWococoSwitchExporter; type UniversalAliases = Nothing; - type CallDispatcher = Call; + type CallDispatcher = RuntimeCall; } /// No local origins on this chain are allowed to dispatch XCM sends/executions. -pub type LocalOriginToLocation = SignedToAccountId32; +pub type LocalOriginToLocation = SignedToAccountId32; /// The means for routing XCM messages which are not for local execution into the right message /// queues. @@ -242,17 +237,17 @@ pub type XcmRouter = ( ); impl pallet_xcm::Config for Runtime { - type Event = Event; - type SendXcmOrigin = EnsureXcmOrigin; + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = XcmRouter; - type ExecuteXcmOrigin = EnsureXcmOrigin; + type ExecuteXcmOrigin = EnsureXcmOrigin; type XcmExecuteFilter = Everything; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = XcmWeigher; - type Origin = Origin; - type Call = Call; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; type Currency = Balances; @@ -264,7 +259,7 @@ impl pallet_xcm::Config for Runtime { } impl cumulus_pallet_xcm::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type XcmExecutor = XcmExecutor; } @@ -276,15 +271,16 @@ impl ExportXcm for BridgeHubRococoOrBridgeHubWococoSwitchExporter { fn validate( network: NetworkId, channel: u32, + universal_source: &mut Option, destination: &mut Option, message: &mut Option>, ) -> SendResult { match network { Rococo => - ToBridgeHubRococoHaulBlobExporter::validate(network, channel, destination, message) + ToBridgeHubRococoHaulBlobExporter::validate(network, channel, universal_source, destination, message) .map(|result| ((Rococo, result.0), result.1)), Wococo => - ToBridgeHubWococoHaulBlobExporter::validate(network, channel, destination, message) + ToBridgeHubWococoHaulBlobExporter::validate(network, channel, universal_source, destination, message) .map(|result| ((Wococo, result.0), result.1)), _ => unimplemented!("Unsupported network: {:?}", network), } diff --git a/polkadot-parachain/src/command.rs b/polkadot-parachain/src/command.rs index 2845d2b2ec7..575db58224c 100644 --- a/polkadot-parachain/src/command.rs +++ b/polkadot-parachain/src/command.rs @@ -37,8 +37,6 @@ use sp_core::hexdisplay::HexDisplay; use sp_runtime::traits::{AccountIdConversion, Block as BlockT}; use std::{net::SocketAddr, path::PathBuf}; -use crate::chain_spec::bridge_hubs::BridgeHubRuntimeType; - /// Helper enum that is used for better distinction of different parachain/runtime configuration /// (it is based/calculated on ChainSpec's ID attribute) #[derive(Debug, PartialEq, Default)] From bc1f1d44d39cd14495362baaaa4e92c6c517ad0c Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 14 Nov 2022 15:58:28 +0100 Subject: [PATCH 85/91] Fixes - WeigthToFee + fmt --- .../src/bridge_common_config.rs | 22 +++--- .../src/bridge_hub_rococo_config.rs | 20 +++--- .../src/bridge_hub_wococo_config.rs | 12 +++- .../bridge-hub-rococo/src/constants.rs | 8 +-- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 70 +++++++------------ .../bridge-hub-rococo/src/weights/mod.rs | 2 +- .../bridge-hub-rococo/src/xcm_config.rs | 34 ++++++--- 7 files changed, 84 insertions(+), 84 deletions(-) diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 85414d09bef..94c0d1660be 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -42,11 +42,8 @@ pub struct XcmBlobMessageDispatch, } -impl< - SourceBridgeHubChain: Chain, - TargetBridgeHubChain: Chain, - BlobDispatcher: DispatchBlob, - > MessageDispatch, BalanceOf> +impl + MessageDispatch, BalanceOf> for XcmBlobMessageDispatch { type DispatchPayload = XcmAsPlainPayload; @@ -79,13 +76,16 @@ impl< let dispatch_result = match BlobDispatcher::dispatch_blob(payload) { Ok(_) => true, Err(e) => { - let e= match e { + let e = match e { DispatchBlobError::Unbridgable => "DispatchBlobError::Unbridgable", DispatchBlobError::InvalidEncoding => "DispatchBlobError::InvalidEncoding", - DispatchBlobError::UnsupportedLocationVersion => "DispatchBlobError::UnsupportedLocationVersion", - DispatchBlobError::UnsupportedXcmVersion => "DispatchBlobError::UnsupportedXcmVersion", + DispatchBlobError::UnsupportedLocationVersion => + "DispatchBlobError::UnsupportedLocationVersion", + DispatchBlobError::UnsupportedXcmVersion => + "DispatchBlobError::UnsupportedXcmVersion", DispatchBlobError::RoutingError => "DispatchBlobError::RoutingError", - DispatchBlobError::NonUniversalDestination => "DispatchBlobError::NonUniversalDestination", + DispatchBlobError::NonUniversalDestination => + "DispatchBlobError::NonUniversalDestination", DispatchBlobError::WrongGlobal => "DispatchBlobError::WrongGlobal", }; log::error!( @@ -142,9 +142,7 @@ impl HaulBlob for XcmBlobHaulerAdapter { let hash = (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256); hash }) - .map_err(|e| { - e - }); + .map_err(|e| e); log::info!(target: "runtime::bridge-hub", "haul_blob result: {:?} on lane: {:?}", result, lane); result.expect("failed to process: TODO:check-parameter - wait for origin/gav-xcm-v3, there is a comment about handliing errors for HaulBlob"); } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs index 475adf13100..cc3aef6aa63 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::{ParachainInfo, Runtime, WithBridgeHubWococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmRouter}; +use crate::{ + ParachainInfo, Runtime, WithBridgeHubWococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, + XcmBlobHaulerAdapter, XcmRouter, +}; use bp_messages::{ source_chain::TargetHeaderChain, target_chain::{ProvedMessages, SourceHeaderChain}, @@ -54,8 +57,11 @@ pub type OnBridgeHubRococoBlobDispatcher = BridgeBlobDispatcher; /// Export XCM messages to be relayed to the otherside -pub type ToBridgeHubWococoHaulBlobExporter = - HaulBlobExporter, WococoGlobalConsensusNetwork, ()>; +pub type ToBridgeHubWococoHaulBlobExporter = HaulBlobExporter< + XcmBlobHaulerAdapter, + WococoGlobalConsensusNetwork, + (), +>; pub struct ToBridgeHubWococoXcmBlobHauler; pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO: LaneId = [0, 0, 0, 2]; impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { @@ -186,9 +192,7 @@ impl messages::BridgedChainWithMessages for BridgeHubWococo { } } - fn transaction_payment( - transaction: MessageTransaction, - ) -> messages::BalanceOf { + fn transaction_payment(transaction: MessageTransaction) -> messages::BalanceOf { log::info!( "[BridgeHubWococo::BridgedChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", transaction @@ -232,9 +236,7 @@ impl ThisChainWithMessages for BridgeHubRococo { MessageNonce::MAX / 2 } - fn transaction_payment( - transaction: MessageTransaction, - ) -> messages::BalanceOf { + fn transaction_payment(transaction: MessageTransaction) -> messages::BalanceOf { log::info!( "[BridgeHubRococo::ThisChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", transaction diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs index 99dd24d6a29..a4039e1b944 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::{ParachainInfo, Runtime, WithBridgeHubRococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmRouter}; +use crate::{ + ParachainInfo, Runtime, WithBridgeHubRococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, + XcmBlobHaulerAdapter, XcmRouter, +}; use bp_messages::{ source_chain::TargetHeaderChain, target_chain::{ProvedMessages, SourceHeaderChain}, @@ -54,8 +57,11 @@ pub type OnBridgeHubWococoBlobDispatcher = BridgeBlobDispatcher; /// Export XCM messages to be relayed to the otherside -pub type ToBridgeHubRococoHaulBlobExporter = - HaulBlobExporter, RococoGlobalConsensusNetwork, ()>; +pub type ToBridgeHubRococoHaulBlobExporter = HaulBlobExporter< + XcmBlobHaulerAdapter, + RococoGlobalConsensusNetwork, + (), +>; pub struct ToBridgeHubRococoXcmBlobHauler; pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO: LaneId = [0, 0, 0, 1]; impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/constants.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/constants.rs index 4352e3ef554..641ae6dfe43 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/constants.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/constants.rs @@ -39,8 +39,8 @@ pub mod fee { /// node's balance type. /// /// This should typically create a mapping between the following ranges: - /// - [0, MAXIMUM_BLOCK_WEIGHT] - /// - [Balance::min, Balance::max] + /// - `[0, MAXIMUM_BLOCK_WEIGHT]` + /// - `[Balance::min, Balance::max]` /// /// Yet, it can be used for any other sort of change to weight-fee. Some examples being: /// - Setting it to `0` will essentially disable the weight fee. @@ -49,8 +49,8 @@ pub mod fee { impl WeightToFeePolynomial for WeightToFee { type Balance = Balance; fn polynomial() -> WeightToFeeCoefficients { - // in Polkadot, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: - // in Statemint, we map to 1/10 of that, or 1/100 CENT + // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: + // in BridgeHub, we map to 1/10 of that, or 1/100 CENT let p = super::currency::CENTS; let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); smallvec![WeightToFeeCoefficient { diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index d154f9721d7..10a31c9dec6 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -29,11 +29,10 @@ pub mod constants; mod weights; pub mod xcm_config; -use codec::Decode; use bridge_common_config::*; +use codec::Decode; use constants::currency::*; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use smallvec::smallvec; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -49,13 +48,11 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, dispatch::DispatchClass, + parameter_types, traits::Everything, - weights::{ - ConstantMultiplier, Weight, - WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, - }, + weights::{ConstantMultiplier, Weight}, PalletId, }; use frame_system::{ @@ -77,10 +74,12 @@ use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use crate::{ bridge_hub_rococo_config::OnBridgeHubRococoBlobDispatcher, - bridge_hub_wococo_config::OnBridgeHubWococoBlobDispatcher, + bridge_hub_wococo_config::OnBridgeHubWococoBlobDispatcher, constants::fee::WeightToFee, xcm_config::XcmRouter, }; -use parachains_common::{AccountId, Signature, AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, MAXIMUM_BLOCK_WEIGHT}; +use parachains_common::{ + AccountId, Signature, AVERAGE_ON_INITIALIZE_RATIO, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, +}; use xcm::latest::prelude::BodyId; use xcm_executor::XcmExecutor; @@ -126,7 +125,8 @@ pub type SignedExtra = ( ); /// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; @@ -140,33 +140,6 @@ pub type Executive = frame_executive::Executive< AllPalletsWithSystem, >; -/// Handles converting a weight scalar to a fee value, based on the scale and granularity of the -/// node's balance type. -/// -/// This should typically create a mapping between the following ranges: -/// - `[0, MAXIMUM_BLOCK_WEIGHT]` -/// - `[Balance::min, Balance::max]` -/// -/// Yet, it can be used for any other sort of change to weight-fee. Some examples being: -/// - Setting it to `0` will essentially disable the weight fee. -/// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. -pub struct WeightToFee; -impl WeightToFeePolynomial for WeightToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1 MILLIUNIT: - // in our template, we map to 1/10 of that, or 1/10 MILLIUNIT - let p = MILLIUNIT / 10; - let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } -} - /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know /// the specifics of the runtime. They can then be made to be agnostic over specific formats /// of data like extrinsics, allowing for them to continue syncing the network through upgrades @@ -226,7 +199,6 @@ pub const UNIT: Balance = 1_000_000_000_000; pub const MILLIUNIT: Balance = 1_000_000_000; pub const MICROUNIT: Balance = 1_000_000; - /// The version information used to identify this runtime when compiled natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { @@ -492,8 +464,9 @@ impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_wococo::Wococo; type MaxRequests = MaxRequests; type HeadersToKeep = HeadersToKeep; - type MaxBridgedAuthorities = frame_support::traits::ConstU32<{bp_wococo::MAX_AUTHORITIES_COUNT}>; - type MaxBridgedHeaderSize = frame_support::traits::ConstU32<{bp_wococo::MAX_HEADER_SIZE}>; + type MaxBridgedAuthorities = + frame_support::traits::ConstU32<{ bp_wococo::MAX_AUTHORITIES_COUNT }>; + type MaxBridgedHeaderSize = frame_support::traits::ConstU32<{ bp_wococo::MAX_HEADER_SIZE }>; type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; } @@ -503,8 +476,9 @@ impl pallet_bridge_grandpa::Config for Runtime { type BridgedChain = bp_rococo::Rococo; type MaxRequests = MaxRequests; type HeadersToKeep = HeadersToKeep; - type MaxBridgedAuthorities = frame_support::traits::ConstU32<{bp_rococo::MAX_AUTHORITIES_COUNT}>; - type MaxBridgedHeaderSize = frame_support::traits::ConstU32<{bp_rococo::MAX_HEADER_SIZE}>; + type MaxBridgedAuthorities = + frame_support::traits::ConstU32<{ bp_rococo::MAX_AUTHORITIES_COUNT }>; + type MaxBridgedHeaderSize = frame_support::traits::ConstU32<{ bp_rococo::MAX_HEADER_SIZE }>; type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; } @@ -525,7 +499,8 @@ impl pallet_bridge_parachains::Config for Runtime type ParasPalletName = WococoBridgeParachainPalletName; type TrackedParachains = Everything; type HeadsToKeep = ParachainHeadsToKeep; - type MaxParaHeadSize = frame_support::traits::ConstU32<{bp_wococo::MAX_NESTED_PARACHAIN_HEAD_SIZE}>; + type MaxParaHeadSize = + frame_support::traits::ConstU32<{ bp_wococo::MAX_NESTED_PARACHAIN_HEAD_SIZE }>; } /// Add parachain bridge pallet to track Rococo bridge hub parachain @@ -537,7 +512,8 @@ impl pallet_bridge_parachains::Config for Runtime type ParasPalletName = RococoBridgeParachainPalletName; type TrackedParachains = Everything; type HeadsToKeep = ParachainHeadsToKeep; - type MaxParaHeadSize = frame_support::traits::ConstU32<{bp_rococo::MAX_NESTED_PARACHAIN_HEAD_SIZE}>; + type MaxParaHeadSize = + frame_support::traits::ConstU32<{ bp_rococo::MAX_NESTED_PARACHAIN_HEAD_SIZE }>; } /// Add XCM messages support for BrigdeHubRococo to support Rococo->Wococo XCM messages @@ -553,7 +529,8 @@ impl pallet_bridge_messages::Config for Run type MaxUnconfirmedMessagesAtInboundLane = bridge_hub_rococo_config::MaxUnconfirmedMessagesAtInboundLane; - type MaximalOutboundPayloadSize = bridge_hub_rococo_config::ToBridgeHubWococoMaximalOutboundPayloadSize; + type MaximalOutboundPayloadSize = + bridge_hub_rococo_config::ToBridgeHubWococoMaximalOutboundPayloadSize; type OutboundPayload = XcmAsPlainPayload; type OutboundMessageFee = Balance; @@ -588,7 +565,8 @@ impl pallet_bridge_messages::Config for Run type MaxUnconfirmedMessagesAtInboundLane = bridge_hub_wococo_config::MaxUnconfirmedMessagesAtInboundLane; - type MaximalOutboundPayloadSize = bridge_hub_wococo_config::ToBridgeHubRococoMaximalOutboundPayloadSize; + type MaximalOutboundPayloadSize = + bridge_hub_wococo_config::ToBridgeHubRococoMaximalOutboundPayloadSize; type OutboundPayload = XcmAsPlainPayload; type OutboundMessageFee = Balance; diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs index 504a4280fcd..8006fb43e5d 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -20,8 +20,8 @@ pub mod block_weights; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; -pub mod paritydb_weights; pub mod pallet_balances; +pub mod paritydb_weights; pub mod rocksdb_weights; pub mod xcm; diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index 20e72bb3040..c825e780716 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -15,8 +15,8 @@ // along with Cumulus. If not, see . use super::{ - AccountId, Balance, Balances, RuntimeCall, RuntimeEvent, RuntimeOrigin, ParachainInfo, ParachainSystem, PolkadotXcm, - Runtime, XcmpQueue, + AccountId, Balance, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, + RuntimeCall, RuntimeEvent, RuntimeOrigin, XcmpQueue, }; use crate::{ bridge_hub_rococo_config::ToBridgeHubWococoHaulBlobExporter, @@ -30,7 +30,13 @@ use frame_support::{ use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use xcm::latest::prelude::*; -use xcm_builder::{AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, WeightInfoBounds}; +use xcm_builder::{ + AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, + EnsureXcmOrigin, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + WeightInfoBounds, +}; use xcm_executor::{traits::ExportXcm, XcmExecutor}; parameter_types! { @@ -276,12 +282,22 @@ impl ExportXcm for BridgeHubRococoOrBridgeHubWococoSwitchExporter { message: &mut Option>, ) -> SendResult { match network { - Rococo => - ToBridgeHubRococoHaulBlobExporter::validate(network, channel, universal_source, destination, message) - .map(|result| ((Rococo, result.0), result.1)), - Wococo => - ToBridgeHubWococoHaulBlobExporter::validate(network, channel, universal_source, destination, message) - .map(|result| ((Wococo, result.0), result.1)), + Rococo => ToBridgeHubRococoHaulBlobExporter::validate( + network, + channel, + universal_source, + destination, + message, + ) + .map(|result| ((Rococo, result.0), result.1)), + Wococo => ToBridgeHubWococoHaulBlobExporter::validate( + network, + channel, + universal_source, + destination, + message, + ) + .map(|result| ((Wococo, result.0), result.1)), _ => unimplemented!("Unsupported network: {:?}", network), } } From c6756cd37fecf0c9379c0f96a9ca70ed4f4971e9 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 16 Nov 2022 23:16:48 +0100 Subject: [PATCH 86/91] BridgeHub fixes --- Cargo.lock | 34 +++++------- Cargo.toml | 6 ++- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 - .../src/bridge_hub_rococo_config.rs | 53 +++++++------------ .../src/bridge_hub_wococo_config.rs | 53 +++++++------------ 5 files changed, 55 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 127f1a1c1cd..f0a310083eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,6 +721,8 @@ dependencies = [ "bp-polkadot-core", "bp-runtime", "frame-support", + "polkadot-runtime-constants", + "smallvec", "sp-api", "sp-std", ] @@ -754,6 +756,7 @@ dependencies = [ "sp-finality-grandpa", "sp-runtime", "sp-std", + "sp-trie", ] [[package]] @@ -936,7 +939,6 @@ dependencies = [ "bp-bridge-hub-rococo", "bp-bridge-hub-wococo", "bp-messages", - "bp-polkadot-core", "bp-rococo", "bp-runtime", "bp-wococo", @@ -1006,6 +1008,7 @@ dependencies = [ name = "bridge-runtime-common" version = "0.1.0" dependencies = [ + "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot-core", @@ -4383,7 +4386,7 @@ dependencies = [ "pallet-conviction-voting", "pallet-democracy", "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=master)", + "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-gilt", @@ -6182,7 +6185,7 @@ dependencies = [ "frame-support", "frame-system", "log", - "pallet-election-provider-support-benchmarking 4.0.0-dev (git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges)", + "pallet-election-provider-support-benchmarking", "parity-scale-codec", "rand 0.7.3", "scale-info", @@ -6196,19 +6199,6 @@ dependencies = [ "strum", ] -[[package]] -name = "pallet-election-provider-support-benchmarking" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" -dependencies = [ - "frame-benchmarking", - "frame-election-provider-support", - "frame-system", - "parity-scale-codec", - "sp-npos-elections", - "sp-runtime", -] - [[package]] name = "pallet-election-provider-support-benchmarking" version = "4.0.0-dev" @@ -6435,7 +6425,7 @@ dependencies = [ [[package]] name = "pallet-nomination-pools-benchmarking" version = "1.0.0" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -6482,7 +6472,7 @@ dependencies = [ [[package]] name = "pallet-offences-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -6639,7 +6629,7 @@ dependencies = [ [[package]] name = "pallet-session-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "frame-benchmarking", "frame-support", @@ -8416,7 +8406,7 @@ dependencies = [ "pallet-collective", "pallet-democracy", "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=master)", + "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-grandpa", @@ -12433,7 +12423,7 @@ dependencies = [ [[package]] name = "substrate-test-client" version = "2.0.1" -source = "git+https://github.com/paritytech/substrate?branch=master#87f3fdea8f227d33322c439d45a9e1796637e972" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" dependencies = [ "array-bytes", "async-trait", @@ -13655,7 +13645,7 @@ dependencies = [ "pallet-collective", "pallet-democracy", "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=master)", + "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-grandpa", diff --git a/Cargo.toml b/Cargo.toml index 2939fd905eb..925a43226d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "bridges/modules/relayers", "bridges/modules/shift-session-manager", "bridges/primitives/messages", - "bridges/primitives/polkadot-core", "bridges/primitives/runtime", "bridges/primitives/chain-bridge-hub-rococo", "bridges/primitives/chain-bridge-hub-wococo", @@ -248,6 +247,11 @@ substrate-prometheus-endpoint = { git = "https://github.com/paritytech//substrat substrate-state-trie-migration-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } substrate-wasm-builder = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } try-runtime-cli = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-test-client = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-election-provider-support-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-nomination-pools-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-offences-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-session-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } [patch."https://github.com/paritytech/polkadot"] kusama-runtime = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 3845258ed6e..25bdd1bcbe8 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -73,7 +73,6 @@ parachains-common = { path = "../../../../parachains/common", default-features = bp-bridge-hub-rococo = { path = "../../../../bridges/primitives/chain-bridge-hub-rococo", default-features = false } bp-bridge-hub-wococo = { path = "../../../../bridges/primitives/chain-bridge-hub-wococo", default-features = false } bp-messages = { path = "../../../../bridges/primitives/messages", default-features = false } -bp-polkadot-core = { path = "../../../../bridges/primitives/polkadot-core", default-features = false } bp-runtime = { path = "../../../../bridges/primitives/runtime", default-features = false } bp-rococo = { path = "../../../../bridges/primitives/chain-rococo", default-features = false } bp-wococo = { path = "../../../../bridges/primitives/chain-wococo", default-features = false } @@ -91,7 +90,6 @@ default = [ std = [ "bp-bridge-hub-rococo/std", "bp-bridge-hub-wococo/std", - "bp-polkadot-core/std", "bp-messages/std", "bp-runtime/std", "bp-rococo/std", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs index cc3aef6aa63..c242285c357 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -15,22 +15,21 @@ // along with Cumulus. If not, see . use crate::{ - ParachainInfo, Runtime, WithBridgeHubWococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, - XcmBlobHaulerAdapter, XcmRouter, + BridgeParachainWococoInstance, ParachainInfo, Runtime, WithBridgeHubWococoMessagesInstance, + XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmRouter, }; use bp_messages::{ source_chain::TargetHeaderChain, target_chain::{ProvedMessages, SourceHeaderChain}, InboundLaneData, LaneId, Message, MessageNonce, }; -use bp_polkadot_core::parachains::ParaId; -use bp_runtime::{Chain, ChainId}; +use bp_runtime::ChainId; use bridge_runtime_common::{ messages, messages::{ target::FromBridgedChainMessagesProof, BasicConfirmationTransactionEstimation, - BridgedChain, ChainWithMessages, MessageBridge, MessageTransaction, ThisChain, - ThisChainWithMessages, + BridgedChain, MessageBridge, MessageTransaction, ThisChain, ThisChainWithMessages, + UnderlyingChainProvider, }, }; use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; @@ -89,6 +88,11 @@ impl MessageBridge for WithBridgeHubWococoMessageBridge { bp_bridge_hub_rococo::WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME; type ThisChain = BridgeHubRococo; type BridgedChain = BridgeHubWococo; + type BridgedHeaderChain = pallet_bridge_parachains::ParachainHeaders< + Runtime, + BridgeParachainWococoInstance, + bp_bridge_hub_wococo::BridgeHubWococo, + >; fn bridged_balance_to_this_balance( bridged_balance: bridge_runtime_common::messages::BalanceOf>, @@ -112,12 +116,8 @@ pub type ToBridgeHubWococoMaximalOutboundPayloadSize = #[derive(RuntimeDebug, Clone, Copy)] pub struct BridgeHubWococo; -impl ChainWithMessages for BridgeHubWococo { - type Hash = bp_bridge_hub_wococo::Hash; - type AccountId = bp_bridge_hub_wococo::AccountId; - type Signer = bp_bridge_hub_wococo::AccountSigner; - type Signature = bp_bridge_hub_wococo::Signature; - type Balance = bp_bridge_hub_wococo::Balance; +impl UnderlyingChainProvider for BridgeHubWococo { + type Chain = bp_bridge_hub_wococo::BridgeHubWococo; } impl SourceHeaderChain for BridgeHubWococo { @@ -128,12 +128,10 @@ impl SourceHeaderChain for BridgeHubWococo { proof: Self::MessagesProof, messages_count: u32, ) -> Result>, Self::Error> { - bridge_runtime_common::messages::target::verify_messages_proof_from_parachain::< + bridge_runtime_common::messages::target::verify_messages_proof::< WithBridgeHubWococoMessageBridge, - bp_bridge_hub_wococo::Header, - crate::Runtime, - crate::BridgeParachainWococoInstance, - >(ParaId(bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID), proof, messages_count) + >(proof, messages_count) + .map_err(Into::into) } } @@ -149,20 +147,11 @@ impl TargetHeaderChain for BridgeHubWococo fn verify_messages_delivery_proof( proof: Self::MessagesDeliveryProof, ) -> Result<(LaneId, InboundLaneData), Self::Error> { - messages::source::verify_messages_delivery_proof_from_parachain::< - WithBridgeHubWococoMessageBridge, - bp_bridge_hub_wococo::Header, - crate::Runtime, - crate::BridgeParachainWococoInstance, - >(ParaId(bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID), proof) + messages::source::verify_messages_delivery_proof::(proof) } } impl messages::BridgedChainWithMessages for BridgeHubWococo { - fn maximal_extrinsic_size() -> u32 { - bp_bridge_hub_wococo::BridgeHubWococo::max_extrinsic_size() - } - fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { true } @@ -206,19 +195,15 @@ impl messages::BridgedChainWithMessages for BridgeHubWococo { #[derive(RuntimeDebug, Clone, Copy)] pub struct BridgeHubRococo; -impl ChainWithMessages for BridgeHubRococo { - type Hash = bp_bridge_hub_rococo::Hash; - type AccountId = bp_bridge_hub_rococo::AccountId; - type Signer = bp_bridge_hub_rococo::AccountSigner; - type Signature = bp_bridge_hub_rococo::Signature; - type Balance = bp_bridge_hub_rococo::Balance; +impl UnderlyingChainProvider for BridgeHubRococo { + type Chain = bp_bridge_hub_rococo::BridgeHubRococo; } impl ThisChainWithMessages for BridgeHubRococo { type RuntimeOrigin = crate::RuntimeOrigin; type RuntimeCall = crate::RuntimeCall; type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation< - Self::AccountId, + bp_bridge_hub_rococo::AccountId, { bp_bridge_hub_rococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() }, { bp_bridge_hub_wococo::EXTRA_STORAGE_PROOF_SIZE }, { bp_bridge_hub_rococo::TX_EXTRA_BYTES }, diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs index a4039e1b944..c90b3dd29d9 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -15,22 +15,21 @@ // along with Cumulus. If not, see . use crate::{ - ParachainInfo, Runtime, WithBridgeHubRococoMessagesInstance, XcmAsPlainPayload, XcmBlobHauler, - XcmBlobHaulerAdapter, XcmRouter, + BridgeParachainRococoInstance, ParachainInfo, Runtime, WithBridgeHubRococoMessagesInstance, + XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmRouter, }; use bp_messages::{ source_chain::TargetHeaderChain, target_chain::{ProvedMessages, SourceHeaderChain}, InboundLaneData, LaneId, Message, MessageNonce, }; -use bp_polkadot_core::parachains::ParaId; -use bp_runtime::{Chain, ChainId}; +use bp_runtime::ChainId; use bridge_runtime_common::{ messages, messages::{ target::FromBridgedChainMessagesProof, BasicConfirmationTransactionEstimation, - BridgedChain, ChainWithMessages, MessageBridge, MessageTransaction, ThisChain, - ThisChainWithMessages, + BridgedChain, MessageBridge, MessageTransaction, ThisChain, ThisChainWithMessages, + UnderlyingChainProvider, }, }; use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; @@ -89,6 +88,11 @@ impl MessageBridge for WithBridgeHubRococoMessageBridge { bp_bridge_hub_wococo::WITH_BRIDGE_HUB_WOCOCO_MESSAGES_PALLET_NAME; type ThisChain = BridgeHubWococo; type BridgedChain = BridgeHubRococo; + type BridgedHeaderChain = pallet_bridge_parachains::ParachainHeaders< + Runtime, + BridgeParachainRococoInstance, + bp_bridge_hub_rococo::BridgeHubRococo, + >; fn bridged_balance_to_this_balance( bridged_balance: bridge_runtime_common::messages::BalanceOf>, @@ -112,12 +116,8 @@ pub type ToBridgeHubRococoMaximalOutboundPayloadSize = #[derive(RuntimeDebug, Clone, Copy)] pub struct BridgeHubRococo; -impl ChainWithMessages for BridgeHubRococo { - type Hash = bp_bridge_hub_rococo::Hash; - type AccountId = bp_bridge_hub_rococo::AccountId; - type Signer = bp_bridge_hub_rococo::AccountSigner; - type Signature = bp_bridge_hub_rococo::Signature; - type Balance = bp_bridge_hub_rococo::Balance; +impl UnderlyingChainProvider for BridgeHubRococo { + type Chain = bp_bridge_hub_rococo::BridgeHubRococo; } impl SourceHeaderChain for BridgeHubRococo { @@ -128,12 +128,10 @@ impl SourceHeaderChain for BridgeHubRococo { proof: Self::MessagesProof, messages_count: u32, ) -> Result>, Self::Error> { - bridge_runtime_common::messages::target::verify_messages_proof_from_parachain::< + bridge_runtime_common::messages::target::verify_messages_proof::< WithBridgeHubRococoMessageBridge, - bp_bridge_hub_rococo::Header, - crate::Runtime, - crate::BridgeParachainRococoInstance, - >(ParaId(bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID), proof, messages_count) + >(proof, messages_count) + .map_err(Into::into) } } @@ -149,20 +147,11 @@ impl TargetHeaderChain for BridgeHubRococo fn verify_messages_delivery_proof( proof: Self::MessagesDeliveryProof, ) -> Result<(LaneId, InboundLaneData), Self::Error> { - messages::source::verify_messages_delivery_proof_from_parachain::< - WithBridgeHubRococoMessageBridge, - bp_bridge_hub_rococo::Header, - crate::Runtime, - crate::BridgeParachainRococoInstance, - >(ParaId(bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID), proof) + messages::source::verify_messages_delivery_proof::(proof) } } impl messages::BridgedChainWithMessages for BridgeHubRococo { - fn maximal_extrinsic_size() -> u32 { - bp_bridge_hub_rococo::BridgeHubRococo::max_extrinsic_size() - } - fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { true } @@ -206,19 +195,15 @@ impl messages::BridgedChainWithMessages for BridgeHubRococo { #[derive(RuntimeDebug, Clone, Copy)] pub struct BridgeHubWococo; -impl ChainWithMessages for BridgeHubWococo { - type Hash = bp_bridge_hub_wococo::Hash; - type AccountId = bp_bridge_hub_wococo::AccountId; - type Signer = bp_bridge_hub_wococo::AccountSigner; - type Signature = bp_bridge_hub_wococo::Signature; - type Balance = bp_bridge_hub_wococo::Balance; +impl UnderlyingChainProvider for BridgeHubWococo { + type Chain = bp_bridge_hub_wococo::BridgeHubWococo; } impl ThisChainWithMessages for BridgeHubWococo { type RuntimeOrigin = crate::RuntimeOrigin; type RuntimeCall = crate::RuntimeCall; type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation< - Self::AccountId, + bp_bridge_hub_wococo::AccountId, { bp_bridge_hub_wococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() }, { bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE }, { bp_bridge_hub_wococo::TX_EXTRA_BYTES }, From 05985d77f6c755e30d8e6e40047d1120514eccf9 Mon Sep 17 00:00:00 2001 From: Anthony Lazam Date: Thu, 17 Nov 2022 20:00:17 +0800 Subject: [PATCH 87/91] Update BridgeHub runtime version --- parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 10a31c9dec6..61063c82970 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -168,7 +168,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bridge-hub-rococo"), impl_name: create_runtime_str!("bridge-hub-rococo"), authoring_version: 1, - spec_version: 1, + spec_version: 9300, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 18a3ea9aafbaddf6c45959acd0455af082f683dc Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 23 Nov 2022 00:09:04 +0100 Subject: [PATCH 88/91] Fixes --- Cargo.lock | 25 ++++++- .../src/bridge_common_config.rs | 44 ++++-------- .../src/bridge_hub_rococo_config.rs | 72 ++----------------- .../src/bridge_hub_wococo_config.rs | 72 ++----------------- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 34 +++------ 5 files changed, 57 insertions(+), 190 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0a310083eb..5c3d6d46531 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -713,6 +713,27 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bp-beefy" +version = "0.1.0" +dependencies = [ + "beefy-merkle-tree", + "beefy-primitives", + "bp-runtime", + "frame-support", + "pallet-beefy-mmr", + "pallet-mmr", + "parity-scale-codec", + "scale-info", + "serde", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "static_assertions", +] + [[package]] name = "bp-bridge-hub-rococo" version = "0.1.0" @@ -763,7 +784,6 @@ dependencies = [ name = "bp-messages" version = "0.1.0" dependencies = [ - "bitvec", "bp-runtime", "frame-support", "hex", @@ -780,6 +800,7 @@ dependencies = [ name = "bp-millau" version = "0.1.0" dependencies = [ + "bp-beefy", "bp-messages", "bp-runtime", "fixed-hash 0.7.0", @@ -911,6 +932,7 @@ dependencies = [ "sp-finality-grandpa", "sp-runtime", "sp-std", + "xcm", ] [[package]] @@ -5966,7 +5988,6 @@ dependencies = [ name = "pallet-bridge-messages" version = "0.1.0" dependencies = [ - "bitvec", "bp-messages", "bp-runtime", "bp-test-utils", diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 94c0d1660be..5a2a322d96d 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -19,7 +19,7 @@ use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, LaneId, }; -use bp_runtime::{messages::MessageDispatchResult, AccountIdOf, BalanceOf, Chain}; +use bp_runtime::{messages::MessageDispatchResult, AccountIdOf, Chain}; use codec::Encode; use frame_support::{dispatch::Weight, parameter_types}; use xcm::latest::prelude::*; @@ -43,38 +43,37 @@ pub struct XcmBlobMessageDispatch - MessageDispatch, BalanceOf> + MessageDispatch> for XcmBlobMessageDispatch { type DispatchPayload = XcmAsPlainPayload; - fn dispatch_weight( - message: &mut DispatchMessage>, - ) -> Weight { + fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { log::error!( "[XcmBlobMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor - message: ?...?", ); + // TODO:check-parameter - setup weight? Weight::zero() } fn dispatch( _relayer_account: &AccountIdOf, - message: DispatchMessage>, + message: DispatchMessage, ) -> MessageDispatchResult { - log::warn!("[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob triggering"); + log::warn!("[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob triggering - message_nonce: {:?}", message.key.nonce); let payload = match message.data.payload { Ok(payload) => payload, Err(e) => { - log::error!("[XcmBlobMessageDispatch] payload error: {:?}", e); + log::error!("[XcmBlobMessageDispatch] payload error: {:?} - message_nonce: {:?}", e, message.key.nonce); return MessageDispatchResult { - dispatch_result: false, + // TODO:check-parameter - setup uspent_weight? unspent_weight: Weight::zero(), dispatch_fee_paid_during_dispatch: false, } }, }; - let dispatch_result = match BlobDispatcher::dispatch_blob(payload) { - Ok(_) => true, + match BlobDispatcher::dispatch_blob(payload) { + Ok(_) => log::debug!("[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob was ok - message_nonce: {:?}", message.key.nonce), Err(e) => { let e = match e { DispatchBlobError::Unbridgable => "DispatchBlobError::Unbridgable", @@ -89,14 +88,13 @@ impl "DispatchBlobError::WrongGlobal", }; log::error!( - "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?}", - e + "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?} - message_nonce: {:?}", + e, message.key.nonce ); - false }, - }; + } MessageDispatchResult { - dispatch_result, + // TODO:check-parameter - setup uspent_weight? dispatch_fee_paid_during_dispatch: false, unspent_weight: Weight::zero(), } @@ -106,15 +104,8 @@ impl, - XcmAsPlainPayload, - >; + type MessageSender: MessagesBridge; /// Our location within the Consensus Universe. fn message_sender_origin() -> InteriorMultiLocation; @@ -127,15 +118,10 @@ pub struct XcmBlobHaulerAdapter(sp_std::marker::PhantomData HaulBlob for XcmBlobHaulerAdapter { fn haul_blob(blob: sp_std::prelude::Vec) { let lane = H::xcm_lane(); - // TODO:check-parameter - fee could be taken from BridgeMessage - or add as optional fo send_message - // TODO:check-parameter - or add here something like PriceForSiblingDelivery - let fee = ::Balance::from(0u8); - let result = H::MessageSender::send_message( pallet_xcm::Origin::from(MultiLocation::from(H::message_sender_origin())).into(), lane, blob, - fee, ); let result = result .map(|artifacts| { diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs index c242285c357..2a8e00bb7a0 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -27,13 +27,11 @@ use bp_runtime::ChainId; use bridge_runtime_common::{ messages, messages::{ - target::FromBridgedChainMessagesProof, BasicConfirmationTransactionEstimation, - BridgedChain, MessageBridge, MessageTransaction, ThisChain, ThisChainWithMessages, + target::FromBridgedChainMessagesProof, MessageBridge, ThisChainWithMessages, UnderlyingChainProvider, }, }; -use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; -use sp_runtime::FixedU128; +use frame_support::{parameter_types, RuntimeDebug}; use xcm::{ latest::prelude::*, prelude::{InteriorMultiLocation, NetworkId}, @@ -49,6 +47,7 @@ parameter_types! { pub const BridgeHubWococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; pub BridgeHubRococoUniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(ParachainInfo::parachain_id().into())); pub WococoGlobalConsensusNetwork: NetworkId = NetworkId::Wococo; + pub ActiveOutboundLanesToBridgeHubWococo: &'static [bp_messages::LaneId] = &[DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO]; } /// Dispatches received XCM messages from other bridge @@ -64,7 +63,6 @@ pub type ToBridgeHubWococoHaulBlobExporter = HaulBlobExporter< pub struct ToBridgeHubWococoXcmBlobHauler; pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO: LaneId = [0, 0, 0, 2]; impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { - type SenderChain = bp_bridge_hub_rococo::BridgeHubRococo; type MessageSender = pallet_bridge_messages::Pallet; @@ -80,8 +78,6 @@ impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { /// Messaging Bridge configuration for BridgeHubRococo -> BridgeHubWococo pub struct WithBridgeHubWococoMessageBridge; impl MessageBridge for WithBridgeHubWococoMessageBridge { - // TODO:check-parameter - relayers rewards - const RELAYER_FEE_PERCENT: u32 = 0; const THIS_CHAIN_ID: ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; const BRIDGED_CHAIN_ID: ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; const BRIDGED_MESSAGES_PALLET_NAME: &'static str = @@ -93,15 +89,6 @@ impl MessageBridge for WithBridgeHubWococoMessageBridge { BridgeParachainWococoInstance, bp_bridge_hub_wococo::BridgeHubWococo, >; - - fn bridged_balance_to_this_balance( - bridged_balance: bridge_runtime_common::messages::BalanceOf>, - bridged_to_this_conversion_rate_override: Option, - ) -> bridge_runtime_common::messages::BalanceOf> { - log::info!("[WithBridgeHubWococoMessageBridge] bridged_balance_to_this_balance (returns 0 balance, TODO: fix) - bridged_balance: {:?}, bridged_to_this_conversion_rate_override: {:?}", bridged_balance, bridged_to_this_conversion_rate_override); - // TODO:check-parameter - any payment? from sovereign account? - 0 - } } /// Message verifier for BridgeHubWococo messages sent from BridgeHubRococo @@ -120,14 +107,14 @@ impl UnderlyingChainProvider for BridgeHubWococo { type Chain = bp_bridge_hub_wococo::BridgeHubWococo; } -impl SourceHeaderChain for BridgeHubWococo { +impl SourceHeaderChain for BridgeHubWococo { type Error = &'static str; type MessagesProof = FromBridgedChainMessagesProof; fn verify_messages_proof( proof: Self::MessagesProof, messages_count: u32, - ) -> Result>, Self::Error> { + ) -> Result, Self::Error> { bridge_runtime_common::messages::target::verify_messages_proof::< WithBridgeHubWococoMessageBridge, >(proof, messages_count) @@ -155,40 +142,6 @@ impl messages::BridgedChainWithMessages for BridgeHubWococo { fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { true } - - fn estimate_delivery_transaction( - message_payload: &[u8], - include_pay_dispatch_fee_cost: bool, - message_dispatch_weight: Weight, - ) -> MessageTransaction { - let message_payload_len = u32::try_from(message_payload.len()).unwrap_or(u32::MAX); - let extra_bytes_in_payload = message_payload_len - .saturating_sub(pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH); - - MessageTransaction { - dispatch_weight: bp_bridge_hub_wococo::ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT - .saturating_mul(extra_bytes_in_payload as u64) - .saturating_add(bp_bridge_hub_wococo::DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT) - .saturating_sub(if include_pay_dispatch_fee_cost { - Weight::from_ref_time(0) - } else { - bp_bridge_hub_wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT - }) - .saturating_add(message_dispatch_weight), - size: message_payload_len - .saturating_add(bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE) - .saturating_add(bp_bridge_hub_wococo::TX_EXTRA_BYTES), - } - } - - fn transaction_payment(transaction: MessageTransaction) -> messages::BalanceOf { - log::info!( - "[BridgeHubWococo::BridgedChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", - transaction - ); - // TODO:check-parameter - any payment? from sovereign account? - 0 - } } /// BridgeHubRococo chain from message lane point of view. @@ -202,12 +155,6 @@ impl UnderlyingChainProvider for BridgeHubRococo { impl ThisChainWithMessages for BridgeHubRococo { type RuntimeOrigin = crate::RuntimeOrigin; type RuntimeCall = crate::RuntimeCall; - type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation< - bp_bridge_hub_rococo::AccountId, - { bp_bridge_hub_rococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() }, - { bp_bridge_hub_wococo::EXTRA_STORAGE_PROOF_SIZE }, - { bp_bridge_hub_rococo::TX_EXTRA_BYTES }, - >; fn is_message_accepted(origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool { log::info!("[BridgeHubRococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); @@ -220,13 +167,4 @@ impl ThisChainWithMessages for BridgeHubRococo { ); MessageNonce::MAX / 2 } - - fn transaction_payment(transaction: MessageTransaction) -> messages::BalanceOf { - log::info!( - "[BridgeHubRococo::ThisChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", - transaction - ); - // TODO:check-parameter - any payment? from sovereign account? - 0 - } } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs index c90b3dd29d9..2c80ca78070 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -27,13 +27,11 @@ use bp_runtime::ChainId; use bridge_runtime_common::{ messages, messages::{ - target::FromBridgedChainMessagesProof, BasicConfirmationTransactionEstimation, - BridgedChain, MessageBridge, MessageTransaction, ThisChain, ThisChainWithMessages, + target::FromBridgedChainMessagesProof, MessageBridge, ThisChainWithMessages, UnderlyingChainProvider, }, }; -use frame_support::{dispatch::Weight, parameter_types, RuntimeDebug}; -use sp_runtime::FixedU128; +use frame_support::{parameter_types, RuntimeDebug}; use xcm::{ latest::prelude::*, prelude::{InteriorMultiLocation, NetworkId}, @@ -49,6 +47,7 @@ parameter_types! { pub const BridgeHubRococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; pub BridgeHubWococoUniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Wococo), Parachain(ParachainInfo::parachain_id().into())); pub RococoGlobalConsensusNetwork: NetworkId = NetworkId::Rococo; + pub ActiveOutboundLanesToBridgeHubRococo: &'static [bp_messages::LaneId] = &[DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO]; } /// Dispatches received XCM messages from other bridge @@ -64,7 +63,6 @@ pub type ToBridgeHubRococoHaulBlobExporter = HaulBlobExporter< pub struct ToBridgeHubRococoXcmBlobHauler; pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO: LaneId = [0, 0, 0, 1]; impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { - type SenderChain = bp_bridge_hub_wococo::BridgeHubWococo; type MessageSender = pallet_bridge_messages::Pallet; @@ -80,8 +78,6 @@ impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { /// Messaging Bridge configuration for BridgeHubWococo -> BridgeHubRococo pub struct WithBridgeHubRococoMessageBridge; impl MessageBridge for WithBridgeHubRococoMessageBridge { - // TODO:check-parameter - relayers rewards - const RELAYER_FEE_PERCENT: u32 = 0; const THIS_CHAIN_ID: ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; const BRIDGED_CHAIN_ID: ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; const BRIDGED_MESSAGES_PALLET_NAME: &'static str = @@ -93,15 +89,6 @@ impl MessageBridge for WithBridgeHubRococoMessageBridge { BridgeParachainRococoInstance, bp_bridge_hub_rococo::BridgeHubRococo, >; - - fn bridged_balance_to_this_balance( - bridged_balance: bridge_runtime_common::messages::BalanceOf>, - bridged_to_this_conversion_rate_override: Option, - ) -> bridge_runtime_common::messages::BalanceOf> { - log::info!("[WithBridgeHubRococoMessageBridge] bridged_balance_to_this_balance (returns 0 balance, TODO: fix) - bridged_balance: {:?}, bridged_to_this_conversion_rate_override: {:?}", bridged_balance, bridged_to_this_conversion_rate_override); - // TODO:check-parameter - any payment? from sovereign account? - 0 - } } /// Message verifier for BridgeHubRococo messages sent from BridgeHubWococo @@ -120,14 +107,14 @@ impl UnderlyingChainProvider for BridgeHubRococo { type Chain = bp_bridge_hub_rococo::BridgeHubRococo; } -impl SourceHeaderChain for BridgeHubRococo { +impl SourceHeaderChain for BridgeHubRococo { type Error = &'static str; type MessagesProof = FromBridgedChainMessagesProof; fn verify_messages_proof( proof: Self::MessagesProof, messages_count: u32, - ) -> Result>, Self::Error> { + ) -> Result, Self::Error> { bridge_runtime_common::messages::target::verify_messages_proof::< WithBridgeHubRococoMessageBridge, >(proof, messages_count) @@ -155,40 +142,6 @@ impl messages::BridgedChainWithMessages for BridgeHubRococo { fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { true } - - fn estimate_delivery_transaction( - message_payload: &[u8], - include_pay_dispatch_fee_cost: bool, - message_dispatch_weight: Weight, - ) -> MessageTransaction { - let message_payload_len = u32::try_from(message_payload.len()).unwrap_or(u32::MAX); - let extra_bytes_in_payload = message_payload_len - .saturating_sub(pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH); - - MessageTransaction { - dispatch_weight: bp_bridge_hub_rococo::ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT - .saturating_mul(extra_bytes_in_payload as u64) - .saturating_add(bp_bridge_hub_rococo::DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT) - .saturating_sub(if include_pay_dispatch_fee_cost { - Weight::from_ref_time(0) - } else { - bp_bridge_hub_rococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT - }) - .saturating_add(message_dispatch_weight), - size: message_payload_len - .saturating_add(bp_bridge_hub_wococo::EXTRA_STORAGE_PROOF_SIZE) - .saturating_add(bp_bridge_hub_rococo::TX_EXTRA_BYTES), - } - } - - fn transaction_payment(transaction: MessageTransaction) -> messages::BalanceOf { - log::info!( - "[BridgeHubRococo::BridgedChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", - transaction - ); - // TODO:check-parameter - any payment? from sovereign account? - 0 - } } /// BridgeHubWococo chain from message lane point of view. @@ -202,12 +155,6 @@ impl UnderlyingChainProvider for BridgeHubWococo { impl ThisChainWithMessages for BridgeHubWococo { type RuntimeOrigin = crate::RuntimeOrigin; type RuntimeCall = crate::RuntimeCall; - type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation< - bp_bridge_hub_wococo::AccountId, - { bp_bridge_hub_wococo::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() }, - { bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE }, - { bp_bridge_hub_wococo::TX_EXTRA_BYTES }, - >; fn is_message_accepted(origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool { log::info!("[BridgeHubWococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); @@ -220,13 +167,4 @@ impl ThisChainWithMessages for BridgeHubWococo { ); MessageNonce::MAX / 2 } - - fn transaction_payment(transaction: MessageTransaction) -> messages::BalanceOf { - log::info!( - "[BridgeHubWococo::ThisChainWithMessages] transaction_payment (returns 0 balance, TODO: fix) - transaction: {:?}", - transaction - ); - // TODO:check-parameter - any payment? from sovereign account? - 0 - } } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 61063c82970..ba0c67cb2b0 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -216,7 +216,6 @@ pub mod runtime_api { use bp_messages::{ InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, }; - use frame_support::{sp_runtime::FixedU128, Parameter}; use sp_std::prelude::Vec; bp_runtime::decl_bridge_messages_runtime_apis!(bridge_hub_rococo); bp_runtime::decl_bridge_messages_runtime_apis!(bridge_hub_wococo); @@ -522,8 +521,7 @@ impl pallet_bridge_messages::Config for Run type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type BridgedChainId = bridge_hub_rococo_config::BridgeHubWococoChainId; - type Parameter = (); - type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; + type ActiveOutboundLanes = bridge_hub_rococo_config::ActiveOutboundLanesToBridgeHubWococo; type MaxUnrewardedRelayerEntriesAtInboundLane = bridge_hub_rococo_config::MaxUnrewardedRelayerEntriesAtInboundLane; type MaxUnconfirmedMessagesAtInboundLane = @@ -532,17 +530,13 @@ impl pallet_bridge_messages::Config for Run type MaximalOutboundPayloadSize = bridge_hub_rococo_config::ToBridgeHubWococoMaximalOutboundPayloadSize; type OutboundPayload = XcmAsPlainPayload; - type OutboundMessageFee = Balance; type InboundPayload = XcmAsPlainPayload; - type InboundMessageFee = Balance; type InboundRelayer = AccountId; type TargetHeaderChain = bridge_hub_rococo_config::BridgeHubWococo; type LaneMessageVerifier = bridge_hub_rococo_config::ToBridgeHubWococoMessageVerifier; type MessageDeliveryAndDispatchPayment = (); - type OnMessageAccepted = (); - type OnDeliveryConfirmed = (); type SourceHeaderChain = bridge_hub_rococo_config::BridgeHubWococo; type MessageDispatch = XcmBlobMessageDispatch< @@ -550,6 +544,7 @@ impl pallet_bridge_messages::Config for Run bp_bridge_hub_rococo::BridgeHubRococo, OnBridgeHubRococoBlobDispatcher, >; + } /// Add XCM messages support for BrigdeHubWococo to support Wococo->Rococo XCM messages @@ -558,8 +553,7 @@ impl pallet_bridge_messages::Config for Run type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type BridgedChainId = bridge_hub_wococo_config::BridgeHubRococoChainId; - type Parameter = (); - type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; + type ActiveOutboundLanes = bridge_hub_wococo_config::ActiveOutboundLanesToBridgeHubRococo; type MaxUnrewardedRelayerEntriesAtInboundLane = bridge_hub_wococo_config::MaxUnrewardedRelayerEntriesAtInboundLane; type MaxUnconfirmedMessagesAtInboundLane = @@ -568,17 +562,13 @@ impl pallet_bridge_messages::Config for Run type MaximalOutboundPayloadSize = bridge_hub_wococo_config::ToBridgeHubRococoMaximalOutboundPayloadSize; type OutboundPayload = XcmAsPlainPayload; - type OutboundMessageFee = Balance; type InboundPayload = XcmAsPlainPayload; - type InboundMessageFee = Balance; type InboundRelayer = AccountId; type TargetHeaderChain = bridge_hub_wococo_config::BridgeHubRococo; type LaneMessageVerifier = bridge_hub_wococo_config::ToBridgeHubRococoMessageVerifier; type MessageDeliveryAndDispatchPayment = (); - type OnMessageAccepted = (); - type OnDeliveryConfirmed = (); type SourceHeaderChain = bridge_hub_wococo_config::BridgeHubRococo; type MessageDispatch = XcmBlobMessageDispatch< @@ -797,21 +787,15 @@ impl_runtime_apis! { } } - // This exposed by BridgeHubRococo - impl runtime_api::ToBridgeHubWococoOutboundLaneApi for Runtime { - fn estimate_message_delivery_and_dispatch_fee( - _lane_id: bp_messages::LaneId, - payload: XcmAsPlainPayload, - conversion_rate: Option, - ) -> Option { - None - } + // TODO: add here other directions + // This exposed by BridgeHubRococo + impl runtime_api::ToBridgeHubWococoOutboundLaneApi for Runtime { fn message_details( lane: bp_messages::LaneId, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, - ) -> Vec> { + ) -> Vec { bridge_runtime_common::messages_api::outbound_message_details::< Runtime, WithBridgeHubWococoMessagesInstance, @@ -820,10 +804,10 @@ impl_runtime_apis! { } // This is exposed by BridgeHubWococo - impl runtime_api::FromBridgeHubRococoInboundLaneApi for Runtime { + impl runtime_api::FromBridgeHubRococoInboundLaneApi for Runtime { fn message_details( lane: bp_messages::LaneId, - messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, + messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> Vec { bridge_runtime_common::messages_api::inbound_message_details::< Runtime, From 1a68129afb9cc04150bcff13d669ccfadd9315fc Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 23 Nov 2022 15:08:59 +0100 Subject: [PATCH 89/91] Zombienet for bridge-hub setup --- parachains/runtimes/bridge-hubs/README.md | 43 +++++++++++--- scripts/bridges_rococo_wococo.sh | 4 +- .../bridge_hub_rococo_local_network.toml | 56 +++++++++++++++++++ .../bridge_hub_wococo_local_network.toml | 56 +++++++++++++++++++ .../0004-run_bridge_hubs_rococo.toml | 37 ------------ .../0004-run_bridge_hubs_wococo.toml | 37 ------------ 6 files changed, 150 insertions(+), 83 deletions(-) create mode 100644 zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml create mode 100644 zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml delete mode 100644 zombienet_tests/0004-run_bridge_hubs_rococo.toml delete mode 100644 zombienet_tests/0004-run_bridge_hubs_wococo.toml diff --git a/parachains/runtimes/bridge-hubs/README.md b/parachains/runtimes/bridge-hubs/README.md index 1509df525b1..d2065553e3e 100644 --- a/parachains/runtimes/bridge-hubs/README.md +++ b/parachains/runtimes/bridge-hubs/README.md @@ -41,6 +41,22 @@ cargo build -p substrate-relay cp target/release/substrate-relay ~/local_bridge_testing/bin/substrate-relay ``` +### Run chains (Rococo + BridgeHub, Wococo + BridgeHub) with zombienet + +``` +# Rococo + BridgeHubWococo +POLKADOT_BINARY_PATH=../../polkadot/target/release/polkadot \ +POLKADOT_PARACHAIN_BINARY_PATH=./target/release/polkadot-parachain \ + ./zombienet-linux --provider native spawn ./zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +``` + +``` +# Wococo + BridgeHubWococo +POLKADOT_BINARY_PATH=../../polkadot/target/release/polkadot \ +POLKADOT_PARACHAIN_BINARY_PATH=./target/release/polkadot-parachain \ + ./zombienet-linux --provider native spawn ./zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml +``` + ### Run chains (Rococo + BridgeHub, Wococo + BridgeHub) from `./scripts/bridges_rococo_wococo.sh` ``` @@ -75,7 +91,7 @@ or RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay init-bridge rococo-to-bridge-hub-wococo \ --source-host localhost \ - --source-port 48943 \ + --source-port 9942 \ --target-host localhost \ --target-port 8945 \ --target-signer //Bob @@ -84,7 +100,7 @@ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay init-bridge wococo-to-bridge-hub-rococo \ --source-host localhost \ - --source-port 48945 \ + --source-port 9945 \ --target-host localhost \ --target-port 8943 \ --target-signer //Bob @@ -100,7 +116,7 @@ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay relay-headers rococo-to-bridge-hub-wococo \ --source-host localhost \ - --source-port 48943 \ + --source-port 9942 \ --target-host localhost \ --target-port 8945 \ --target-signer //Bob \ @@ -110,7 +126,7 @@ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay relay-headers wococo-to-bridge-hub-rococo \ --source-host localhost \ - --source-port 48945 \ + --source-port 9945 \ --target-host localhost \ --target-port 8943 \ --target-signer //Bob \ @@ -134,7 +150,7 @@ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay relay-parachains bridge-hub-rococo-to-bridge-hub-wococo \ --source-host localhost \ - --source-port 48943 \ + --source-port 9942 \ --target-host localhost \ --target-port 8945 \ --target-signer //Bob \ @@ -144,7 +160,7 @@ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay relay-parachains bridge-hub-wococo-to-bridge-hub-rococo \ --source-host localhost \ - --source-port 48945 \ + --source-port 9945 \ --target-host localhost \ --target-port 8943 \ --target-signer //Bob \ @@ -176,7 +192,20 @@ RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ --target-transactions-mortality=4 \ --lane 00000002 ``` -TODO: + +``` +# Wococo -> Rococo +RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + ~/local_bridge_testing/bin/substrate-relay relay-messages bridge-hub-wococo-to-bridge-hub-rococo \ + --source-host localhost \ + --source-port 8945 \ + --source-signer //Charlie \ + --target-host localhost \ + --target-port 8943 \ + --target-signer //Charlie \ + --target-transactions-mortality=4 \ + --lane 00000001 +``` --- diff --git a/scripts/bridges_rococo_wococo.sh b/scripts/bridges_rococo_wococo.sh index 53d7dc974aa..f6d2630af6e 100755 --- a/scripts/bridges_rococo_wococo.sh +++ b/scripts/bridges_rococo_wococo.sh @@ -138,7 +138,7 @@ case "$1" in RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay init-bridge rococo-to-bridge-hub-wococo \ --source-host localhost \ - --source-port 48943 \ + --source-port 9942 \ --target-host localhost \ --target-port 8945 \ --target-signer //Bob @@ -148,7 +148,7 @@ case "$1" in RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ ~/local_bridge_testing/bin/substrate-relay init-bridge wococo-to-bridge-hub-rococo \ --source-host localhost \ - --source-port 48945 \ + --source-port 9945 \ --target-host localhost \ --target-port 8943 \ --target-signer //Bob diff --git a/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml b/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml new file mode 100644 index 00000000000..e6ca8b893f7 --- /dev/null +++ b/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml @@ -0,0 +1,56 @@ +[relaychain] +default_command = "{{POLKADOT_BINARY_PATH}}" +default_args = [ "-lparachain=debug,xcm::weight=trace,xcm::filter_asset_location=trace,xcm::send_xcm=trace,xcm::barriers=trace,xcm::barrier=trace,xcm::execute_xcm=trace,xcm::contains=trace,xcm::execute_xcm_in_credit,xcm::process_instruction=trace,xcm::currency_adapter=trace,xcm::origin_conversion=trace,xcm::fungibles_adapter=trace,xcm::process=trace,xcm::execute=trace" ] +chain = "rococo-local" + + [[relaychain.nodes]] + name = "alice-validator" + validator = true + rpc_port = 9932 + ws_port = 9942 + extra_args = ["--no-mdns --bootnodes {{'bob-validator'|zombie('multiAddress')}}"] + + [[relaychain.nodes]] + name = "bob-validator" + validator = true + rpc_port = 9933 + ws_port = 9943 + extra_args = ["--no-mdns --bootnodes {{'alice-validator'|zombie('multiAddress')}}"] + + [[relaychain.nodes]] + name = "charlie-validator" + validator = true + rpc_port = 9934 + ws_port = 9944 + extra_args = ["--no-mdns --bootnodes {{'alice-validator'|zombie('multiAddress')}}"] + +[[parachains]] +id = 1013 +chain = "bridge-hub-rococo-local" +cumulus_based = true + + # run alice as parachain collator + [[parachains.collators]] + name = "alice-collator" + validator = true + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + rpc_port = 8933 + ws_port = 8943 + extra_args = [ + "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm::weight=trace,xcm::filter_asset_location=trace,xcm::send_xcm=trace,xcm::barriers=trace,xcm::barrier=trace,xcm::execute_xcm=trace,xcm::contains=trace,xcm::execute_xcm_in_credit,xcm::process_instruction=trace,xcm::currency_adapter=trace,xcm::origin_conversion=trace,xcm::fungibles_adapter=trace,xcm::process=trace,xcm::execute=trace", + "--force-authoring", "--no-mdns", "--bootnodes {{'bob-collator'|zombie('multiAddress')}}", + "-- --port 41333 --rpc-port 48933 --ws-port 48943 --no-mdns", "--bootnodes {{'alice-validator'|zombie('multiAddress')}}" + ] + + # run bob as parachain collator + [[parachains.collators]] + name = "bob-collator" + validator = true + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + rpc_port = 8934 + ws_port = 8944 + extra_args = [ + "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm::weight=trace,xcm::filter_asset_location=trace,xcm::send_xcm=trace,xcm::barriers=trace,xcm::barrier=trace,xcm::execute_xcm=trace,xcm::contains=trace,xcm::execute_xcm_in_credit,xcm::process_instruction=trace,xcm::currency_adapter=trace,xcm::origin_conversion=trace,xcm::fungibles_adapter=trace,xcm::process=trace,xcm::execute=trace", + "--force-authoring", "--no-mdns", "--bootnodes {{'alice-collator'|zombie('multiAddress')}}", + "-- --port 41334 --rpc-port 48934 --ws-port 48944 --no-mdns", "--bootnodes {{'bob-validator'|zombie('multiAddress')}}" + ] diff --git a/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml b/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml new file mode 100644 index 00000000000..cc287419616 --- /dev/null +++ b/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml @@ -0,0 +1,56 @@ +[relaychain] +default_command = "{{POLKADOT_BINARY_PATH}}" +default_args = [ "-lparachain=debug,xcm::weight=trace,xcm::filter_asset_location=trace,xcm::send_xcm=trace,xcm::barriers=trace,xcm::barrier=trace,xcm::execute_xcm=trace,xcm::contains=trace,xcm::execute_xcm_in_credit,xcm::process_instruction=trace,xcm::currency_adapter=trace,xcm::origin_conversion=trace,xcm::fungibles_adapter=trace,xcm::process=trace,xcm::execute=trace" ] +chain = "wococo-local" + + [[relaychain.nodes]] + name = "alice-validator-wo" + validator = true + rpc_port = 9935 + ws_port = 9945 + extra_args = ["--no-mdns --bootnodes {{'bob-validator-wo'|zombie('multiAddress')}}"] + + [[relaychain.nodes]] + name = "bob-validator-wo" + validator = true + rpc_port = 9936 + ws_port = 9946 + extra_args = ["--no-mdns --bootnodes {{'alice-validator-wo'|zombie('multiAddress')}}"] + + [[relaychain.nodes]] + name = "charlie-validator-wo" + validator = true + rpc_port = 9937 + ws_port = 9947 + extra_args = ["--no-mdns --bootnodes {{'alice-validator-wo'|zombie('multiAddress')}}"] + +[[parachains]] +id = 1014 +chain = "bridge-hub-wococo-local" +cumulus_based = true + + # run alice as parachain collator + [[parachains.collators]] + name = "alice-collator-wo" + validator = true + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + rpc_port = 8935 + ws_port = 8945 + extra_args = [ + "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm::weight=trace,xcm::filter_asset_location=trace,xcm::send_xcm=trace,xcm::barriers=trace,xcm::barrier=trace,xcm::execute_xcm=trace,xcm::contains=trace,xcm::execute_xcm_in_credit,xcm::process_instruction=trace,xcm::currency_adapter=trace,xcm::origin_conversion=trace,xcm::fungibles_adapter=trace,xcm::process=trace,xcm::execute=trace", + "--force-authoring", "--no-mdns", "--bootnodes {{'bob-collator-wo'|zombie('multiAddress')}}", + "-- --port 41335 --rpc-port 48935 --ws-port 48945 --no-mdns", "--bootnodes {{'alice-validator-wo'|zombie('multiAddress')}}" + ] + + # run bob as parachain collator + [[parachains.collators]] + name = "bob-collator-wo" + validator = true + command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + rpc_port = 8936 + ws_port = 8946 + extra_args = [ + "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm::weight=trace,xcm::filter_asset_location=trace,xcm::send_xcm=trace,xcm::barriers=trace,xcm::barrier=trace,xcm::execute_xcm=trace,xcm::contains=trace,xcm::execute_xcm_in_credit,xcm::process_instruction=trace,xcm::currency_adapter=trace,xcm::origin_conversion=trace,xcm::fungibles_adapter=trace,xcm::process=trace,xcm::execute=trace", + "--force-authoring", "--no-mdns", "--bootnodes {{'alice-collator-wo'|zombie('multiAddress')}}", + "-- --port 41336 --rpc-port 48936 --ws-port 48946 --no-mdns", "--bootnodes {{'bob-validator-wo'|zombie('multiAddress')}}" + ] diff --git a/zombienet_tests/0004-run_bridge_hubs_rococo.toml b/zombienet_tests/0004-run_bridge_hubs_rococo.toml deleted file mode 100644 index 9c9c9c49d15..00000000000 --- a/zombienet_tests/0004-run_bridge_hubs_rococo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[relaychain] -default_command = "{{POLKADOT_BINARY_PATH}}" -default_args = [ "-lparachain=debug" ] - -chain = "rococo-local" - - [[relaychain.nodes]] - name = "alice" - validator = true - - [[relaychain.nodes]] - name = "bob" - validator = true - - [[relaychain.nodes]] - name = "charlie" - validator = true - -[[parachains]] -id = 1013 -cumulus_based = true -chain = "bridge-hub-rococo-local" - - [[parachains.collators]] - name = "alice" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" - args = ["-lparachain=debug"] - - [[parachains.collators]] - name = "bob" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" - args = ["-lparachain=debug"] - - [[parachains.collators]] - name = "charlie" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" - args = ["-lparachain=debug"] diff --git a/zombienet_tests/0004-run_bridge_hubs_wococo.toml b/zombienet_tests/0004-run_bridge_hubs_wococo.toml deleted file mode 100644 index b938b1672dd..00000000000 --- a/zombienet_tests/0004-run_bridge_hubs_wococo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[relaychain] -default_command = "{{POLKADOT_BINARY_PATH}}" -default_args = [ "-lparachain=debug" ] - -chain = "wococo-local" - - [[relaychain.nodes]] - name = "alice" - validator = true - - [[relaychain.nodes]] - name = "bob" - validator = true - - [[relaychain.nodes]] - name = "charlie" - validator = true - -[[parachains]] -id = 1013 -cumulus_based = true -chain = "bridge-hub-wococo-local" - - [[parachains.collators]] - name = "alice" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" - args = ["-lparachain=debug"] - - [[parachains.collators]] - name = "bob" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" - args = ["-lparachain=debug"] - - [[parachains.collators]] - name = "charlie" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" - args = ["-lparachain=debug"] From fa505c0a8188f276013c98bfe6d0de4186415e00 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 23 Nov 2022 22:54:43 +0100 Subject: [PATCH 90/91] Fixes --- .../src/bridge_common_config.rs | 23 ++++- .../src/bridge_hub_rococo_config.rs | 3 +- .../src/bridge_hub_wococo_config.rs | 3 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 32 ++++++- .../bridge-hub-rococo/src/weights/xcm/mod.rs | 7 +- .../bridge-hub-rococo/src/xcm_config.rs | 87 ++++--------------- .../src/chain_spec/bridge_hubs.rs | 4 +- 7 files changed, 77 insertions(+), 82 deletions(-) diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 5a2a322d96d..fd1672d9fcb 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -50,6 +50,7 @@ impl) -> Weight { log::error!( + target: crate::LOG_TARGET, "[XcmBlobMessageDispatch] TODO: change here to XCMv3 dispatch_weight with XcmExecutor - message: ?...?", ); // TODO:check-parameter - setup weight? @@ -60,11 +61,20 @@ impl, message: DispatchMessage, ) -> MessageDispatchResult { - log::warn!("[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob triggering - message_nonce: {:?}", message.key.nonce); + log::warn!( + target: crate::LOG_TARGET, + "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob triggering - message_nonce: {:?}", + message.key.nonce + ); let payload = match message.data.payload { Ok(payload) => payload, Err(e) => { - log::error!("[XcmBlobMessageDispatch] payload error: {:?} - message_nonce: {:?}", e, message.key.nonce); + log::error!( + target: crate::LOG_TARGET, + "[XcmBlobMessageDispatch] payload error: {:?} - message_nonce: {:?}", + e, + message.key.nonce + ); return MessageDispatchResult { // TODO:check-parameter - setup uspent_weight? unspent_weight: Weight::zero(), @@ -73,7 +83,11 @@ impl log::debug!("[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob was ok - message_nonce: {:?}", message.key.nonce), + Ok(_) => log::debug!( + target: crate::LOG_TARGET, + "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob was ok - message_nonce: {:?}", + message.key.nonce + ), Err(e) => { let e = match e { DispatchBlobError::Unbridgable => "DispatchBlobError::Unbridgable", @@ -88,6 +102,7 @@ impl "DispatchBlobError::WrongGlobal", }; log::error!( + target: crate::LOG_TARGET, "[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?} - message_nonce: {:?}", e, message.key.nonce ); @@ -129,7 +144,7 @@ impl HaulBlob for XcmBlobHaulerAdapter { hash }) .map_err(|e| e); - log::info!(target: "runtime::bridge-hub", "haul_blob result: {:?} on lane: {:?}", result, lane); + log::info!(target: crate::LOG_TARGET, "haul_blob result: {:?} on lane: {:?}", result, lane); result.expect("failed to process: TODO:check-parameter - wait for origin/gav-xcm-v3, there is a comment about handliing errors for HaulBlob"); } } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs index 2a8e00bb7a0..383189aff30 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs @@ -157,12 +157,13 @@ impl ThisChainWithMessages for BridgeHubRococo { type RuntimeCall = crate::RuntimeCall; fn is_message_accepted(origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool { - log::info!("[BridgeHubRococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); + log::info!(target: crate::LOG_TARGET, "[BridgeHubRococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); lane == &DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO } fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { log::info!( + target: crate::LOG_TARGET, "[BridgeHubRococo::ThisChainWithMessages] maximal_pending_messages_at_outbound_lane" ); MessageNonce::MAX / 2 diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs index 2c80ca78070..8973d9f179c 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs @@ -157,12 +157,13 @@ impl ThisChainWithMessages for BridgeHubWococo { type RuntimeCall = crate::RuntimeCall; fn is_message_accepted(origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool { - log::info!("[BridgeHubWococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); + log::info!(target: crate::LOG_TARGET, "[BridgeHubWococo::ThisChainWithMessages] is_message_accepted - origin: {:?}, lane: {:?}", origin, lane); lane == &DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO } fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { log::info!( + target: crate::LOG_TARGET, "[BridgeHubWococo::ThisChainWithMessages] maximal_pending_messages_at_outbound_lane" ); MessageNonce::MAX / 2 diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index ba0c67cb2b0..7d4f003ac2b 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -83,6 +83,8 @@ use parachains_common::{ use xcm::latest::prelude::BodyId; use xcm_executor::XcmExecutor; +pub const LOG_TARGET: &str = "runtime::bridge-hub"; + /// Balance of an account. pub type Balance = u128; @@ -544,7 +546,6 @@ impl pallet_bridge_messages::Config for Run bp_bridge_hub_rococo::BridgeHubRococo, OnBridgeHubRococoBlobDispatcher, >; - } /// Add XCM messages support for BrigdeHubWococo to support Wococo->Rococo XCM messages @@ -787,9 +788,19 @@ impl_runtime_apis! { } } - // TODO: add here other directions - // This exposed by BridgeHubRococo + impl runtime_api::FromBridgeHubWococoInboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, + ) -> Vec { + bridge_runtime_common::messages_api::inbound_message_details::< + Runtime, + WithBridgeHubWococoMessagesInstance, + >(lane, messages) + } + } + impl runtime_api::ToBridgeHubWococoOutboundLaneApi for Runtime { fn message_details( lane: bp_messages::LaneId, @@ -816,10 +827,23 @@ impl_runtime_apis! { } } + impl runtime_api::ToBridgeHubRococoOutboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + begin: bp_messages::MessageNonce, + end: bp_messages::MessageNonce, + ) -> Vec { + bridge_runtime_common::messages_api::outbound_message_details::< + Runtime, + WithBridgeHubRococoMessagesInstance, + >(lane, begin, end) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade() -> (Weight, Weight) { - log::info!("try-runtime::on_runtime_upgrade parachain-template."); + log::info!(target: LOG_TARGET, "try-runtime::on_runtime_upgrade parachain-template."); let weight = Executive::try_runtime_upgrade().unwrap(); (weight, RuntimeBlockWeights::get().max_block) } diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs index 6706efe1757..7b79e1103bc 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs @@ -214,7 +214,12 @@ impl XcmWeightInfo for BridgeHubXcmWeight { Weight::MAX.ref_time() } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> XCMWeight { - Weight::MAX.ref_time() + log::error!( + target: crate::LOG_TARGET, + "TODO: Calling weight: export_message -> triggers unpaid_execution -> need fix here!" + ); + // TODO:check-parameter - add correct weight also add it to the polkadot gav-xcm-v3 + XcmGeneric::::unpaid_execution().ref_time() } fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> XCMWeight { Weight::MAX.ref_time() diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index c825e780716..fecffebaedf 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -29,6 +29,7 @@ use frame_support::{ }; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; +use sp_core::Get; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, @@ -41,11 +42,24 @@ use xcm_executor::{traits::ExportXcm, XcmExecutor}; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); - // TODO: hack: hardcoded Polkadot? - pub const RelayNetwork: NetworkId = NetworkId::Rococo; pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); - pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); +} + +pub struct RelayNetwork; +impl Get> for RelayNetwork { + fn get() -> Option { + Some(Self::get()) + } +} +impl Get for RelayNetwork { + fn get() -> NetworkId { + match u32::from(ParachainInfo::parachain_id()) { + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID => NetworkId::Rococo, + bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID => NetworkId::Wococo, + para_id => unreachable!("Not supported for para_id: {}", para_id), + } + } } /// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used @@ -107,71 +121,6 @@ match_types! { }; } -//TODO: move DenyThenTry to polkadot's xcm module. -// /// Deny executing the xcm message if it matches any of the Deny filter regardless of anything else. -// /// If it passes the Deny, and matches one of the Allow cases then it is let through. -// pub struct DenyThenTry(PhantomData, PhantomData) -// where -// Deny: ShouldExecute, -// Allow: ShouldExecute; -// -// impl ShouldExecute for DenyThenTry -// where -// Deny: ShouldExecute, -// Allow: ShouldExecute, -// { -// fn should_execute( -// origin: &MultiLocation, -// message: &mut Xcm, -// max_weight: Weight, -// weight_credit: &mut Weight, -// ) -> Result<(), ()> { -// Deny::should_execute(origin, message, max_weight, weight_credit)?; -// Allow::should_execute(origin, message, max_weight, weight_credit) -// } -// } - -// TODO: hacked -// See issue #5233 -// pub struct DenyReserveTransferToRelayChain; -// impl ShouldExecute for DenyReserveTransferToRelayChain { -// fn should_execute( -// origin: &MultiLocation, -// message: &mut Xcm, -// _max_weight: Weight, -// _weight_credit: &mut Weight, -// ) -> Result<(), ()> { -// if message.0.iter().any(|inst| { -// matches!( -// inst, -// InitiateReserveWithdraw { -// reserve: MultiLocation { parents: 1, interior: Here }, -// .. -// } | DepositReserveAsset { dest: MultiLocation { parents: 1, interior: Here }, .. } | -// TransferReserveAsset { -// dest: MultiLocation { parents: 1, interior: Here }, -// .. -// } -// ) -// }) { -// return Err(()) // Deny -// } -// -// // An unexpected reserve transfer has arrived from the Relay Chain. Generally, `IsReserve` -// // should not allow this, but we just log it here. -// if matches!(origin, MultiLocation { parents: 1, interior: Here }) && -// message.0.iter().any(|inst| matches!(inst, ReserveAssetDeposited { .. })) -// { -// log::warn!( -// target: "xcm::barriers", -// "Unexpected ReserveAssetDeposited from the Relay Chain", -// ); -// } -// // Permit everything else -// Ok(()) -// } -// } - match_types! { pub type ParentOrParentsUnitPlurality: impl Contains = { MultiLocation { parents: 1, interior: Here } | diff --git a/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/polkadot-parachain/src/chain_spec/bridge_hubs.rs index bdb1326f0f0..96072825f00 100644 --- a/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -96,7 +96,7 @@ impl BridgeHubRuntimeType { "Wococo BridgeHub", ChainType::Live, "wococo", - ParaId::new(1013), + ParaId::new(1014), None, None, ))) @@ -110,7 +110,7 @@ impl BridgeHubRuntimeType { "Wococo BridgeHub Local", ChainType::Local, "wococo-local", - ParaId::new(1013), + ParaId::new(1014), Some("Alice".to_string()), Some("Bob".to_string()), ))), From 7631b46aa4cdb3054deaa435b3f1eab1ccf45e41 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 24 Nov 2022 10:02:15 +0100 Subject: [PATCH 91/91] Squashed 'bridges/' content from commit 80dfb3943 git-subtree-dir: bridges git-subtree-split: 80dfb3943d34d9a66842a0338d5c3acb65a36512 --- .config/lingua.dic | 242 + .config/spellcheck.toml | 13 + .dockerignore | 1 + .editorconfig | 19 + .github/dependabot.yml | 42 + .gitignore | 26 + .gitlab-ci.yml | 303 + .maintain/millau-weight-template.hbs | 106 + CODEOWNERS | 21 + CODE_OF_CONDUCT.md | 80 + Cargo.lock | 13268 ++++++++++++++++ Cargo.toml | 263 + Dockerfile | 72 + LICENSE | 675 + README.md | 252 + bin/.keep | 0 bin/millau/node/Cargo.toml | 59 + bin/millau/node/build.rs | 23 + bin/millau/node/src/chain_spec.rs | 235 + bin/millau/node/src/cli.rs | 72 + bin/millau/node/src/command.rs | 159 + bin/millau/node/src/lib.rs | 32 + bin/millau/node/src/main.rs | 30 + bin/millau/node/src/service.rs | 461 + bin/millau/runtime/Cargo.toml | 145 + bin/millau/runtime/build.rs | 25 + bin/millau/runtime/src/lib.rs | 1119 ++ bin/millau/runtime/src/rialto_messages.rs | 244 + .../runtime/src/rialto_parachain_messages.rs | 167 + bin/millau/runtime/src/xcm_config.rs | 326 + bin/rialto-parachain/node/Cargo.toml | 84 + bin/rialto-parachain/node/build.rs | 22 + bin/rialto-parachain/node/src/chain_spec.rs | 198 + bin/rialto-parachain/node/src/cli.rs | 140 + bin/rialto-parachain/node/src/command.rs | 446 + bin/rialto-parachain/node/src/lib.rs | 18 + bin/rialto-parachain/node/src/main.rs | 29 + bin/rialto-parachain/node/src/service.rs | 540 + bin/rialto-parachain/runtime/Cargo.toml | 136 + bin/rialto-parachain/runtime/build.rs | 25 + bin/rialto-parachain/runtime/src/lib.rs | 904 ++ .../runtime/src/millau_messages.rs | 163 + bin/rialto/node/Cargo.toml | 49 + bin/rialto/node/build.rs | 23 + bin/rialto/node/src/chain_spec.rs | 286 + bin/rialto/node/src/cli.rs | 87 + bin/rialto/node/src/command.rs | 222 + bin/rialto/node/src/main.rs | 28 + bin/rialto/runtime/Cargo.toml | 145 + bin/rialto/runtime/build.rs | 25 + bin/rialto/runtime/src/lib.rs | 1006 ++ bin/rialto/runtime/src/millau_messages.rs | 242 + bin/rialto/runtime/src/parachains.rs | 210 + bin/rialto/runtime/src/xcm_config.rs | 285 + bin/runtime-common/Cargo.toml | 84 + bin/runtime-common/src/integrity.rs | 291 + bin/runtime-common/src/lib.rs | 220 + bin/runtime-common/src/messages.rs | 1202 ++ bin/runtime-common/src/messages_api.rs | 66 + .../src/messages_benchmarking.rs | 157 + bin/runtime-common/src/messages_extension.rs | 231 + bin/runtime-common/src/messages_generation.rs | 146 + .../src/parachains_benchmarking.rs | 85 + ci.Dockerfile | 53 + deny.toml | 206 + deployments/README.md | 261 + .../bridges/common/generate_messages.sh | 64 + ...y-millau-to-rialto-messages-dashboard.json | 1153 ++ ...y-rialto-to-millau-messages-dashboard.json | 1145 ++ .../rialto-millau-maintenance-dashboard.json | 791 + .../dashboard/prometheus/targets.yml | 4 + .../bridges/rialto-millau/docker-compose.yml | 110 + ...ay-messages-millau-to-rialto-entrypoint.sh | 16 + ...ay-messages-rialto-to-millau-entrypoint.sh | 16 + ...messages-to-millau-generator-entrypoint.sh | 26 + ...messages-to-rialto-generator-entrypoint.sh | 26 + ...ssages-to-rialto-resubmitter-entrypoint.sh | 25 + .../relay-millau-rialto-entrypoint.sh | 36 + ...o-rialto-parachain-messages-dashboard.json | 910 ++ ...arachain-to-millau-messages-dashboard.json | 908 ++ ...arachain-millau-maintenance-dashboard.json | 713 + .../dashboard/prometheus/targets.yml | 3 + .../docker-compose.yml | 90 + ...messages-to-millau-generator-entrypoint.sh | 27 + ...o-rialto-parachain-generator-entrypoint.sh | 26 + ...rialto-parachain-resubmitter-entrypoint.sh | 25 + ...elay-millau-rialto-parachain-entrypoint.sh | 42 + ...y-westend-to-millau-headers-dashboard.json | 781 + ...estend-to-millau-parachains-dashboard.json | 200 + .../dashboard/prometheus/targets.yml | 3 + .../bridges/westend-millau/docker-compose.yml | 72 + ...ay-headers-westend-to-millau-entrypoint.sh | 26 + ...parachains-westend-to-millau-entrypoint.sh | 16 + .../local-scripts/bridge-entrypoint.sh | 7 + .../relay-messages-millau-to-rialto.sh | 20 + .../relay-messages-rialto-to-millau.sh | 20 + .../local-scripts/relay-millau-to-rialto.sh | 29 + .../local-scripts/relay-rialto-to-millau.sh | 27 + deployments/local-scripts/run-millau-node.sh | 11 + deployments/local-scripts/run-rialto-node.sh | 11 + deployments/local-scripts/run-westend-node.sh | 14 + .../monitoring/GrafanaMatrix.Dockerfile | 15 + deployments/monitoring/disabled.yml | 15 + deployments/monitoring/docker-compose.yml | 34 + .../monitoring/grafana-matrix/config.yml | 47 + .../monitoring/grafana/dashboards/nodes.json | 200 + .../dashboards/grafana-dashboard.yaml | 6 + .../datasources/grafana-datasource.yaml | 16 + .../notifiers/grafana-notifier.yaml | 15 + .../monitoring/prometheus/prometheus.yml | 7 + .../dashboard/grafana/beefy-dashboard.json | 585 + .../dashboard/prometheus/millau-targets.yml | 2 + .../dashboard/prometheus/rialto-targets.yml | 2 + .../rialto-chainspec-exporter-entrypoint.sh | 14 + .../rialto-parachain-registrar-entrypoint.sh | 11 + deployments/networks/millau.yml | 101 + deployments/networks/rialto-parachain.yml | 90 + deployments/networks/rialto.yml | 118 + deployments/reverse-proxy/README.md | 15 + deployments/reverse-proxy/docker-compose.yml | 45 + deployments/run.sh | 210 + deployments/types-millau.json | 192 + deployments/types-rialto.json | 192 + deployments/types/build.sh | 20 + deployments/types/common.json | 124 + deployments/types/millau.json | 17 + deployments/types/rialto-millau.json | 56 + deployments/types/rialto.json | 17 + deployments/ui/README.md | 23 + deployments/ui/docker-compose.yml | 13 + docs/high-level-overview.md | 165 + docs/high-level.html | 55 + docs/plan.md | 22 + docs/scenario1.html | 47 + docs/send-message.md | 131 + docs/testing-scenarios.md | 221 + fuzz/storage-proof/Cargo.lock | 2663 ++++ fuzz/storage-proof/Cargo.toml | 25 + fuzz/storage-proof/README.md | 34 + fuzz/storage-proof/src/main.rs | 77 + modules/beefy/Cargo.toml | 54 + modules/beefy/src/lib.rs | 644 + modules/beefy/src/mock.rs | 226 + modules/beefy/src/mock_chain.rs | 299 + modules/beefy/src/utils.rs | 363 + modules/grandpa/Cargo.toml | 59 + modules/grandpa/src/benchmarking.rs | 132 + modules/grandpa/src/extension.rs | 116 + modules/grandpa/src/lib.rs | 1258 ++ modules/grandpa/src/mock.rs | 131 + modules/grandpa/src/storage_types.rs | 66 + modules/grandpa/src/weights.rs | 79 + modules/messages/Cargo.toml | 52 + modules/messages/README.md | 424 + modules/messages/src/benchmarking.rs | 423 + modules/messages/src/inbound_lane.rs | 545 + modules/messages/src/lib.rs | 1928 +++ modules/messages/src/mock.rs | 418 + modules/messages/src/outbound_lane.rs | 436 + modules/messages/src/weights.rs | 163 + modules/messages/src/weights_ext.rs | 289 + modules/parachains/Cargo.toml | 55 + modules/parachains/src/benchmarking.rs | 106 + modules/parachains/src/extension.rs | 166 + modules/parachains/src/lib.rs | 1403 ++ modules/parachains/src/mock.rs | 194 + modules/parachains/src/weights.rs | 101 + modules/parachains/src/weights_ext.rs | 107 + modules/relayers/Cargo.toml | 51 + modules/relayers/src/benchmarking.rs | 40 + modules/relayers/src/lib.rs | 197 + modules/relayers/src/mock.rs | 152 + modules/relayers/src/payment_adapter.rs | 166 + modules/relayers/src/weights.rs | 75 + modules/shift-session-manager/Cargo.toml | 35 + modules/shift-session-manager/src/lib.rs | 268 + primitives/beefy/Cargo.toml | 48 + primitives/beefy/src/lib.rs | 152 + primitives/chain-bridge-hub-rococo/Cargo.toml | 37 + primitives/chain-bridge-hub-rococo/src/lib.rs | 107 + primitives/chain-bridge-hub-wococo/Cargo.toml | 32 + primitives/chain-bridge-hub-wococo/src/lib.rs | 78 + primitives/chain-kusama/Cargo.toml | 26 + primitives/chain-kusama/src/lib.rs | 30 + primitives/chain-millau/Cargo.toml | 56 + primitives/chain-millau/src/lib.rs | 220 + primitives/chain-millau/src/millau_hash.rs | 58 + primitives/chain-polkadot/Cargo.toml | 25 + primitives/chain-polkadot/src/lib.rs | 30 + primitives/chain-rialto-parachain/Cargo.toml | 36 + primitives/chain-rialto-parachain/src/lib.rs | 146 + primitives/chain-rialto/Cargo.toml | 36 + primitives/chain-rialto/src/lib.rs | 180 + primitives/chain-rococo/Cargo.toml | 26 + primitives/chain-rococo/src/lib.rs | 53 + primitives/chain-westend/Cargo.toml | 26 + primitives/chain-westend/src/lib.rs | 55 + primitives/chain-wococo/Cargo.toml | 26 + primitives/chain-wococo/src/lib.rs | 34 + primitives/header-chain/Cargo.toml | 47 + primitives/header-chain/src/justification.rs | 227 + primitives/header-chain/src/lib.rs | 147 + primitives/header-chain/src/storage_keys.rs | 78 + .../tests/implementation_match.rs | 328 + .../header-chain/tests/justification.rs | 192 + primitives/messages/Cargo.toml | 39 + primitives/messages/src/lib.rs | 415 + primitives/messages/src/source_chain.rs | 217 + primitives/messages/src/storage_keys.rs | 128 + primitives/messages/src/target_chain.rs | 163 + primitives/parachains/Cargo.toml | 32 + primitives/parachains/src/lib.rs | 90 + primitives/polkadot-core/Cargo.toml | 45 + primitives/polkadot-core/src/lib.rs | 385 + primitives/polkadot-core/src/parachains.rs | 100 + primitives/relayers/Cargo.toml | 27 + primitives/relayers/src/lib.rs | 45 + primitives/runtime/Cargo.toml | 48 + primitives/runtime/src/chain.rs | 347 + primitives/runtime/src/lib.rs | 508 + primitives/runtime/src/messages.rs | 37 + primitives/runtime/src/storage_proof.rs | 174 + primitives/runtime/src/storage_types.rs | 90 + primitives/test-utils/Cargo.toml | 32 + primitives/test-utils/src/keyring.rs | 94 + primitives/test-utils/src/lib.rs | 345 + relays/bin-substrate/Cargo.toml | 75 + ...ub_rococo_messages_to_bridge_hub_wococo.rs | 64 + ...ub_wococo_messages_to_bridge_hub_rococo.rs | 64 + relays/bin-substrate/src/chains/millau.rs | 63 + .../src/chains/millau_headers_to_rialto.rs | 57 + .../millau_headers_to_rialto_parachain.rs | 76 + .../src/chains/millau_messages_to_rialto.rs | 44 + .../millau_messages_to_rialto_parachain.rs | 44 + relays/bin-substrate/src/chains/mod.rs | 125 + relays/bin-substrate/src/chains/rialto.rs | 55 + .../src/chains/rialto_headers_to_millau.rs | 57 + .../src/chains/rialto_messages_to_millau.rs | 44 + .../src/chains/rialto_parachain.rs | 57 + .../rialto_parachain_messages_to_millau.rs | 44 + .../src/chains/rialto_parachains_to_millau.rs | 74 + relays/bin-substrate/src/chains/rococo.rs | 42 + .../rococo_headers_to_bridge_hub_wococo.rs | 53 + .../rococo_parachains_to_bridge_hub_wococo.rs | 78 + relays/bin-substrate/src/chains/westend.rs | 47 + .../src/chains/westend_headers_to_millau.rs | 51 + .../chains/westend_parachains_to_millau.rs | 66 + relays/bin-substrate/src/chains/wococo.rs | 42 + .../wococo_headers_to_bridge_hub_rococo.rs | 53 + .../wococo_parachains_to_bridge_hub_rococo.rs | 78 + relays/bin-substrate/src/cli/bridge.rs | 105 + relays/bin-substrate/src/cli/chain_schema.rs | 409 + .../bin-substrate/src/cli/encode_message.rs | 145 + relays/bin-substrate/src/cli/init_bridge.rs | 210 + relays/bin-substrate/src/cli/mod.rs | 300 + .../src/cli/register_parachain.rs | 359 + relays/bin-substrate/src/cli/relay_headers.rs | 128 + .../src/cli/relay_headers_and_messages/mod.rs | 682 + .../parachain_to_parachain.rs | 281 + .../relay_to_parachain.rs | 254 + .../relay_to_relay.rs | 194 + .../bin-substrate/src/cli/relay_messages.rs | 117 + .../bin-substrate/src/cli/relay_parachains.rs | 139 + .../src/cli/resubmit_transactions.rs | 561 + relays/bin-substrate/src/cli/send_message.rs | 195 + relays/bin-substrate/src/main.rs | 31 + relays/client-bridge-hub-rococo/Cargo.toml | 29 + relays/client-bridge-hub-rococo/src/lib.rs | 165 + .../src/runtime_wrapper.rs | 219 + relays/client-bridge-hub-wococo/Cargo.toml | 22 + relays/client-bridge-hub-wococo/src/lib.rs | 165 + .../src/runtime_wrapper.rs | 28 + relays/client-kusama/Cargo.toml | 19 + relays/client-kusama/src/lib.rs | 73 + relays/client-millau/Cargo.toml | 25 + relays/client-millau/src/lib.rs | 202 + relays/client-polkadot/Cargo.toml | 19 + relays/client-polkadot/src/lib.rs | 74 + relays/client-rialto-parachain/Cargo.toml | 25 + relays/client-rialto-parachain/src/lib.rs | 165 + relays/client-rialto/Cargo.toml | 25 + relays/client-rialto/src/lib.rs | 205 + relays/client-rococo/Cargo.toml | 19 + relays/client-rococo/src/lib.rs | 78 + relays/client-substrate/Cargo.toml | 49 + relays/client-substrate/src/chain.rs | 235 + relays/client-substrate/src/client.rs | 742 + relays/client-substrate/src/error.rs | 86 + relays/client-substrate/src/guard.rs | 372 + relays/client-substrate/src/lib.rs | 91 + .../src/metrics/float_storage_value.rs | 133 + relays/client-substrate/src/metrics/mod.rs | 21 + relays/client-substrate/src/rpc.rs | 170 + relays/client-substrate/src/sync_header.rs | 61 + relays/client-substrate/src/test_chain.rs | 68 + .../src/transaction_tracker.rs | 447 + relays/client-westend/Cargo.toml | 19 + relays/client-westend/src/lib.rs | 119 + relays/client-wococo/Cargo.toml | 18 + relays/client-wococo/src/lib.rs | 78 + relays/finality/Cargo.toml | 20 + relays/finality/src/finality_loop.rs | 761 + relays/finality/src/finality_loop_tests.rs | 598 + relays/finality/src/lib.rs | 61 + relays/finality/src/sync_loop_metrics.rs | 86 + relays/lib-substrate-relay/Cargo.toml | 57 + relays/lib-substrate-relay/src/error.rs | 63 + .../src/finality/engine.rs | 259 + .../src/finality/guards.rs | 48 + .../src/finality/initialize.rs | 137 + .../lib-substrate-relay/src/finality/mod.rs | 204 + .../src/finality/source.rs | 175 + .../src/finality/target.rs | 135 + relays/lib-substrate-relay/src/lib.rs | 98 + .../lib-substrate-relay/src/messages_lane.rs | 449 + .../src/messages_metrics.rs | 129 + .../src/messages_source.rs | 736 + .../src/messages_target.rs | 298 + .../src/on_demand/headers.rs | 457 + .../lib-substrate-relay/src/on_demand/mod.rs | 35 + .../src/on_demand/parachains.rs | 704 + .../lib-substrate-relay/src/parachains/mod.rs | 108 + .../src/parachains/source.rs | 188 + .../src/parachains/target.rs | 201 + relays/messages/Cargo.toml | 24 + relays/messages/src/lib.rs | 37 + relays/messages/src/message_lane.rs | 71 + relays/messages/src/message_lane_loop.rs | 1136 ++ relays/messages/src/message_race_delivery.rs | 984 ++ relays/messages/src/message_race_limits.rs | 200 + relays/messages/src/message_race_loop.rs | 663 + relays/messages/src/message_race_receiving.rs | 228 + relays/messages/src/message_race_strategy.rs | 517 + relays/messages/src/metrics.rs | 139 + relays/parachains/Cargo.toml | 24 + relays/parachains/src/lib.rs | 30 + relays/parachains/src/parachains_loop.rs | 1255 ++ .../parachains/src/parachains_loop_metrics.rs | 98 + relays/utils/Cargo.toml | 32 + relays/utils/src/error.rs | 46 + relays/utils/src/initialize.rs | 136 + relays/utils/src/lib.rs | 312 + relays/utils/src/metrics.rs | 165 + relays/utils/src/metrics/float_json_value.rs | 147 + relays/utils/src/metrics/global.rs | 118 + relays/utils/src/relay_loop.rs | 269 + rustfmt.toml | 24 + scripts/add_license.sh | 22 + scripts/build-containers.sh | 7 + scripts/ci-cache.sh | 19 + scripts/dump-logs.sh | 46 + scripts/license_header | 16 + scripts/send-message-from-millau-rialto.sh | 33 + scripts/send-message-from-rialto-millau.sh | 33 + scripts/update-weights-setup.sh | 33 + scripts/update-weights.sh | 55 + scripts/update_substrate.sh | 10 + 357 files changed, 79713 insertions(+) create mode 100644 .config/lingua.dic create mode 100644 .config/spellcheck.toml create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .github/dependabot.yml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .maintain/millau-weight-template.hbs create mode 100644 CODEOWNERS create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bin/.keep create mode 100644 bin/millau/node/Cargo.toml create mode 100644 bin/millau/node/build.rs create mode 100644 bin/millau/node/src/chain_spec.rs create mode 100644 bin/millau/node/src/cli.rs create mode 100644 bin/millau/node/src/command.rs create mode 100644 bin/millau/node/src/lib.rs create mode 100644 bin/millau/node/src/main.rs create mode 100644 bin/millau/node/src/service.rs create mode 100644 bin/millau/runtime/Cargo.toml create mode 100644 bin/millau/runtime/build.rs create mode 100644 bin/millau/runtime/src/lib.rs create mode 100644 bin/millau/runtime/src/rialto_messages.rs create mode 100644 bin/millau/runtime/src/rialto_parachain_messages.rs create mode 100644 bin/millau/runtime/src/xcm_config.rs create mode 100644 bin/rialto-parachain/node/Cargo.toml create mode 100644 bin/rialto-parachain/node/build.rs create mode 100644 bin/rialto-parachain/node/src/chain_spec.rs create mode 100644 bin/rialto-parachain/node/src/cli.rs create mode 100644 bin/rialto-parachain/node/src/command.rs create mode 100644 bin/rialto-parachain/node/src/lib.rs create mode 100644 bin/rialto-parachain/node/src/main.rs create mode 100644 bin/rialto-parachain/node/src/service.rs create mode 100644 bin/rialto-parachain/runtime/Cargo.toml create mode 100644 bin/rialto-parachain/runtime/build.rs create mode 100644 bin/rialto-parachain/runtime/src/lib.rs create mode 100644 bin/rialto-parachain/runtime/src/millau_messages.rs create mode 100644 bin/rialto/node/Cargo.toml create mode 100644 bin/rialto/node/build.rs create mode 100644 bin/rialto/node/src/chain_spec.rs create mode 100644 bin/rialto/node/src/cli.rs create mode 100644 bin/rialto/node/src/command.rs create mode 100644 bin/rialto/node/src/main.rs create mode 100644 bin/rialto/runtime/Cargo.toml create mode 100644 bin/rialto/runtime/build.rs create mode 100644 bin/rialto/runtime/src/lib.rs create mode 100644 bin/rialto/runtime/src/millau_messages.rs create mode 100644 bin/rialto/runtime/src/parachains.rs create mode 100644 bin/rialto/runtime/src/xcm_config.rs create mode 100644 bin/runtime-common/Cargo.toml create mode 100644 bin/runtime-common/src/integrity.rs create mode 100644 bin/runtime-common/src/lib.rs create mode 100644 bin/runtime-common/src/messages.rs create mode 100644 bin/runtime-common/src/messages_api.rs create mode 100644 bin/runtime-common/src/messages_benchmarking.rs create mode 100644 bin/runtime-common/src/messages_extension.rs create mode 100644 bin/runtime-common/src/messages_generation.rs create mode 100644 bin/runtime-common/src/parachains_benchmarking.rs create mode 100644 ci.Dockerfile create mode 100644 deny.toml create mode 100644 deployments/README.md create mode 100644 deployments/bridges/common/generate_messages.sh create mode 100644 deployments/bridges/rialto-millau/dashboard/grafana/relay-millau-to-rialto-messages-dashboard.json create mode 100644 deployments/bridges/rialto-millau/dashboard/grafana/relay-rialto-to-millau-messages-dashboard.json create mode 100644 deployments/bridges/rialto-millau/dashboard/grafana/rialto-millau-maintenance-dashboard.json create mode 100644 deployments/bridges/rialto-millau/dashboard/prometheus/targets.yml create mode 100644 deployments/bridges/rialto-millau/docker-compose.yml create mode 100755 deployments/bridges/rialto-millau/entrypoints/relay-messages-millau-to-rialto-entrypoint.sh create mode 100755 deployments/bridges/rialto-millau/entrypoints/relay-messages-rialto-to-millau-entrypoint.sh create mode 100755 deployments/bridges/rialto-millau/entrypoints/relay-messages-to-millau-generator-entrypoint.sh create mode 100755 deployments/bridges/rialto-millau/entrypoints/relay-messages-to-rialto-generator-entrypoint.sh create mode 100755 deployments/bridges/rialto-millau/entrypoints/relay-messages-to-rialto-resubmitter-entrypoint.sh create mode 100755 deployments/bridges/rialto-millau/entrypoints/relay-millau-rialto-entrypoint.sh create mode 100644 deployments/bridges/rialto-parachain-millau/dashboard/grafana/relay-millau-to-rialto-parachain-messages-dashboard.json create mode 100644 deployments/bridges/rialto-parachain-millau/dashboard/grafana/relay-rialto-parachain-to-millau-messages-dashboard.json create mode 100644 deployments/bridges/rialto-parachain-millau/dashboard/grafana/rialto-parachain-millau-maintenance-dashboard.json create mode 100644 deployments/bridges/rialto-parachain-millau/dashboard/prometheus/targets.yml create mode 100644 deployments/bridges/rialto-parachain-millau/docker-compose.yml create mode 100755 deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-millau-generator-entrypoint.sh create mode 100755 deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-rialto-parachain-generator-entrypoint.sh create mode 100755 deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-rialto-parachain-resubmitter-entrypoint.sh create mode 100755 deployments/bridges/rialto-parachain-millau/entrypoints/relay-millau-rialto-parachain-entrypoint.sh create mode 100644 deployments/bridges/westend-millau/dashboard/grafana/relay-westend-to-millau-headers-dashboard.json create mode 100644 deployments/bridges/westend-millau/dashboard/grafana/relay-westend-to-millau-parachains-dashboard.json create mode 100644 deployments/bridges/westend-millau/dashboard/prometheus/targets.yml create mode 100644 deployments/bridges/westend-millau/docker-compose.yml create mode 100755 deployments/bridges/westend-millau/entrypoints/relay-headers-westend-to-millau-entrypoint.sh create mode 100755 deployments/bridges/westend-millau/entrypoints/relay-parachains-westend-to-millau-entrypoint.sh create mode 100755 deployments/local-scripts/bridge-entrypoint.sh create mode 100755 deployments/local-scripts/relay-messages-millau-to-rialto.sh create mode 100755 deployments/local-scripts/relay-messages-rialto-to-millau.sh create mode 100755 deployments/local-scripts/relay-millau-to-rialto.sh create mode 100755 deployments/local-scripts/relay-rialto-to-millau.sh create mode 100755 deployments/local-scripts/run-millau-node.sh create mode 100755 deployments/local-scripts/run-rialto-node.sh create mode 100755 deployments/local-scripts/run-westend-node.sh create mode 100644 deployments/monitoring/GrafanaMatrix.Dockerfile create mode 100644 deployments/monitoring/disabled.yml create mode 100644 deployments/monitoring/docker-compose.yml create mode 100644 deployments/monitoring/grafana-matrix/config.yml create mode 100644 deployments/monitoring/grafana/dashboards/nodes.json create mode 100644 deployments/monitoring/grafana/provisioning/dashboards/grafana-dashboard.yaml create mode 100644 deployments/monitoring/grafana/provisioning/datasources/grafana-datasource.yaml create mode 100644 deployments/monitoring/grafana/provisioning/notifiers/grafana-notifier.yaml create mode 100644 deployments/monitoring/prometheus/prometheus.yml create mode 100644 deployments/networks/dashboard/grafana/beefy-dashboard.json create mode 100644 deployments/networks/dashboard/prometheus/millau-targets.yml create mode 100644 deployments/networks/dashboard/prometheus/rialto-targets.yml create mode 100755 deployments/networks/entrypoints/rialto-chainspec-exporter-entrypoint.sh create mode 100755 deployments/networks/entrypoints/rialto-parachain-registrar-entrypoint.sh create mode 100644 deployments/networks/millau.yml create mode 100644 deployments/networks/rialto-parachain.yml create mode 100644 deployments/networks/rialto.yml create mode 100644 deployments/reverse-proxy/README.md create mode 100644 deployments/reverse-proxy/docker-compose.yml create mode 100755 deployments/run.sh create mode 100644 deployments/types-millau.json create mode 100644 deployments/types-rialto.json create mode 100755 deployments/types/build.sh create mode 100644 deployments/types/common.json create mode 100644 deployments/types/millau.json create mode 100644 deployments/types/rialto-millau.json create mode 100644 deployments/types/rialto.json create mode 100644 deployments/ui/README.md create mode 100644 deployments/ui/docker-compose.yml create mode 100644 docs/high-level-overview.md create mode 100644 docs/high-level.html create mode 100644 docs/plan.md create mode 100644 docs/scenario1.html create mode 100644 docs/send-message.md create mode 100644 docs/testing-scenarios.md create mode 100644 fuzz/storage-proof/Cargo.lock create mode 100644 fuzz/storage-proof/Cargo.toml create mode 100644 fuzz/storage-proof/README.md create mode 100644 fuzz/storage-proof/src/main.rs create mode 100644 modules/beefy/Cargo.toml create mode 100644 modules/beefy/src/lib.rs create mode 100644 modules/beefy/src/mock.rs create mode 100644 modules/beefy/src/mock_chain.rs create mode 100644 modules/beefy/src/utils.rs create mode 100644 modules/grandpa/Cargo.toml create mode 100644 modules/grandpa/src/benchmarking.rs create mode 100644 modules/grandpa/src/extension.rs create mode 100644 modules/grandpa/src/lib.rs create mode 100644 modules/grandpa/src/mock.rs create mode 100644 modules/grandpa/src/storage_types.rs create mode 100644 modules/grandpa/src/weights.rs create mode 100644 modules/messages/Cargo.toml create mode 100644 modules/messages/README.md create mode 100644 modules/messages/src/benchmarking.rs create mode 100644 modules/messages/src/inbound_lane.rs create mode 100644 modules/messages/src/lib.rs create mode 100644 modules/messages/src/mock.rs create mode 100644 modules/messages/src/outbound_lane.rs create mode 100644 modules/messages/src/weights.rs create mode 100644 modules/messages/src/weights_ext.rs create mode 100644 modules/parachains/Cargo.toml create mode 100644 modules/parachains/src/benchmarking.rs create mode 100644 modules/parachains/src/extension.rs create mode 100644 modules/parachains/src/lib.rs create mode 100644 modules/parachains/src/mock.rs create mode 100644 modules/parachains/src/weights.rs create mode 100644 modules/parachains/src/weights_ext.rs create mode 100644 modules/relayers/Cargo.toml create mode 100644 modules/relayers/src/benchmarking.rs create mode 100644 modules/relayers/src/lib.rs create mode 100644 modules/relayers/src/mock.rs create mode 100644 modules/relayers/src/payment_adapter.rs create mode 100644 modules/relayers/src/weights.rs create mode 100644 modules/shift-session-manager/Cargo.toml create mode 100644 modules/shift-session-manager/src/lib.rs create mode 100644 primitives/beefy/Cargo.toml create mode 100644 primitives/beefy/src/lib.rs create mode 100644 primitives/chain-bridge-hub-rococo/Cargo.toml create mode 100644 primitives/chain-bridge-hub-rococo/src/lib.rs create mode 100644 primitives/chain-bridge-hub-wococo/Cargo.toml create mode 100644 primitives/chain-bridge-hub-wococo/src/lib.rs create mode 100644 primitives/chain-kusama/Cargo.toml create mode 100644 primitives/chain-kusama/src/lib.rs create mode 100644 primitives/chain-millau/Cargo.toml create mode 100644 primitives/chain-millau/src/lib.rs create mode 100644 primitives/chain-millau/src/millau_hash.rs create mode 100644 primitives/chain-polkadot/Cargo.toml create mode 100644 primitives/chain-polkadot/src/lib.rs create mode 100644 primitives/chain-rialto-parachain/Cargo.toml create mode 100644 primitives/chain-rialto-parachain/src/lib.rs create mode 100644 primitives/chain-rialto/Cargo.toml create mode 100644 primitives/chain-rialto/src/lib.rs create mode 100644 primitives/chain-rococo/Cargo.toml create mode 100644 primitives/chain-rococo/src/lib.rs create mode 100644 primitives/chain-westend/Cargo.toml create mode 100644 primitives/chain-westend/src/lib.rs create mode 100644 primitives/chain-wococo/Cargo.toml create mode 100644 primitives/chain-wococo/src/lib.rs create mode 100644 primitives/header-chain/Cargo.toml create mode 100644 primitives/header-chain/src/justification.rs create mode 100644 primitives/header-chain/src/lib.rs create mode 100644 primitives/header-chain/src/storage_keys.rs create mode 100644 primitives/header-chain/tests/implementation_match.rs create mode 100644 primitives/header-chain/tests/justification.rs create mode 100644 primitives/messages/Cargo.toml create mode 100644 primitives/messages/src/lib.rs create mode 100644 primitives/messages/src/source_chain.rs create mode 100644 primitives/messages/src/storage_keys.rs create mode 100644 primitives/messages/src/target_chain.rs create mode 100644 primitives/parachains/Cargo.toml create mode 100644 primitives/parachains/src/lib.rs create mode 100644 primitives/polkadot-core/Cargo.toml create mode 100644 primitives/polkadot-core/src/lib.rs create mode 100644 primitives/polkadot-core/src/parachains.rs create mode 100644 primitives/relayers/Cargo.toml create mode 100644 primitives/relayers/src/lib.rs create mode 100644 primitives/runtime/Cargo.toml create mode 100644 primitives/runtime/src/chain.rs create mode 100644 primitives/runtime/src/lib.rs create mode 100644 primitives/runtime/src/messages.rs create mode 100644 primitives/runtime/src/storage_proof.rs create mode 100644 primitives/runtime/src/storage_types.rs create mode 100644 primitives/test-utils/Cargo.toml create mode 100644 primitives/test-utils/src/keyring.rs create mode 100644 primitives/test-utils/src/lib.rs create mode 100644 relays/bin-substrate/Cargo.toml create mode 100644 relays/bin-substrate/src/chains/bridge_hub_rococo_messages_to_bridge_hub_wococo.rs create mode 100644 relays/bin-substrate/src/chains/bridge_hub_wococo_messages_to_bridge_hub_rococo.rs create mode 100644 relays/bin-substrate/src/chains/millau.rs create mode 100644 relays/bin-substrate/src/chains/millau_headers_to_rialto.rs create mode 100644 relays/bin-substrate/src/chains/millau_headers_to_rialto_parachain.rs create mode 100644 relays/bin-substrate/src/chains/millau_messages_to_rialto.rs create mode 100644 relays/bin-substrate/src/chains/millau_messages_to_rialto_parachain.rs create mode 100644 relays/bin-substrate/src/chains/mod.rs create mode 100644 relays/bin-substrate/src/chains/rialto.rs create mode 100644 relays/bin-substrate/src/chains/rialto_headers_to_millau.rs create mode 100644 relays/bin-substrate/src/chains/rialto_messages_to_millau.rs create mode 100644 relays/bin-substrate/src/chains/rialto_parachain.rs create mode 100644 relays/bin-substrate/src/chains/rialto_parachain_messages_to_millau.rs create mode 100644 relays/bin-substrate/src/chains/rialto_parachains_to_millau.rs create mode 100644 relays/bin-substrate/src/chains/rococo.rs create mode 100644 relays/bin-substrate/src/chains/rococo_headers_to_bridge_hub_wococo.rs create mode 100644 relays/bin-substrate/src/chains/rococo_parachains_to_bridge_hub_wococo.rs create mode 100644 relays/bin-substrate/src/chains/westend.rs create mode 100644 relays/bin-substrate/src/chains/westend_headers_to_millau.rs create mode 100644 relays/bin-substrate/src/chains/westend_parachains_to_millau.rs create mode 100644 relays/bin-substrate/src/chains/wococo.rs create mode 100644 relays/bin-substrate/src/chains/wococo_headers_to_bridge_hub_rococo.rs create mode 100644 relays/bin-substrate/src/chains/wococo_parachains_to_bridge_hub_rococo.rs create mode 100644 relays/bin-substrate/src/cli/bridge.rs create mode 100644 relays/bin-substrate/src/cli/chain_schema.rs create mode 100644 relays/bin-substrate/src/cli/encode_message.rs create mode 100644 relays/bin-substrate/src/cli/init_bridge.rs create mode 100644 relays/bin-substrate/src/cli/mod.rs create mode 100644 relays/bin-substrate/src/cli/register_parachain.rs create mode 100644 relays/bin-substrate/src/cli/relay_headers.rs create mode 100644 relays/bin-substrate/src/cli/relay_headers_and_messages/mod.rs create mode 100644 relays/bin-substrate/src/cli/relay_headers_and_messages/parachain_to_parachain.rs create mode 100644 relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_parachain.rs create mode 100644 relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_relay.rs create mode 100644 relays/bin-substrate/src/cli/relay_messages.rs create mode 100644 relays/bin-substrate/src/cli/relay_parachains.rs create mode 100644 relays/bin-substrate/src/cli/resubmit_transactions.rs create mode 100644 relays/bin-substrate/src/cli/send_message.rs create mode 100644 relays/bin-substrate/src/main.rs create mode 100644 relays/client-bridge-hub-rococo/Cargo.toml create mode 100644 relays/client-bridge-hub-rococo/src/lib.rs create mode 100644 relays/client-bridge-hub-rococo/src/runtime_wrapper.rs create mode 100644 relays/client-bridge-hub-wococo/Cargo.toml create mode 100644 relays/client-bridge-hub-wococo/src/lib.rs create mode 100644 relays/client-bridge-hub-wococo/src/runtime_wrapper.rs create mode 100644 relays/client-kusama/Cargo.toml create mode 100644 relays/client-kusama/src/lib.rs create mode 100644 relays/client-millau/Cargo.toml create mode 100644 relays/client-millau/src/lib.rs create mode 100644 relays/client-polkadot/Cargo.toml create mode 100644 relays/client-polkadot/src/lib.rs create mode 100644 relays/client-rialto-parachain/Cargo.toml create mode 100644 relays/client-rialto-parachain/src/lib.rs create mode 100644 relays/client-rialto/Cargo.toml create mode 100644 relays/client-rialto/src/lib.rs create mode 100644 relays/client-rococo/Cargo.toml create mode 100644 relays/client-rococo/src/lib.rs create mode 100644 relays/client-substrate/Cargo.toml create mode 100644 relays/client-substrate/src/chain.rs create mode 100644 relays/client-substrate/src/client.rs create mode 100644 relays/client-substrate/src/error.rs create mode 100644 relays/client-substrate/src/guard.rs create mode 100644 relays/client-substrate/src/lib.rs create mode 100644 relays/client-substrate/src/metrics/float_storage_value.rs create mode 100644 relays/client-substrate/src/metrics/mod.rs create mode 100644 relays/client-substrate/src/rpc.rs create mode 100644 relays/client-substrate/src/sync_header.rs create mode 100644 relays/client-substrate/src/test_chain.rs create mode 100644 relays/client-substrate/src/transaction_tracker.rs create mode 100644 relays/client-westend/Cargo.toml create mode 100644 relays/client-westend/src/lib.rs create mode 100644 relays/client-wococo/Cargo.toml create mode 100644 relays/client-wococo/src/lib.rs create mode 100644 relays/finality/Cargo.toml create mode 100644 relays/finality/src/finality_loop.rs create mode 100644 relays/finality/src/finality_loop_tests.rs create mode 100644 relays/finality/src/lib.rs create mode 100644 relays/finality/src/sync_loop_metrics.rs create mode 100644 relays/lib-substrate-relay/Cargo.toml create mode 100644 relays/lib-substrate-relay/src/error.rs create mode 100644 relays/lib-substrate-relay/src/finality/engine.rs create mode 100644 relays/lib-substrate-relay/src/finality/guards.rs create mode 100644 relays/lib-substrate-relay/src/finality/initialize.rs create mode 100644 relays/lib-substrate-relay/src/finality/mod.rs create mode 100644 relays/lib-substrate-relay/src/finality/source.rs create mode 100644 relays/lib-substrate-relay/src/finality/target.rs create mode 100644 relays/lib-substrate-relay/src/lib.rs create mode 100644 relays/lib-substrate-relay/src/messages_lane.rs create mode 100644 relays/lib-substrate-relay/src/messages_metrics.rs create mode 100644 relays/lib-substrate-relay/src/messages_source.rs create mode 100644 relays/lib-substrate-relay/src/messages_target.rs create mode 100644 relays/lib-substrate-relay/src/on_demand/headers.rs create mode 100644 relays/lib-substrate-relay/src/on_demand/mod.rs create mode 100644 relays/lib-substrate-relay/src/on_demand/parachains.rs create mode 100644 relays/lib-substrate-relay/src/parachains/mod.rs create mode 100644 relays/lib-substrate-relay/src/parachains/source.rs create mode 100644 relays/lib-substrate-relay/src/parachains/target.rs create mode 100644 relays/messages/Cargo.toml create mode 100644 relays/messages/src/lib.rs create mode 100644 relays/messages/src/message_lane.rs create mode 100644 relays/messages/src/message_lane_loop.rs create mode 100644 relays/messages/src/message_race_delivery.rs create mode 100644 relays/messages/src/message_race_limits.rs create mode 100644 relays/messages/src/message_race_loop.rs create mode 100644 relays/messages/src/message_race_receiving.rs create mode 100644 relays/messages/src/message_race_strategy.rs create mode 100644 relays/messages/src/metrics.rs create mode 100644 relays/parachains/Cargo.toml create mode 100644 relays/parachains/src/lib.rs create mode 100644 relays/parachains/src/parachains_loop.rs create mode 100644 relays/parachains/src/parachains_loop_metrics.rs create mode 100644 relays/utils/Cargo.toml create mode 100644 relays/utils/src/error.rs create mode 100644 relays/utils/src/initialize.rs create mode 100644 relays/utils/src/lib.rs create mode 100644 relays/utils/src/metrics.rs create mode 100644 relays/utils/src/metrics/float_json_value.rs create mode 100644 relays/utils/src/metrics/global.rs create mode 100644 relays/utils/src/relay_loop.rs create mode 100644 rustfmt.toml create mode 100755 scripts/add_license.sh create mode 100755 scripts/build-containers.sh create mode 100755 scripts/ci-cache.sh create mode 100755 scripts/dump-logs.sh create mode 100644 scripts/license_header create mode 100755 scripts/send-message-from-millau-rialto.sh create mode 100755 scripts/send-message-from-rialto-millau.sh create mode 100644 scripts/update-weights-setup.sh create mode 100755 scripts/update-weights.sh create mode 100755 scripts/update_substrate.sh diff --git a/.config/lingua.dic b/.config/lingua.dic new file mode 100644 index 00000000000..3e2d83b02dd --- /dev/null +++ b/.config/lingua.dic @@ -0,0 +1,242 @@ +90 + +&& +1KB +1MB +5MB += +API/SM +APIs +AccountId/MS +Apache-2.0/M +Autogenerated +BFT/M +BTC/S +Best/MS +BlockId +BlockNumber +BridgeStorage +BridgeHub +BridgeHubRococo +BridgeHubWococo +CLI/MS +Chain1 +Chain2 +ChainSpec +ChainTime +DOT/S +ERC-20 +Ethereum +FN +FinalizationError +GPL/M +GPLv3/M +GiB/S +Handler/MS +Hasher +HeaderA +HeaderId +InitiateChange +Instance1 +Instance2 +Instance42 +KSM/S +KYC/M +KeyPair +Kovan +Lane1 +Lane2 +Lane3 +LaneId +MIN_SIZE +MIT/M +MMR +MaxUnrewardedRelayerEntriesAtInboundLane +MaybeExtra +MaybeOrphan +Merklized +MessageNonce +MessageNonces +MessagePayload +MetricsParams +Millau/MS +OldHeader +OutboundMessages +PoA +PoV/MS +Pre +RLP +RPC/MS +Rialto/MS +RialtoParachain/MS +Relayer/MS +Runtime1 +Runtime2 +SIZE_FACTOR +SS58 +SS58Prefix +STALL_SYNC_TIMEOUT +SURI +ServiceFactory/MS +SignedExtension +Stringified +Submitter1 +S|N +TCP +ThisChain +TODO +U256 +Unparsed +Vec +WND/S +Westend/MS +Wococo/MS +XCM/S +XCMP/M +annualised/MS +api/SM +aren +arg +args +async +auth +auths/SM +backoff +benchmarking/MS +best_substrate_header +bitfield/MS +blake2/MS +blockchain/MS +borked +chain_getBlock +choosen +config/MS +crypto/MS +customizable/B +debian/M +decodable/MS +delivery_and_dispatch_fee +dev +dispatchable +dispatchables +doesn +ed25519 +enum/MS +entrypoint/MS +ethereum/MS +externality/MS +extrinsic/MS +extrinsics +fedora/M +functor +fuzzer +hasher +hardcoded +https +implementers +include/BG +inherent/MS +initialize/RG +instantiate/B +intrinsic/MS +invariant/MS +invariants +io +isn +isolate/BG +js +jsonrpsee +keccak +keccak256/M +keyring +keystore/MS +kusama/S +lane +malus +max_value +merkle/MS +metadata +millau +misbehavior/SM +misbehaviors +multivalidator/SM +natively +no_std +nonces +number +ok +oneshot/MS +others' +pallet_bridge_grandpa +pallet_bridge_messages +pallet_message_lane +parablock/MS +parachain/MS +param/MS +parameterize/D +plancks +polkadot/MS +pov-block/MS +precommit +promethius +promethius' +provisioner/MS +probabilistically +prune_depth +prune_end +receival +reconnection +redhat/M +repo/MS +runtime/MS +rustc/MS +relayer/MS +shouldn +source_at_target +source_latest_confirmed +source_latest_generated +sp_finality_grandpa +spawner +sr25519 +src +stringified +struct/MS +submitters/MS +subsystem/MS +subsystems' +subcommand/MS +synchronizer +target_at_source +target_latest_confirmed +target_latest_received +taskmanager/MS +teleport/RG +teleportation/SM +teleporter/SM +teleporters +testnet/MS +timeframe +tokio +timestamp +trie/MS +trustless/Y +tuple +u32 +ubuntu/M +undeliverable +unfinalized +union/MSG +unpruned +unservable/B +unsynced +updatable +validator/SM +ve +vec +verifier +w3f/MS +wakeup +wasm/M +websocket +x2 +~ diff --git a/.config/spellcheck.toml b/.config/spellcheck.toml new file mode 100644 index 00000000000..e061c29ac22 --- /dev/null +++ b/.config/spellcheck.toml @@ -0,0 +1,13 @@ +[hunspell] +lang = "en_US" +search_dirs = ["."] +extra_dictionaries = ["lingua.dic"] +skip_os_lookups = true +use_builtin = true + +[hunspell.quirks] +# `Type`'s +# 5x +transform_regex = ["^'([^\\s])'$", "^[0-9]+(?:\\.[0-9]*)?x$", "^'s$", "^\\+$", "[><+-]"] +allow_concatenation = true +allow_dashes = true diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..f4ceea78560 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +**/target/ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..e2375881ea0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true +[*] +indent_style=tab +indent_size=tab +tab_width=4 +end_of_line=lf +charset=utf-8 +trim_trailing_whitespace=true +max_line_length=100 +insert_final_newline=true + +[*.{yml,md,yaml,sh}] +indent_style=space +indent_size=2 +tab_width=8 +end_of_line=lf + +[*.md] +max_line_length=80 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..a06d573703d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,42 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: weekly + time: "03:00" + timezone: Europe/Berlin + open-pull-requests-limit: 20 + ignore: + - dependency-name: frame-* + versions: + - ">= 0" + - dependency-name: node-inspect + versions: + - ">= 0" + - dependency-name: pallet-* + versions: + - ">= 0" + - dependency-name: sc-* + versions: + - ">= 0" + - dependency-name: sp-* + versions: + - ">= 0" + - dependency-name: substrate-* + versions: + - ">= 0" + - dependency-name: vergen + versions: + - 4.0.1 + - 4.0.2 + - 4.1.0 + - 4.2.0 + - dependency-name: jsonrpc-core + versions: + - 17.0.0 + - dependency-name: finality-grandpa + versions: + - 0.13.0 + - 0.14.0 + rebase-strategy: disabled diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..5d10cfa41a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +**/target/ +**/.env +**/.env2 +**/rust-toolchain +hfuzz_target +hfuzz_workspace +**/Cargo.lock + +**/*.rs.bk + +*.o +*.so +*.rlib +*.dll +.gdb_history + +*.exe + +.DS_Store + +.cargo +.idea +.vscode +*.iml +*.swp +*.swo diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000000..eb01cfc67ea --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,303 @@ +stages: + - lint + - check + - test + - build + - publish + +workflow: + rules: + - if: $CI_COMMIT_TAG + - if: $CI_COMMIT_BRANCH + +variables: &default-vars + GIT_STRATEGY: fetch + GIT_DEPTH: 100 + CARGO_INCREMENTAL: 0 + ARCH: "x86_64" + CI_IMAGE: "paritytech/bridges-ci:production" + RUST_BACKTRACE: full + +default: + cache: {} + +.collect-artifacts: &collect-artifacts + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: on_success + expire_in: 7 days + paths: + - artifacts/ + +.kubernetes-build: &kubernetes-build + tags: + - kubernetes-parity-build + interruptible: true + +.docker-env: &docker-env + image: "${CI_IMAGE}" + before_script: + - rustup show + - cargo --version + - rustup +nightly show + - cargo +nightly --version + - sccache -s + retry: + max: 2 + when: + - runner_system_failure + - unknown_failure + - api_failure + interruptible: true + tags: + - linux-docker + +.test-refs: &test-refs + rules: + # FIXME: This is the cause why pipelines wouldn't start. The problem might be in our custom + # mirroring. This should be investigated further, but for now let's have the working + # pipeline. + # - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH + # changes: + # - '**.md' + # - diagrams/* + # - docs/* + # when: never + - if: $CI_PIPELINE_SOURCE == "pipeline" + - if: $CI_PIPELINE_SOURCE == "web" + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + +.build-refs: &build-refs + rules: + # won't run on the CI image update pipeline + - if: $CI_PIPELINE_SOURCE == "pipeline" + when: never + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]{4}-[0-9]{2}-[0-9]{2}.*$/ # i.e. v2021-09-27, v2021-09-27-1 + # there are two types of nightly pipelines: + # 1. this one is triggered by the schedule with $PIPELINE == "nightly", it's for releasing. + # this job runs only on nightly pipeline with the mentioned variable, against `master` branch + - if: $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "nightly" + +.nightly-test: &nightly-test + rules: + # 2. another is triggered by scripts repo $CI_PIPELINE_SOURCE == "pipeline" it's for the CI image + # update, it also runs all the nightly checks. + - if: $CI_PIPELINE_SOURCE == "pipeline" + +#### stage: lint + +clippy-nightly: + stage: lint + <<: *docker-env + <<: *test-refs + variables: + RUSTFLAGS: "-D warnings" + script: + # clippy currently is raising `derive-partial-eq-without-eq` warning even if `Eq` is actually derived + - SKIP_WASM_BUILD=1 cargo +nightly clippy --all-targets -- -A clippy::redundant_closure -A clippy::derive-partial-eq-without-eq -A clippy::or_fun_call + +fmt: + stage: lint + <<: *docker-env + <<: *test-refs + script: + - cargo +nightly fmt --all -- --check + +spellcheck: + stage: lint + <<: *docker-env + <<: *test-refs + script: + - cargo spellcheck check --cfg=.config/spellcheck.toml --checkers hunspell -m 1 + +#### stage: check + +check: + stage: check + <<: *docker-env + <<: *test-refs + script: &check-script + - SKIP_WASM_BUILD=1 time cargo check --locked --verbose --workspace + # Check Rialto benchmarks runtime + - SKIP_WASM_BUILD=1 time cargo check -p rialto-runtime --locked --features runtime-benchmarks --verbose + # Check Millau benchmarks runtime + - SKIP_WASM_BUILD=1 time cargo check -p millau-runtime --locked --features runtime-benchmarks --verbose + +check-nightly: + stage: check + <<: *docker-env + <<: *nightly-test + script: + - rustup default nightly + - *check-script + +#### stage: test + +test: + stage: test + <<: *docker-env + <<: *test-refs +# variables: +# RUSTFLAGS: "-D warnings" + script: &test-script + - time cargo fetch + - time cargo fetch --manifest-path=`cargo metadata --format-version=1 | jq --compact-output --raw-output ".packages[] | select(.name == \"polkadot-runtime\").manifest_path"` + - time cargo fetch --manifest-path=`cargo metadata --format-version=1 | jq --compact-output --raw-output ".packages[] | select(.name == \"kusama-runtime\").manifest_path"` + - CARGO_NET_OFFLINE=true SKIP_POLKADOT_RUNTIME_WASM_BUILD=1 SKIP_KUSAMA_RUNTIME_WASM_BUILD=1 SKIP_POLKADOT_TEST_RUNTIME_WASM_BUILD=1 time cargo test --verbose --workspace + +test-nightly: + stage: test + <<: *docker-env + <<: *nightly-test + script: + - rustup default nightly + - *test-script + +deny: + stage: test + <<: *docker-env + <<: *nightly-test + <<: *collect-artifacts + script: + - cargo deny check advisories --hide-inclusion-graph + - cargo deny check bans sources --hide-inclusion-graph + after_script: + - mkdir -p ./artifacts + - echo "___Complete logs can be found in the artifacts___" + - cargo deny check advisories 2> advisories.log + - cargo deny check bans sources 2> bans_sources.log + # this job is allowed to fail, only licenses check is important + allow_failure: true + +deny-licenses: + stage: test + <<: *docker-env + <<: *test-refs + <<: *collect-artifacts + script: + - cargo deny check licenses --hide-inclusion-graph + after_script: + - mkdir -p ./artifacts + - echo "___Complete logs can be found in the artifacts___" + - cargo deny check licenses 2> licenses.log + +benchmarks-test: + stage: test + <<: *docker-env + <<: *nightly-test + script: + - time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet --chain=dev --steps=2 --repeat=1 --pallet=pallet_bridge_messages --extrinsic=* --execution=wasm --wasm-execution=Compiled --heap-pages=4096 + - time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet --chain=dev --steps=2 --repeat=1 --pallet=pallet_bridge_grandpa --extrinsic=* --execution=wasm --wasm-execution=Compiled --heap-pages=4096 + - time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet --chain=dev --steps=2 --repeat=1 --pallet=pallet_bridge_parachains --extrinsic=* --execution=wasm --wasm-execution=Compiled --heap-pages=4096 + - time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet --chain=dev --steps=2 --repeat=1 --pallet=pallet_bridge_relayers --extrinsic=* --execution=wasm --wasm-execution=Compiled --heap-pages=4096 + # we may live with failing benchmarks, it is just a signal for us + allow_failure: true + +#### stage: build + +build: + stage: build + <<: *docker-env + <<: *build-refs + <<: *collect-artifacts + # master + script: &build-script + - time cargo fetch + - time cargo fetch --manifest-path=`cargo metadata --format-version=1 | jq --compact-output --raw-output ".packages[] | select(.name == \"polkadot-runtime\").manifest_path"` + - time cargo fetch --manifest-path=`cargo metadata --format-version=1 | jq --compact-output --raw-output ".packages[] | select(.name == \"kusama-runtime\").manifest_path"` + - CARGO_NET_OFFLINE=true SKIP_POLKADOT_RUNTIME_WASM_BUILD=1 SKIP_KUSAMA_RUNTIME_WASM_BUILD=1 SKIP_POLKADOT_TEST_RUNTIME_WASM_BUILD=1 time cargo build --release --verbose --workspace + after_script: + # Prepare artifacts + - mkdir -p ./artifacts + - strip ./target/release/rialto-bridge-node + - mv -v ./target/release/rialto-bridge-node ./artifacts/ + - strip ./target/release/rialto-parachain-collator + - mv -v ./target/release/rialto-parachain-collator ./artifacts/ + - strip ./target/release/millau-bridge-node + - mv -v ./target/release/millau-bridge-node ./artifacts/ + - strip ./target/release/substrate-relay + - mv -v ./target/release/substrate-relay ./artifacts/ + - mv -v ./deployments/local-scripts/bridge-entrypoint.sh ./artifacts/ + - mv -v ./ci.Dockerfile ./artifacts/ + +build-nightly: + stage: build + <<: *docker-env + <<: *collect-artifacts + <<: *nightly-test + script: + - rustup default nightly + - *build-script + +#### stage: publish + +.build-push-image: &build-push-image + <<: *kubernetes-build + image: quay.io/buildah/stable:v1.27 + <<: *build-refs + variables: &image-variables + GIT_STRATEGY: none + DOCKERFILE: ci.Dockerfile + IMAGE_NAME: docker.io/paritytech/$CI_JOB_NAME + needs: + - job: build + artifacts: true + before_script: &check-versions + - if [[ "${CI_COMMIT_TAG}" ]]; then + VERSION=${CI_COMMIT_TAG}; + elif [[ "${CI_COMMIT_REF_NAME}" ]]; then + VERSION=$(echo ${CI_COMMIT_REF_NAME} | sed -r 's#/+#-#g'); + fi + # When building from version tags (v1.0, v2.1rc1, ...) we'll use "production" to tag + # docker image. In all other cases, it'll be "latest". + - if [[ $CI_COMMIT_REF_NAME =~ ^v[0-9]+\.[0-9]+.*$ ]]; then + FLOATING_TAG="production"; + else + FLOATING_TAG="latest"; + fi + - echo "Effective tags = ${VERSION} sha-${CI_COMMIT_SHORT_SHA} ${FLOATING_TAG}" + script: + - test "${Docker_Hub_User_Parity}" -a "${Docker_Hub_Pass_Parity}" || + ( echo "no docker credentials provided"; exit 1 ) + - cd ./artifacts + - buildah bud + --format=docker + --build-arg VCS_REF="${CI_COMMIT_SHORT_SHA}" + --build-arg BUILD_DATE="$(date +%d-%m-%Y)" + --build-arg PROJECT="${CI_JOB_NAME}" + --build-arg VERSION="${VERSION}" + --tag "${IMAGE_NAME}:${VERSION}" + --tag "${IMAGE_NAME}:sha-${CI_COMMIT_SHORT_SHA}" + --tag "${IMAGE_NAME}:${FLOATING_TAG}" + --file "${DOCKERFILE}" . + # The job will success only on the protected branch + - echo "${Docker_Hub_Pass_Parity}" | + buildah login --username "${Docker_Hub_User_Parity}" --password-stdin docker.io + - buildah info + - buildah push --format=v2s2 "${IMAGE_NAME}:${VERSION}" + - buildah push --format=v2s2 "${IMAGE_NAME}:sha-${CI_COMMIT_SHORT_SHA}" + - buildah push --format=v2s2 "${IMAGE_NAME}:${FLOATING_TAG}" + after_script: + - env REGISTRY_AUTH_FILE= buildah logout --all + +rialto-bridge-node: + stage: publish + <<: *build-push-image + +rialto-parachain-collator: + stage: publish + <<: *build-push-image + +millau-bridge-node: + stage: publish + <<: *build-push-image + +substrate-relay: + stage: publish + <<: *build-push-image + +# FIXME: publish binaries diff --git a/.maintain/millau-weight-template.hbs b/.maintain/millau-weight-template.hbs new file mode 100644 index 00000000000..ac5c6b85b91 --- /dev/null +++ b/.maintain/millau-weight-template.hbs @@ -0,0 +1,106 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Autogenerated weights for `{{pallet}}` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}}, STEPS: {{cmd.steps}}, REPEAT: {{cmd.repeat}} +//! LOW RANGE: {{cmd.lowest_range_values}}, HIGH RANGE: {{cmd.highest_range_values}} +//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}} +//! CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} + +// Executed Command: +{{#each args as |arg|~}} +// {{arg}} +{{/each}} + +#![allow(clippy::all)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for `{{pallet}}`. +pub trait WeightInfo { + {{~#each benchmarks as |benchmark|}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{c.name}}: u32, {{/each~}} + ) -> Weight; + {{~/each}} +} + +/// Weights for `{{pallet}}` that are generated using one of the Bridge testnets. +/// +/// Those weights are test only and must never be used in production. +pub struct BridgeWeight(PhantomData); +impl WeightInfo for BridgeWeight { + {{~#each benchmarks as |benchmark|}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + Weight::from_ref_time({{underscore benchmark.base_weight}} as u64) + {{~#each benchmark.component_weight as |cw|}} + .saturating_add(Weight::from_ref_time({{underscore cw.slope}} as u64).saturating_mul({{cw.name}} as u64)) + {{~/each}} + {{~#if (ne benchmark.base_reads "0")}} + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as u64)) + {{~/if}} + {{~#each benchmark.component_reads as |cr|}} + .saturating_add(T::DbWeight::get().reads(({{cr.slope}} as u64).saturating_mul({{cr.name}} as u64))) + {{~/each}} + {{~#if (ne benchmark.base_writes "0")}} + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}} as u64)) + {{~/if}} + {{~#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}} as u64).saturating_mul({{cw.name}} as u64))) + {{~/each}} + } + {{~/each}} +} + +// For backwards compatibility and tests +impl WeightInfo for () { + {{~#each benchmarks as |benchmark|}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + Weight::from_ref_time({{underscore benchmark.base_weight}} as u64) + {{~#each benchmark.component_weight as |cw|}} + .saturating_add(Weight::from_ref_time({{underscore cw.slope}} as u64).saturating_mul({{cw.name}} as u64)) + {{~/each}} + {{~#if (ne benchmark.base_reads "0")}} + .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}} as u64)) + {{~/if}} + {{~#each benchmark.component_reads as |cr|}} + .saturating_add(RocksDbWeight::get().reads(({{cr.slope}} as u64).saturating_mul({{cr.name}} as u64))) + {{~/each}} + {{~#if (ne benchmark.base_writes "0")}} + .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}} as u64)) + {{~/if}} + {{~#each benchmark.component_writes as |cw|}} + .saturating_add(RocksDbWeight::get().writes(({{cw.slope}} as u64).saturating_mul({{cw.name}} as u64))) + {{~/each}} + } + {{~/each}} +} diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000000..3941ba8451a --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,21 @@ +# Lists some code owners. +# +# A codeowner just oversees some part of the codebase. If an owned file is changed then the +# corresponding codeowner receives a review request. An approval of the codeowner might be +# required for merging a PR (depends on repository settings). +# +# For details about syntax, see: +# https://help.github.com/en/articles/about-code-owners +# But here are some important notes: +# +# - Glob syntax is git-like, e.g. `/core` means the core directory in the root, unlike `core` +# which can be everywhere. +# - Multiple owners are supported. +# - Either handle (e.g, @github_user or @github_org/team) or email can be used. Keep in mind, +# that handles might work better because they are more recognizable on GitHub, +# eyou can use them for mentioning unlike an email. +# - The latest matching rule, if multiple, takes precedence. + +# CI +/.github/ @paritytech/ci +/.gitlab-ci.yml @paritytech/ci diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..70541fb72fa --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,80 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers +pledge to making participation in our project and our community a harassment-free experience for +everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity +and expression, level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit + permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +### Facilitation, Not Strongarming + +We recognise that this software is merely a tool for users to create and maintain their blockchain +of preference. We see that blockchains are naturally community platforms with users being the +ultimate decision makers. We assert that good software will maximise user agency by facilitate +user-expression on the network. As such: + +- This project will strive to give users as much choice as is both reasonable and possible over what + protocol they adhere to; but +- use of the project's technical forums, commenting systems, pull requests and issue trackers as a + means to express individual protocol preferences is forbidden. + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are +expected to take appropriate and fair corrective action in response to any instances of unacceptable +behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, +code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or +to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is +representing the project or its community. Examples of representing a project or community include +using an official project e-mail address, posting via an official social media account, or acting as +an appointed representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting +the project team at admin@parity.io. All complaints will be reviewed and investigated and will +result in a response that is deemed necessary and appropriate to the circumstances. The project team +is obligated to maintain confidentiality with regard to the reporter of an incident. Further +details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face +temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000000..85763e18a7b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,13268 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.3", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "always-assert" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf688625d06217d5b1bb0ea9d9c44a1635fd0ee3534466388d18203174f4d11" + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + +[[package]] +name = "approx" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510c76ecefdceada737ea728f4f9a84bd2e1ef29f1ba555e560940fe279954de" + +[[package]] +name = "array-bytes" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a913633b0c922e6b745072795f50d90ebea78ba31a57e2ac8c2fc7b50950949" + +[[package]] +name = "array_tool" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f8cb5d814eb646a863c4f24978cff2880c4be96ad8cde2c0f0678732902e271" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "asn1_der" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-mutex", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-lock" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +dependencies = [ + "async-io", + "blocking", + "cfg-if 1.0.0", + "event-listener", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi", +] + +[[package]] +name = "async-std" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite 0.2.9", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-std-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba50e24d9ee0a8950d3d03fc6d0dd10aa14b5de3b101949b4e160f7fee7c723" +dependencies = [ + "async-std", + "async-trait", + "futures-io", + "futures-util", + "pin-utils", + "socket2", + "trust-dns-resolver", +] + +[[package]] +name = "async-task" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "asynchronous-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite 0.2.9", +] + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backoff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721c249ab59cbc483ad4294c9ee2671835c1e43e9ffc277e6b4ecfef733cfdc5" +dependencies = [ + "instant", + "rand 0.7.3", +] + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.3", + "instant", + "pin-project-lite 0.2.9", + "rand 0.8.5", + "tokio", +] + +[[package]] +name = "backtrace" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object 0.27.1", + "rustc-demangle", +] + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "base64" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" +dependencies = [ + "serde", +] + +[[package]] +name = "beefy-gadget" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "async-trait", + "beefy-primitives", + "fnv", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-chain-spec", + "sc-client-api", + "sc-consensus", + "sc-finality-grandpa", + "sc-keystore", + "sc-network", + "sc-network-common", + "sc-network-gossip", + "sc-utils", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-keystore", + "sp-mmr-primitives", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", + "wasm-timer", +] + +[[package]] +name = "beefy-gadget-rpc" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "beefy-gadget", + "beefy-primitives", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-rpc", + "sc-utils", + "serde", + "sp-core", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "beefy-merkle-tree" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "beefy-primitives", + "sp-api", + "sp-runtime", +] + +[[package]] +name = "beefy-primitives" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-mmr-primitives", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "cc", + "cfg-if 1.0.0", + "constant_time_eq", + "digest 0.10.3", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-buffer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "blocking" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + +[[package]] +name = "bounded-vec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3372be4090bf9d4da36bd8ba7ce6ca1669503d0cf6e667236c6df7f053153eb6" +dependencies = [ + "thiserror", +] + +[[package]] +name = "bp-beefy" +version = "0.1.0" +dependencies = [ + "beefy-merkle-tree", + "beefy-primitives", + "bp-runtime", + "frame-support", + "pallet-beefy-mmr", + "pallet-mmr", + "parity-scale-codec", + "scale-info", + "serde", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "static_assertions", +] + +[[package]] +name = "bp-bridge-hub-rococo" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "polkadot-runtime-constants", + "smallvec", + "sp-api", + "sp-std", +] + +[[package]] +name = "bp-bridge-hub-wococo" +version = "0.1.0" +dependencies = [ + "bp-bridge-hub-rococo", + "bp-messages", + "bp-runtime", + "frame-support", + "sp-api", + "sp-std", +] + +[[package]] +name = "bp-header-chain" +version = "0.1.0" +dependencies = [ + "bp-runtime", + "bp-test-utils", + "finality-grandpa", + "frame-support", + "hex", + "hex-literal", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "bp-kusama" +version = "0.1.0" +dependencies = [ + "bp-polkadot-core", + "bp-runtime", + "sp-api", +] + +[[package]] +name = "bp-messages" +version = "0.1.0" +dependencies = [ + "bp-runtime", + "frame-support", + "hex", + "hex-literal", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-std", +] + +[[package]] +name = "bp-millau" +version = "0.1.0" +dependencies = [ + "bp-beefy", + "bp-messages", + "bp-runtime", + "fixed-hash 0.7.0", + "frame-support", + "frame-system", + "hash256-std-hasher", + "impl-codec", + "impl-serde 0.3.2", + "parity-util-mem", + "scale-info", + "serde", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "bp-parachains" +version = "0.1.0" +dependencies = [ + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "parity-scale-codec", + "scale-info", + "sp-core", +] + +[[package]] +name = "bp-polkadot" +version = "0.1.0" +dependencies = [ + "bp-polkadot-core", + "bp-runtime", + "sp-api", +] + +[[package]] +name = "bp-polkadot-core" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "hex", + "parity-scale-codec", + "parity-util-mem", + "scale-info", + "serde", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-relayers" +version = "0.1.0" +dependencies = [ + "frame-support", + "hex", + "hex-literal", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-rialto" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "sp-api", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-rialto-parachain" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-runtime", + "frame-support", + "frame-system", + "sp-api", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bp-rococo" +version = "0.1.0" +dependencies = [ + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "sp-api", +] + +[[package]] +name = "bp-runtime" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "hash-db", + "hex-literal", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", + "trie-db", +] + +[[package]] +name = "bp-test-utils" +version = "0.1.0" +dependencies = [ + "bp-header-chain", + "ed25519-dalek", + "finality-grandpa", + "parity-scale-codec", + "sp-application-crypto", + "sp-finality-grandpa", + "sp-runtime", + "sp-std", + "xcm", +] + +[[package]] +name = "bp-westend" +version = "0.1.0" +dependencies = [ + "bp-polkadot-core", + "bp-runtime", + "sp-api", +] + +[[package]] +name = "bp-wococo" +version = "0.1.0" +dependencies = [ + "bp-polkadot-core", + "bp-rococo", + "bp-runtime", + "sp-api", +] + +[[package]] +name = "bridge-runtime-common" +version = "0.1.0" +dependencies = [ + "bp-header-chain", + "bp-messages", + "bp-parachains", + "bp-polkadot-core", + "bp-runtime", + "frame-support", + "frame-system", + "hash-db", + "log", + "millau-runtime", + "pallet-balances", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-xcm", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", + "static_assertions", + "xcm", + "xcm-builder", + "xcm-executor", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "build-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f" +dependencies = [ + "semver 0.6.0", +] + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + +[[package]] +name = "camino" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.4", + "serde", + "serde_json", +] + +[[package]] +name = "castaway" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed247d1586918e46f2bbe0f13b06498db8dab5a8c1093f156652e9f2e0a73fc3" + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[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 = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.44", + "winapi", +] + +[[package]] +name = "cid" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ed9c8b2d17acb8110c46f1da5bf4a696d745e1474a16db0cd2b49cd0249bf2" +dependencies = [ + "core2", + "multibase", + "multihash", + "serde", + "unsigned-varint", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "ckb-merkle-mountain-range" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f061f97d64fd1822664bdfb722f7ae5469a97b77567390f7442be5b5dc82a5b" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "clang-sys" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term 0.11.0", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap" +version = "4.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "once_cell", + "strsim 0.10.0", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "coarsetime" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "454038500439e141804c655b4cd1bc6a70bcb95cd2bc9463af5661b6956f0e46" +dependencies = [ + "libc", + "once_cell", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "comfy-table" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85914173c2f558d61613bfbbf1911f14e630895087a7ed2fafc0f5319e1536e7" +dependencies = [ + "strum 0.24.1", + "strum_macros 0.24.0", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpp_demangle" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931ab2a3e6330a07900b8e7ca4e106cdcbb93f2b9a52df55e54ee53d8305b55d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44409ccf2d0f663920cab563d2b79fcd6b2e9a2bcc6e929fef76c8f82ad6c17a" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de2018ad96eb97f621f7d6b900a0cc661aec8d02ea4a50e56ecb48e5a2fcaf" +dependencies = [ + "arrayvec 0.7.2", + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "gimli", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5287ce36e6c4758fbaf298bd1a8697ad97a4f2375a3d1b61142ea538db4877e5" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2855c24219e2f08827f3f4ffb2da92e134ae8d8ecc185b11ec8f9878cf5f588e" + +[[package]] +name = "cranelift-entity" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b65673279d75d34bf11af9660ae2dbd1c22e6d28f163f5c72f4e1dc56d56103" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed2b3d7a4751163f6c4a349205ab1b7d9c00eecf19dcea48592ef1f7688eefc" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be64cecea9d90105fc6a2ba2d003e98c867c1d6c4c86cc878f97ad9fb916293" + +[[package]] +name = "cranelift-native" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a03a6ac1b063e416ca4b93f6247978c991475e8271465340caa6f92f3c16a4" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c699873f7b30bc5f20dd03a796b4183e073a46616c91704792ec35e45d13f913" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "crc32fast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.4", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array 0.14.4", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "cumulus-client-cli" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "clap 4.0.26", + "parity-scale-codec", + "sc-chain-spec", + "sc-cli", + "sc-service", + "sp-core", + "sp-runtime", + "url", +] + +[[package]] +name = "cumulus-client-collator" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-client-consensus-common", + "cumulus-client-network", + "cumulus-primitives-core", + "futures", + "parity-scale-codec", + "parking_lot 0.12.1", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-overseer", + "polkadot-primitives", + "sc-client-api", + "sp-api", + "sp-consensus", + "sp-core", + "sp-runtime", + "tracing", +] + +[[package]] +name = "cumulus-client-consensus-aura" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "async-trait", + "cumulus-client-consensus-common", + "cumulus-primitives-core", + "futures", + "parity-scale-codec", + "sc-client-api", + "sc-consensus", + "sc-consensus-aura", + "sc-consensus-slots", + "sc-telemetry", + "sp-api", + "sp-application-crypto", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "substrate-prometheus-endpoint", + "tracing", +] + +[[package]] +name = "cumulus-client-consensus-common" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "async-trait", + "cumulus-relay-chain-interface", + "dyn-clone", + "futures", + "parity-scale-codec", + "polkadot-primitives", + "sc-client-api", + "sc-consensus", + "sp-blockchain", + "sp-consensus", + "sp-runtime", + "sp-trie", + "tracing", +] + +[[package]] +name = "cumulus-client-network" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "async-trait", + "cumulus-relay-chain-interface", + "futures", + "futures-timer", + "parity-scale-codec", + "parking_lot 0.12.1", + "polkadot-node-primitives", + "polkadot-parachain", + "polkadot-primitives", + "sc-client-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", + "sp-state-machine", + "tracing", +] + +[[package]] +name = "cumulus-client-pov-recovery" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-primitives-core", + "cumulus-relay-chain-interface", + "futures", + "futures-timer", + "parity-scale-codec", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-overseer", + "polkadot-primitives", + "rand 0.8.5", + "sc-client-api", + "sc-consensus", + "sp-consensus", + "sp-maybe-compressed-blob", + "sp-runtime", + "tracing", +] + +[[package]] +name = "cumulus-client-service" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-client-cli", + "cumulus-client-collator", + "cumulus-client-consensus-common", + "cumulus-client-pov-recovery", + "cumulus-primitives-core", + "cumulus-relay-chain-interface", + "parking_lot 0.12.1", + "polkadot-primitives", + "sc-client-api", + "sc-consensus", + "sc-service", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "cumulus-pallet-aura-ext" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "frame-support", + "frame-system", + "pallet-aura", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-aura", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "cumulus-pallet-dmp-queue" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", +] + +[[package]] +name = "cumulus-pallet-parachain-system" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "bytes", + "cumulus-pallet-parachain-system-proc-macro", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "environmental", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "polkadot-parachain", + "scale-info", + "sp-core", + "sp-externalities", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", + "sp-version", +] + +[[package]] +name = "cumulus-pallet-parachain-system-proc-macro" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cumulus-pallet-xcm" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", +] + +[[package]] +name = "cumulus-pallet-xcmp-queue" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "polkadot-runtime-common", + "rand_chacha 0.3.1", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + +[[package]] +name = "cumulus-primitives-core" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain", + "polkadot-primitives", + "sp-api", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "cumulus-primitives-parachain-inherent" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "async-trait", + "cumulus-primitives-core", + "cumulus-relay-chain-interface", + "cumulus-test-relay-sproof-builder", + "parity-scale-codec", + "sc-client-api", + "scale-info", + "sp-api", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-storage", + "sp-trie", + "tracing", +] + +[[package]] +name = "cumulus-primitives-timestamp" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-primitives-core", + "futures", + "parity-scale-codec", + "sp-inherents", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "cumulus-relay-chain-inprocess-interface" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "async-trait", + "cumulus-primitives-core", + "cumulus-relay-chain-interface", + "futures", + "futures-timer", + "polkadot-cli", + "polkadot-client", + "polkadot-service", + "sc-cli", + "sc-client-api", + "sc-sysinfo", + "sc-telemetry", + "sc-tracing", + "sp-api", + "sp-consensus", + "sp-core", + "sp-runtime", + "sp-state-machine", +] + +[[package]] +name = "cumulus-relay-chain-interface" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "async-trait", + "cumulus-primitives-core", + "futures", + "jsonrpsee-core", + "parity-scale-codec", + "polkadot-overseer", + "polkadot-service", + "sc-client-api", + "sp-api", + "sp-blockchain", + "sp-state-machine", + "thiserror", +] + +[[package]] +name = "cumulus-relay-chain-minimal-node" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "array-bytes", + "async-trait", + "cumulus-primitives-core", + "cumulus-relay-chain-interface", + "cumulus-relay-chain-rpc-interface", + "futures", + "lru 0.8.1", + "polkadot-availability-distribution", + "polkadot-core-primitives", + "polkadot-network-bridge", + "polkadot-node-core-av-store", + "polkadot-node-network-protocol", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-primitives", + "polkadot-service", + "sc-authority-discovery", + "sc-client-api", + "sc-consensus", + "sc-keystore", + "sc-network", + "sc-network-common", + "sc-network-light", + "sc-network-sync", + "sc-service", + "sc-telemetry", + "sc-tracing", + "sc-transaction-pool", + "sc-transaction-pool-api", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-consensus-babe", + "sp-runtime", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "cumulus-relay-chain-rpc-interface" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "async-trait", + "backoff 0.4.0", + "cumulus-primitives-core", + "cumulus-relay-chain-interface", + "futures", + "futures-timer", + "jsonrpsee", + "parity-scale-codec", + "polkadot-service", + "sc-client-api", + "sc-rpc-api", + "sp-api", + "sp-authority-discovery", + "sp-consensus-babe", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-storage", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "cumulus-test-relay-sproof-builder" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-primitives-core", + "parity-scale-codec", + "polkadot-primitives", + "sp-runtime", + "sp-state-machine", + "sp-std", +] + +[[package]] +name = "curl" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc6d233563261f8db6ffb83bbaad5a73837a6e6b28868e926337ebbdece0be3" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.51+curl-7.80.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d130987e6a6a34fe0889e1083022fa48cd90e6709a84be3fb8dd95801de5af20" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "cxx" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "data-encoding-macro" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" +dependencies = [ + "data-encoding", + "syn", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.0", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dns-parser" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" +dependencies = [ + "byteorder", + "quick-error 1.2.3", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dtoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6053ff46b5639ceb91756a85a4c8914668393a03170efd79c8884a529d80656" + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" + +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.8", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.2.0", + "hashbrown", + "hex", + "rand_core 0.6.3", + "sha2 0.9.8", + "zeroize", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array 0.14.4", + "group", + "rand_core 0.6.3", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumflags2" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumn" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038b1afa59052df211f9efd58f8b1d84c242935ede1c3dbaed26b018a9e06ae2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime 2.1.0", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime 2.1.0", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "environmental" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash 0.8.0", + "impl-rlp", + "impl-serde 0.4.0", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81224dc661606574f5a0f28c9947d0ee1d93ff11c5f1c4e7272f52e8c0b5483c" +dependencies = [ + "ethbloom", + "fixed-hash 0.8.0", + "impl-rlp", + "impl-serde 0.4.0", + "primitive-types", + "uint", +] + +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + +[[package]] +name = "exit-future" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e43f2f1833d64e33f15592464d6fdd70f349dda7b1a53088eb83cd94014008c5" +dependencies = [ + "futures", +] + +[[package]] +name = "expander" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a718c0675c555c5f976fff4ea9e2c150fa06cefa201cadef87cfbf9324075881" +dependencies = [ + "blake3", + "fs-err", + "proc-macro2", + "quote", +] + +[[package]] +name = "expander" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3774182a5df13c3d1690311ad32fbe913feef26baba609fa2dd5f72042bd2ab6" +dependencies = [ + "blake2", + "fs-err", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fatality" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad875162843b0d046276327afe0136e9ed3a23d5a754210fb6f1f33610d39ab" +dependencies = [ + "fatality-proc-macro", + "thiserror", +] + +[[package]] +name = "fatality-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5aa1e3ae159e592ad222dc90c5acbad632b527779ba88486abe92782ab268bd" +dependencies = [ + "expander 0.0.4", + "indexmap", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "thiserror", +] + +[[package]] +name = "fdlimit" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4c9e43643f5a3be4ca5b67d26b98031ff9db6806c3440ae32e02e3ceac3f1b" +dependencies = [ + "libc", +] + +[[package]] +name = "ff" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "file-per-thread-logger" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fdbe0d94371f9ce939b555dd342d0686cc4c0cadbcd4b61d70af5ff97eb4126" +dependencies = [ + "env_logger 0.7.1", + "log", +] + +[[package]] +name = "filetime" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "windows-sys 0.36.1", +] + +[[package]] +name = "finality-grandpa" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22349c6a11563a202d95772a68e0fcf56119e74ea8a2a19cf2301460fcd0df5" +dependencies = [ + "either", + "futures", + "futures-timer", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot 0.12.1", + "scale-info", +] + +[[package]] +name = "finality-relay" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "backoff 0.2.1", + "bp-header-chain", + "futures", + "log", + "num-traits", + "parking_lot 0.11.2", + "relay-utils", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "libz-sys", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fork-tree" +version = "3.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "frame-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "linregress", + "log", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-std", + "sp-storage", +] + +[[package]] +name = "frame-benchmarking-cli" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "Inflector", + "array-bytes", + "chrono", + "clap 4.0.26", + "comfy-table", + "frame-benchmarking", + "frame-support", + "frame-system", + "gethostname", + "handlebars", + "hash-db", + "itertools", + "kvdb", + "lazy_static", + "linked-hash-map", + "log", + "memory-db", + "parity-scale-codec", + "rand 0.8.5", + "rand_pcg 0.3.1", + "sc-block-builder", + "sc-cli", + "sc-client-api", + "sc-client-db", + "sc-executor", + "sc-service", + "sc-sysinfo", + "serde", + "serde_json", + "serde_nanos", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-database", + "sp-externalities", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "sp-state-machine", + "sp-storage", + "sp-trie", + "tempfile", + "thiserror", + "thousands", +] + +[[package]] +name = "frame-election-provider-solution-type" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-election-provider-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-election-provider-solution-type", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-npos-elections", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "frame-executive" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "frame-metadata" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" +dependencies = [ + "cfg-if 1.0.0", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "bitflags", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-arithmetic", + "sp-core", + "sp-core-hashing-proc-macro", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-weights", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "Inflector", + "cfg-expr", + "frame-support-procedural-tools", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", + "sp-weights", +] + +[[package]] +name = "frame-system-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "frame-system-rpc-runtime-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + +[[package]] +name = "frame-try-runtime" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "fs-err" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ebd3504ad6116843b8375ad70df74e7bfe83cac77a1f3fe73200c844d43bfe0" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" + +[[package]] +name = "futures-executor" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite 0.2.9", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" +dependencies = [ + "futures-io", + "rustls", + "webpki", +] + +[[package]] +name = "futures-sink" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" + +[[package]] +name = "futures-task" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite 0.2.9", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug 0.3.0", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "globset" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.6.9", + "tracing", +] + +[[package]] +name = "handlebars" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.1", + "serde", + "serde_json", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.4", + "hmac 0.8.1", +] + +[[package]] +name = "honggfuzz" +version = "0.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea09577d948a98a5f59b7c891e274c4fb35ad52f67782b3d0cb53b9c05301f1" +dependencies = [ + "arbitrary", + "lazy_static", + "memmap", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.1", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite 0.2.9", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error 1.2.3", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 0.4.8", + "pin-project-lite 0.2.9", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "if-addrs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "if-watch" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065c008e570a43c00de6aed9714035e5ea6a498c255323db9091722af6ee67dd" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "rtnetlink", + "system-configuration", + "windows", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "integer-encoding" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c11140ffea82edce8dcd74137ce9324ec24b3cf0175fc9d7e29164da9915b8" + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "io-lifetimes" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" + +[[package]] +name = "ip_network" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" + +[[package]] +name = "ipconfig" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" +dependencies = [ + "socket2", + "widestring", + "winapi", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "isahc" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d140e84730d325378912ede32d7cd53ef1542725503b3353e5ec8113c7c6f588" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonpath_lib" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61352ec23883402b7d30b3313c16cbabefb8907361c4eb669d990cbb87ceee5a" +dependencies = [ + "array_tool", + "env_logger 0.7.1", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "jsonrpsee" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd0d559d5e679b1ab2f869b486a11182923863b1b3ee8b421763cdd707b783a" +dependencies = [ + "jsonrpsee-core", + "jsonrpsee-http-server", + "jsonrpsee-proc-macros", + "jsonrpsee-types", + "jsonrpsee-ws-client", + "jsonrpsee-ws-server", + "tracing", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8752740ecd374bcbf8b69f3e80b0327942df76f793f8d4e60d3355650c31fb74" +dependencies = [ + "futures-util", + "http", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project", + "rustls-native-certs", + "soketto", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util 0.7.1", + "tracing", + "webpki-roots", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3dc3e9cf2ba50b7b1d7d76a667619f82846caa39e8e8daa8a4962d74acaddca" +dependencies = [ + "anyhow", + "arrayvec 0.7.2", + "async-lock", + "async-trait", + "beef", + "futures-channel", + "futures-timer", + "futures-util", + "globset", + "http", + "hyper", + "jsonrpsee-types", + "lazy_static", + "parking_lot 0.12.1", + "rand 0.8.5", + "rustc-hash", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "unicase", +] + +[[package]] +name = "jsonrpsee-http-server" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03802f0373a38c2420c70b5144742d800b509e2937edc4afb116434f07120117" +dependencies = [ + "futures-channel", + "futures-util", + "hyper", + "jsonrpsee-core", + "jsonrpsee-types", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-futures", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd67957d4280217247588ac86614ead007b301ca2fa9f19c19f880a536f029e3" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e290bba767401b646812f608c099b922d8142603c9e73a50fb192d3ac86f4a0d" +dependencies = [ + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "jsonrpsee-ws-client" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee5feddd5188e62ac08fcf0e56478138e581509d4730f3f7be9b57dd402a4ff" +dependencies = [ + "http", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", +] + +[[package]] +name = "jsonrpsee-ws-server" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d488ba74fb369e5ab68926feb75a483458b88e768d44319f37e4ecad283c7325" +dependencies = [ + "futures-channel", + "futures-util", + "http", + "jsonrpsee-core", + "jsonrpsee-types", + "serde_json", + "soketto", + "tokio", + "tokio-stream", + "tokio-util 0.7.1", + "tracing", + "tracing-futures", +] + +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "sec1", +] + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "kusama-runtime" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "beefy-primitives", + "bitvec", + "frame-election-provider-support", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "kusama-runtime-constants", + "log", + "pallet-authority-discovery", + "pallet-authorship", + "pallet-babe", + "pallet-bags-list", + "pallet-balances", + "pallet-bounties", + "pallet-child-bounties", + "pallet-collective", + "pallet-conviction-voting", + "pallet-democracy", + "pallet-election-provider-multi-phase", + "pallet-elections-phragmen", + "pallet-fast-unstake", + "pallet-gilt", + "pallet-grandpa", + "pallet-identity", + "pallet-im-online", + "pallet-indices", + "pallet-membership", + "pallet-multisig", + "pallet-nomination-pools", + "pallet-nomination-pools-runtime-api", + "pallet-offences", + "pallet-preimage", + "pallet-proxy", + "pallet-ranked-collective", + "pallet-recovery", + "pallet-referenda", + "pallet-scheduler", + "pallet-session", + "pallet-society", + "pallet-staking", + "pallet-staking-reward-fn", + "pallet-timestamp", + "pallet-tips", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-treasury", + "pallet-utility", + "pallet-vesting", + "pallet-whitelist", + "pallet-xcm", + "parity-scale-codec", + "polkadot-primitives", + "polkadot-runtime-common", + "polkadot-runtime-parachains", + "rustc-hex", + "scale-info", + "serde", + "serde_derive", + "smallvec", + "sp-api", + "sp-arithmetic", + "sp-authority-discovery", + "sp-block-builder", + "sp-consensus-babe", + "sp-core", + "sp-inherents", + "sp-io", + "sp-mmr-primitives", + "sp-npos-elections", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "sp-transaction-pool", + "sp-version", + "static_assertions", + "substrate-wasm-builder", + "xcm", + "xcm-builder", + "xcm-executor", +] + +[[package]] +name = "kusama-runtime-constants" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "frame-support", + "polkadot-primitives", + "polkadot-runtime-common", + "smallvec", + "sp-runtime", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "kvdb" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585089ceadba0197ffe9af6740ab350b325e3c1f5fccfbc3522e0250c750409b" +dependencies = [ + "parity-util-mem", + "smallvec", +] + +[[package]] +name = "kvdb-memorydb" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d109c87bfb7759edd2a49b2649c1afe25af785d930ad6a38479b4dc70dd873" +dependencies = [ + "kvdb", + "parity-util-mem", + "parking_lot 0.12.1", +] + +[[package]] +name = "kvdb-rocksdb" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c076cc2cdbac89b9910c853a36c957d3862a779f31c2661174222cefb49ee597" +dependencies = [ + "kvdb", + "log", + "num_cpus", + "parity-util-mem", + "parking_lot 0.12.1", + "regex", + "rocksdb", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "libloading" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.7+1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libp2p" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec878fda12ebec479186b3914ebc48ff180fa4c51847e11a1a68bf65249e02c1" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.3", + "instant", + "lazy_static", + "libp2p-core", + "libp2p-dns", + "libp2p-identify", + "libp2p-kad", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-mplex", + "libp2p-noise", + "libp2p-ping", + "libp2p-request-response", + "libp2p-swarm", + "libp2p-swarm-derive", + "libp2p-tcp", + "libp2p-wasm-ext", + "libp2p-websocket", + "libp2p-yamux", + "multiaddr", + "parking_lot 0.12.1", + "pin-project", + "smallvec", +] + +[[package]] +name = "libp2p-core" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799676bb0807c788065e57551c6527d461ad572162b0519d1958946ff9e0539d" +dependencies = [ + "asn1_der", + "bs58", + "ed25519-dalek", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "lazy_static", + "log", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot 0.12.1", + "pin-project", + "prost", + "prost-build", + "rand 0.8.5", + "rw-stream-sink", + "sha2 0.10.5", + "smallvec", + "thiserror", + "unsigned-varint", + "void", + "zeroize", +] + +[[package]] +name = "libp2p-dns" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2322c9fb40d99101def6a01612ee30500c89abbbecb6297b3cd252903a4c1720" +dependencies = [ + "async-std-resolver", + "futures", + "libp2p-core", + "log", + "parking_lot 0.12.1", + "smallvec", + "trust-dns-resolver", +] + +[[package]] +name = "libp2p-identify" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf9a121f699e8719bda2e6e9e9b6ddafc6cff4602471d6481c1067930ccb29b" +dependencies = [ + "asynchronous-codec", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-swarm", + "log", + "lru 0.8.1", + "prost", + "prost-build", + "prost-codec", + "smallvec", + "thiserror", + "void", +] + +[[package]] +name = "libp2p-kad" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6721c200e2021f6c3fab8b6cf0272ead8912d871610ee194ebd628cecf428f22" +dependencies = [ + "arrayvec 0.7.2", + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-swarm", + "log", + "prost", + "prost-build", + "rand 0.8.5", + "sha2 0.10.5", + "smallvec", + "thiserror", + "uint", + "unsigned-varint", + "void", +] + +[[package]] +name = "libp2p-mdns" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761704e727f7d68d58d7bc2231eafae5fc1b9814de24290f126df09d4bd37a15" +dependencies = [ + "async-io", + "data-encoding", + "dns-parser", + "futures", + "if-watch", + "libp2p-core", + "libp2p-swarm", + "log", + "rand 0.8.5", + "smallvec", + "socket2", + "void", +] + +[[package]] +name = "libp2p-metrics" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee31b08e78b7b8bfd1c4204a9dd8a87b4fcdf6dafc57eb51701c1c264a81cb9" +dependencies = [ + "libp2p-core", + "libp2p-identify", + "libp2p-kad", + "libp2p-ping", + "libp2p-swarm", + "prometheus-client", +] + +[[package]] +name = "libp2p-mplex" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692664acfd98652de739a8acbb0a0d670f1d67190a49be6b4395e22c37337d89" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "smallvec", + "unsigned-varint", +] + +[[package]] +name = "libp2p-noise" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048155686bd81fe6cb5efdef0c6290f25ad32a0a42e8f4f72625cf6a505a206f" +dependencies = [ + "bytes", + "curve25519-dalek 3.2.0", + "futures", + "lazy_static", + "libp2p-core", + "log", + "prost", + "prost-build", + "rand 0.8.5", + "sha2 0.10.5", + "snow", + "static_assertions", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-ping" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7228b9318d34689521349a86eb39a3c3a802c9efc99a0568062ffb80913e3f91" +dependencies = [ + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-swarm", + "log", + "rand 0.8.5", + "void", +] + +[[package]] +name = "libp2p-request-response" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8827af16a017b65311a410bb626205a9ad92ec0473967618425039fa5231adc1" +dependencies = [ + "async-trait", + "bytes", + "futures", + "instant", + "libp2p-core", + "libp2p-swarm", + "log", + "rand 0.8.5", + "smallvec", + "unsigned-varint", +] + +[[package]] +name = "libp2p-swarm" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d13df7c37807965d82930c0e4b04a659efcb6cca237373b206043db5398ecf" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "log", + "pin-project", + "rand 0.8.5", + "smallvec", + "thiserror", + "void", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eddc4497a8b5a506013c40e8189864f9c3a00db2b25671f428ae9007f3ba32" +dependencies = [ + "heck 0.4.0", + "quote", + "syn", +] + +[[package]] +name = "libp2p-tcp" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9839d96761491c6d3e238e70554b856956fca0ab60feb9de2cd08eed4473fa92" +dependencies = [ + "async-io", + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "log", + "socket2", +] + +[[package]] +name = "libp2p-wasm-ext" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b5b8e7a73e379e47b1b77f8a82c4721e97eca01abcd18e9cd91a23ca6ce97" +dependencies = [ + "futures", + "js-sys", + "libp2p-core", + "parity-send-wrapper", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "libp2p-websocket" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3758ae6f89b2531a24b6d9f5776bda6a626b60a57600d7185d43dfa75ca5ecc4" +dependencies = [ + "either", + "futures", + "futures-rustls", + "libp2p-core", + "log", + "parking_lot 0.12.1", + "quicksink", + "rw-stream-sink", + "soketto", + "url", + "webpki-roots", +] + +[[package]] +name = "libp2p-yamux" +version = "0.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6874d66543c4f7e26e3b8ca9a6bead351563a13ab4fafd43c7927f7c0d6c12" +dependencies = [ + "futures", + "libp2p-core", + "log", + "parking_lot 0.12.1", + "thiserror", + "yamux", +] + +[[package]] +name = "librocksdb-sys" +version = "0.8.0+7.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "tikv-jemalloc-sys", +] + +[[package]] +name = "libsecp256k1" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.8", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libz-sys" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "linked_hash_set" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "linregress" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c601a85f5ecd1aba625247bca0031585fb1c446461b142878a16f8245ddeb8" +dependencies = [ + "nalgebra", + "statrs", +] + +[[package]] +name = "linux-raw-sys" +version = "0.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" + +[[package]] +name = "lock_api" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", + "value-bag", +] + +[[package]] +name = "lru" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "lru" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "lz4" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +dependencies = [ + "libc", + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "matrixmultiply" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memfd" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "480b5a5de855d11ff13195950bdc8b98b5e942ef47afc447f6615cdcc4e15d80" +dependencies = [ + "rustix", +] + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "memmap2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory-db" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac11bb793c28fa095b7554466f53b3a60a2cd002afdac01bcf135cbd73a269" +dependencies = [ + "hash-db", + "hashbrown", + "parity-util-mem", +] + +[[package]] +name = "memory-lru" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce95ae042940bad7e312857b929ee3d11b8f799a80cb7b9c7ec5125516906395" +dependencies = [ + "lru 0.8.1", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "messages-relay" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "bp-messages", + "bp-runtime", + "finality-relay", + "futures", + "hex", + "log", + "num-traits", + "parking_lot 0.11.2", + "relay-utils", + "sp-arithmetic", +] + +[[package]] +name = "mick-jaeger" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69672161530e8aeca1d1400fbf3f1a1747ff60ea604265a4e906c2442df20532" +dependencies = [ + "futures", + "rand 0.8.5", + "thrift", +] + +[[package]] +name = "millau-bridge-node" +version = "0.1.0" +dependencies = [ + "beefy-gadget", + "beefy-gadget-rpc", + "beefy-primitives", + "clap 4.0.26", + "frame-benchmarking", + "frame-benchmarking-cli", + "jsonrpsee", + "millau-runtime", + "node-inspect", + "pallet-mmr-rpc", + "pallet-transaction-payment-rpc", + "sc-basic-authorship", + "sc-cli", + "sc-client-api", + "sc-consensus", + "sc-consensus-aura", + "sc-executor", + "sc-finality-grandpa", + "sc-finality-grandpa-rpc", + "sc-keystore", + "sc-rpc", + "sc-service", + "sc-telemetry", + "sc-transaction-pool", + "serde_json", + "sp-consensus", + "sp-consensus-aura", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "sp-timestamp", + "substrate-build-script-utils", + "substrate-frame-rpc-system", +] + +[[package]] +name = "millau-runtime" +version = "0.1.0" +dependencies = [ + "beefy-primitives", + "bp-messages", + "bp-millau", + "bp-polkadot-core", + "bp-relayers", + "bp-rialto", + "bp-rialto-parachain", + "bp-runtime", + "bp-westend", + "bridge-runtime-common", + "env_logger 0.8.4", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "hex-literal", + "log", + "pallet-aura", + "pallet-balances", + "pallet-beefy", + "pallet-beefy-mmr", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-bridge-relayers", + "pallet-grandpa", + "pallet-mmr", + "pallet-randomness-collective-flip", + "pallet-session", + "pallet-shift-session-manager", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-xcm", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-io", + "sp-mmr-primitives", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", + "static_assertions", + "substrate-wasm-builder", + "xcm", + "xcm-builder", + "xcm-executor", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", +] + +[[package]] +name = "mockall" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" +dependencies = [ + "cfg-if 1.0.0", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiaddr" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" +dependencies = [ + "arrayref", + "bs58", + "byteorder", + "data-encoding", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c346cf9999c631f002d8f977c4eaeaa0e6386f16007202308d0b3757522c2cc" +dependencies = [ + "blake2b_simd", + "blake2s_simd", + "blake3", + "core2", + "digest 0.10.3", + "multihash-derive", + "sha2 0.10.5", + "sha3", + "unsigned-varint", +] + +[[package]] +name = "multihash-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" +dependencies = [ + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "multistream-select" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bc41247ec209813e2fd414d6e16b9d94297dacf3cd613fa6ef09cd4d9755c10" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint", +] + +[[package]] +name = "nalgebra" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462fffe4002f4f2e1f6a9dcf12cc1a6fc0e15989014efc02a941d3e0f5dc2120" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational 0.4.0", + "num-traits", + "rand 0.8.5", + "rand_distr", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "names" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d66043b25d4a6cccb23619d10c19c25304b355a7dccd4a8e11423dd2382146" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" + +[[package]] +name = "netlink-packet-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +dependencies = [ + "anyhow", + "bitflags", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027" +dependencies = [ + "async-io", + "bytes", + "futures", + "libc", + "log", +] + +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "node-inspect" +version = "0.9.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "clap 4.0.26", + "parity-scale-codec", + "sc-cli", + "sc-client-api", + "sc-executor", + "sc-service", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-format" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +dependencies = [ + "arrayvec 0.4.12", + "itoa 0.4.8", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint 0.4.3", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "crc32fast", + "hashbrown", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "orchestra" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aab54694ddaa8a9b703724c6ef04272b2d27bc32d2c855aae5cdd1857216b43" +dependencies = [ + "async-trait", + "dyn-clonable", + "futures", + "futures-timer", + "orchestra-proc-macro", + "pin-project", + "prioritized-metered-channel", + "thiserror", + "tracing", +] + +[[package]] +name = "orchestra-proc-macro" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a702b2f6bf592b3eb06c00d80d05afaf7a8eff6b41bb361e397d799acc21b45a" +dependencies = [ + "expander 0.0.6", + "itertools", + "petgraph", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ordered-float" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" + +[[package]] +name = "pallet-aura" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-aura", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-authority-discovery" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-authority-discovery", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-authorship" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-authorship", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-babe" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-babe", + "sp-consensus-vrf", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-bags-list" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "pallet-balances" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-beefy" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "beefy-primitives", + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-beefy-mmr" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "beefy-merkle-tree", + "beefy-primitives", + "frame-support", + "frame-system", + "log", + "pallet-beefy", + "pallet-mmr", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-bounties" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-bridge-beefy" +version = "0.1.0" +dependencies = [ + "beefy-merkle-tree", + "beefy-primitives", + "bp-beefy", + "bp-runtime", + "bp-test-utils", + "ckb-merkle-mountain-range", + "frame-support", + "frame-system", + "log", + "pallet-beefy-mmr", + "pallet-mmr", + "parity-scale-codec", + "primitive-types", + "rand 0.8.5", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-bridge-grandpa" +version = "0.1.0" +dependencies = [ + "bp-header-chain", + "bp-runtime", + "bp-test-utils", + "finality-grandpa", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-finality-grandpa", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-bridge-messages" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-runtime", + "bp-test-utils", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "num-traits", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-bridge-parachains" +version = "0.1.0" +dependencies = [ + "bp-header-chain", + "bp-parachains", + "bp-polkadot-core", + "bp-runtime", + "bp-test-utils", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-bridge-grandpa", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-bridge-relayers" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-relayers", + "bp-runtime", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-bridge-messages", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-child-bounties" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-bounties", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-collective" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-conviction-voting" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "assert_matches", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-democracy" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-election-provider-multi-phase" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +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", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-npos-elections", + "sp-runtime", + "sp-std", + "static_assertions", + "strum 0.24.1", +] + +[[package]] +name = "pallet-election-provider-support-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-system", + "parity-scale-codec", + "sp-npos-elections", + "sp-runtime", +] + +[[package]] +name = "pallet-elections-phragmen" +version = "5.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-npos-elections", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-fast-unstake" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-gilt" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-grandpa" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-core", + "sp-finality-grandpa", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-identity" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "enumflags2", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-im-online" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-indices" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-membership" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-mmr" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "ckb-merkle-mountain-range", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-mmr-primitives", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-mmr-rpc" +version = "3.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "jsonrpsee", + "parity-scale-codec", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-mmr-primitives", + "sp-runtime", +] + +[[package]] +name = "pallet-multisig" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-nomination-pools" +version = "1.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-nomination-pools-runtime-api" +version = "1.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-std", +] + +[[package]] +name = "pallet-offences" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-preimage" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-proxy" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-randomness-collective-flip" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "safe-mix", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-ranked-collective" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-recovery" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-referenda" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-scheduler" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-session" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-shift-session-manager" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-society" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "rand_chacha 0.2.2", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "serde", + "sp-application-crypto", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-staking-reward-curve" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pallet-staking-reward-fn" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "log", + "sp-arithmetic", +] + +[[package]] +name = "pallet-sudo" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-timestamp" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "pallet-tips" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-transaction-payment" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-transaction-payment-rpc" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "jsonrpsee", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", +] + +[[package]] +name = "pallet-transaction-payment-rpc-runtime-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "pallet-transaction-payment", + "parity-scale-codec", + "sp-api", + "sp-runtime", +] + +[[package]] +name = "pallet-treasury" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-utility" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-vesting" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-whitelist" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-xcm" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + +[[package]] +name = "parachain-info" +version = "0.1.0" +source = "git+https://github.com/paritytech//cumulus?branch=cumulus-locked-for-gav-xcm-v3-and-bridges#6ece1b652f805ffa7bd0052c77a38bd9fce11542" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "parachains-relay" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "bp-parachains", + "bp-polkadot-core", + "futures", + "log", + "parity-scale-codec", + "relay-substrate-client", + "relay-utils", + "sp-core", +] + +[[package]] +name = "parity-db" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8fdb726a43661fa54b43e7114e6b88b2289cae388eb3ad766d9d1754d83fce" +dependencies = [ + "blake2-rfc", + "crc32fast", + "fs2", + "hex", + "libc", + "log", + "lz4", + "memmap2", + "parking_lot 0.12.1", + "rand 0.8.5", + "snap", +] + +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parity-send-wrapper" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" + +[[package]] +name = "parity-util-mem" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" +dependencies = [ + "cfg-if 1.0.0", + "ethereum-types", + "hashbrown", + "impl-trait-for-tuples", + "lru 0.8.1", + "parity-util-mem-derive", + "parking_lot 0.12.1", + "primitive-types", + "smallvec", + "winapi", +] + +[[package]] +name = "parity-util-mem-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] + +[[package]] +name = "parity-wasm" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.1", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.32.0", +] + +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac 0.8.0", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac 0.11.1", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1 0.8.2", +] + +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +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 = "pkg-config" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" + +[[package]] +name = "platforms" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" + +[[package]] +name = "polkadot-approval-distribution" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "rand 0.8.5", + "tracing-gum", +] + +[[package]] +name = "polkadot-availability-bitfield-distribution" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "polkadot-node-network-protocol", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "rand 0.8.5", + "tracing-gum", +] + +[[package]] +name = "polkadot-availability-distribution" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "derive_more", + "fatality", + "futures", + "lru 0.8.1", + "parity-scale-codec", + "polkadot-erasure-coding", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "rand 0.8.5", + "sp-core", + "sp-keystore", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-availability-recovery" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "fatality", + "futures", + "lru 0.8.1", + "parity-scale-codec", + "polkadot-erasure-coding", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "rand 0.8.5", + "sc-network", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-cli" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "clap 4.0.26", + "frame-benchmarking-cli", + "futures", + "log", + "polkadot-client", + "polkadot-node-core-pvf", + "polkadot-node-metrics", + "polkadot-performance-test", + "polkadot-service", + "sc-cli", + "sc-service", + "sc-sysinfo", + "sc-tracing", + "sp-core", + "sp-keyring", + "sp-trie", + "substrate-build-script-utils", + "thiserror", + "try-runtime-cli", +] + +[[package]] +name = "polkadot-client" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "beefy-primitives", + "frame-benchmarking", + "frame-benchmarking-cli", + "frame-system", + "frame-system-rpc-runtime-api", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "polkadot-core-primitives", + "polkadot-node-core-parachains-inherent", + "polkadot-primitives", + "polkadot-runtime", + "polkadot-runtime-common", + "sc-client-api", + "sc-consensus", + "sc-executor", + "sc-service", + "sp-api", + "sp-authority-discovery", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-consensus-babe", + "sp-core", + "sp-finality-grandpa", + "sp-inherents", + "sp-keyring", + "sp-mmr-primitives", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-storage", + "sp-timestamp", + "sp-transaction-pool", +] + +[[package]] +name = "polkadot-collator-protocol" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "always-assert", + "bitvec", + "fatality", + "futures", + "futures-timer", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "sp-core", + "sp-keystore", + "sp-runtime", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-core-primitives" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "parity-scale-codec", + "parity-util-mem", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "polkadot-dispute-distribution" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "derive_more", + "fatality", + "futures", + "futures-timer", + "indexmap", + "lru 0.8.1", + "parity-scale-codec", + "polkadot-erasure-coding", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "sc-network", + "sp-application-crypto", + "sp-keystore", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-erasure-coding" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "parity-scale-codec", + "polkadot-node-primitives", + "polkadot-primitives", + "reed-solomon-novelpoly", + "sp-core", + "sp-trie", + "thiserror", +] + +[[package]] +name = "polkadot-gossip-support" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "futures-timer", + "polkadot-node-network-protocol", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "rand 0.8.5", + "rand_chacha 0.3.1", + "sc-network", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "tracing-gum", +] + +[[package]] +name = "polkadot-network-bridge" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "always-assert", + "async-trait", + "bytes", + "fatality", + "futures", + "parity-scale-codec", + "parking_lot 0.12.1", + "polkadot-node-network-protocol", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-primitives", + "sc-network", + "sc-network-common", + "sp-consensus", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-collation-generation" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "parity-scale-codec", + "polkadot-erasure-coding", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "sp-core", + "sp-maybe-compressed-blob", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-approval-voting" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bitvec", + "derive_more", + "futures", + "futures-timer", + "kvdb", + "lru 0.8.1", + "merlin", + "parity-scale-codec", + "polkadot-node-jaeger", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-primitives", + "sc-keystore", + "schnorrkel", + "sp-application-crypto", + "sp-consensus", + "sp-consensus-slots", + "sp-runtime", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-av-store" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bitvec", + "futures", + "futures-timer", + "kvdb", + "parity-scale-codec", + "polkadot-erasure-coding", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-primitives", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-backing" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bitvec", + "fatality", + "futures", + "polkadot-erasure-coding", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "polkadot-statement-table", + "sp-keystore", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-bitfield-signing" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "sp-keystore", + "thiserror", + "tracing-gum", + "wasm-timer", +] + +[[package]] +name = "polkadot-node-core-candidate-validation" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "async-trait", + "futures", + "parity-scale-codec", + "polkadot-node-core-pvf", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-parachain", + "polkadot-primitives", + "sp-maybe-compressed-blob", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-chain-api" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "sc-client-api", + "sc-consensus-babe", + "sp-blockchain", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-chain-selection" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "futures-timer", + "kvdb", + "parity-scale-codec", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-dispute-coordinator" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "fatality", + "futures", + "kvdb", + "lru 0.8.1", + "parity-scale-codec", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "sc-keystore", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-parachains-inherent" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "polkadot-node-subsystem", + "polkadot-primitives", + "sp-blockchain", + "sp-inherents", + "sp-runtime", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-provisioner" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bitvec", + "fatality", + "futures", + "futures-timer", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "rand 0.8.5", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-pvf" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "always-assert", + "assert_matches", + "async-process", + "async-std", + "futures", + "futures-timer", + "parity-scale-codec", + "pin-project", + "polkadot-core-primitives", + "polkadot-node-metrics", + "polkadot-parachain", + "rand 0.8.5", + "rayon", + "sc-executor", + "sc-executor-common", + "sc-executor-wasmtime", + "slotmap", + "sp-core", + "sp-externalities", + "sp-io", + "sp-maybe-compressed-blob", + "sp-tracing", + "sp-wasm-interface", + "tempfile", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-pvf-checker" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-primitives", + "sp-keystore", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-core-runtime-api" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "futures", + "memory-lru", + "parity-util-mem", + "polkadot-node-subsystem", + "polkadot-node-subsystem-types", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "sp-consensus-babe", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-jaeger" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "async-std", + "lazy_static", + "log", + "mick-jaeger", + "parity-scale-codec", + "parking_lot 0.12.1", + "polkadot-node-primitives", + "polkadot-primitives", + "sc-network", + "sp-core", + "thiserror", +] + +[[package]] +name = "polkadot-node-metrics" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bs58", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "polkadot-primitives", + "prioritized-metered-channel", + "sc-cli", + "sc-service", + "sc-tracing", + "substrate-prometheus-endpoint", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-network-protocol" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "async-trait", + "derive_more", + "fatality", + "futures", + "hex", + "parity-scale-codec", + "polkadot-node-jaeger", + "polkadot-node-primitives", + "polkadot-primitives", + "rand 0.8.5", + "sc-authority-discovery", + "sc-network", + "sc-network-common", + "strum 0.24.1", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-node-primitives" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bounded-vec", + "futures", + "parity-scale-codec", + "polkadot-parachain", + "polkadot-primitives", + "schnorrkel", + "serde", + "sp-application-crypto", + "sp-consensus-babe", + "sp-consensus-vrf", + "sp-core", + "sp-keystore", + "sp-maybe-compressed-blob", + "thiserror", + "zstd", +] + +[[package]] +name = "polkadot-node-subsystem" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "polkadot-node-jaeger", + "polkadot-node-subsystem-types", + "polkadot-overseer", +] + +[[package]] +name = "polkadot-node-subsystem-types" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "async-trait", + "derive_more", + "futures", + "orchestra", + "polkadot-node-jaeger", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-primitives", + "polkadot-statement-table", + "sc-network", + "smallvec", + "sp-api", + "sp-authority-discovery", + "sp-consensus-babe", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "polkadot-node-subsystem-util" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "async-trait", + "derive_more", + "fatality", + "futures", + "itertools", + "kvdb", + "lru 0.8.1", + "parity-db", + "parity-scale-codec", + "parity-util-mem", + "parking_lot 0.11.2", + "pin-project", + "polkadot-node-jaeger", + "polkadot-node-metrics", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-overseer", + "polkadot-primitives", + "prioritized-metered-channel", + "rand 0.8.5", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-overseer" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "lru 0.8.1", + "orchestra", + "parity-util-mem", + "parking_lot 0.12.1", + "polkadot-node-metrics", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem-types", + "polkadot-primitives", + "sc-client-api", + "sp-api", + "sp-core", + "tracing-gum", +] + +[[package]] +name = "polkadot-parachain" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "derive_more", + "frame-support", + "parity-scale-codec", + "parity-util-mem", + "polkadot-core-primitives", + "scale-info", + "serde", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "polkadot-performance-test" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "env_logger 0.9.0", + "kusama-runtime", + "log", + "polkadot-erasure-coding", + "polkadot-node-core-pvf", + "polkadot-node-primitives", + "quote", + "thiserror", +] + +[[package]] +name = "polkadot-primitives" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bitvec", + "frame-system", + "hex-literal", + "parity-scale-codec", + "parity-util-mem", + "polkadot-core-primitives", + "polkadot-parachain", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-authority-discovery", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-trie", + "sp-version", +] + +[[package]] +name = "polkadot-rpc" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "beefy-gadget", + "beefy-gadget-rpc", + "jsonrpsee", + "pallet-mmr-rpc", + "pallet-transaction-payment-rpc", + "polkadot-primitives", + "sc-chain-spec", + "sc-client-api", + "sc-consensus-babe", + "sc-consensus-babe-rpc", + "sc-consensus-epochs", + "sc-finality-grandpa", + "sc-finality-grandpa-rpc", + "sc-rpc", + "sc-sync-state-rpc", + "sc-transaction-pool-api", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-consensus-babe", + "sp-keystore", + "sp-runtime", + "substrate-frame-rpc-system", + "substrate-state-trie-migration-rpc", +] + +[[package]] +name = "polkadot-runtime" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "beefy-primitives", + "bitvec", + "frame-election-provider-support", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "log", + "pallet-authority-discovery", + "pallet-authorship", + "pallet-babe", + "pallet-bags-list", + "pallet-balances", + "pallet-bounties", + "pallet-child-bounties", + "pallet-collective", + "pallet-democracy", + "pallet-election-provider-multi-phase", + "pallet-elections-phragmen", + "pallet-fast-unstake", + "pallet-grandpa", + "pallet-identity", + "pallet-im-online", + "pallet-indices", + "pallet-membership", + "pallet-multisig", + "pallet-nomination-pools", + "pallet-nomination-pools-runtime-api", + "pallet-offences", + "pallet-preimage", + "pallet-proxy", + "pallet-scheduler", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "pallet-tips", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-treasury", + "pallet-utility", + "pallet-vesting", + "pallet-xcm", + "parity-scale-codec", + "polkadot-primitives", + "polkadot-runtime-common", + "polkadot-runtime-constants", + "polkadot-runtime-parachains", + "rustc-hex", + "scale-info", + "serde", + "serde_derive", + "smallvec", + "sp-api", + "sp-authority-discovery", + "sp-block-builder", + "sp-consensus-babe", + "sp-core", + "sp-inherents", + "sp-io", + "sp-mmr-primitives", + "sp-npos-elections", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "sp-transaction-pool", + "sp-version", + "static_assertions", + "substrate-wasm-builder", + "xcm", + "xcm-builder", + "xcm-executor", +] + +[[package]] +name = "polkadot-runtime-common" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "beefy-primitives", + "bitvec", + "frame-election-provider-support", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "libsecp256k1", + "log", + "pallet-authorship", + "pallet-bags-list", + "pallet-balances", + "pallet-beefy-mmr", + "pallet-election-provider-multi-phase", + "pallet-session", + "pallet-staking", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-treasury", + "pallet-vesting", + "parity-scale-codec", + "polkadot-primitives", + "polkadot-runtime-parachains", + "rustc-hex", + "scale-info", + "serde", + "serde_derive", + "slot-range-helper", + "sp-api", + "sp-core", + "sp-inherents", + "sp-io", + "sp-npos-elections", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "static_assertions", + "xcm", +] + +[[package]] +name = "polkadot-runtime-constants" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "frame-support", + "polkadot-primitives", + "polkadot-runtime-common", + "smallvec", + "sp-runtime", +] + +[[package]] +name = "polkadot-runtime-metrics" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bs58", + "parity-scale-codec", + "polkadot-primitives", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "polkadot-runtime-parachains" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "bitflags", + "bitvec", + "derive_more", + "frame-support", + "frame-system", + "log", + "pallet-authority-discovery", + "pallet-authorship", + "pallet-babe", + "pallet-balances", + "pallet-session", + "pallet-staking", + "pallet-timestamp", + "pallet-vesting", + "parity-scale-codec", + "polkadot-primitives", + "polkadot-runtime-metrics", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rustc-hex", + "scale-info", + "serde", + "sp-api", + "sp-core", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "xcm", + "xcm-executor", +] + +[[package]] +name = "polkadot-service" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "async-trait", + "beefy-gadget", + "beefy-primitives", + "frame-support", + "frame-system-rpc-runtime-api", + "futures", + "hex-literal", + "kvdb", + "kvdb-rocksdb", + "lru 0.8.1", + "pallet-babe", + "pallet-im-online", + "pallet-staking", + "pallet-transaction-payment-rpc-runtime-api", + "parity-db", + "polkadot-approval-distribution", + "polkadot-availability-bitfield-distribution", + "polkadot-availability-distribution", + "polkadot-availability-recovery", + "polkadot-client", + "polkadot-collator-protocol", + "polkadot-dispute-distribution", + "polkadot-gossip-support", + "polkadot-network-bridge", + "polkadot-node-collation-generation", + "polkadot-node-core-approval-voting", + "polkadot-node-core-av-store", + "polkadot-node-core-backing", + "polkadot-node-core-bitfield-signing", + "polkadot-node-core-candidate-validation", + "polkadot-node-core-chain-api", + "polkadot-node-core-chain-selection", + "polkadot-node-core-dispute-coordinator", + "polkadot-node-core-parachains-inherent", + "polkadot-node-core-provisioner", + "polkadot-node-core-pvf-checker", + "polkadot-node-core-runtime-api", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-types", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-parachain", + "polkadot-primitives", + "polkadot-rpc", + "polkadot-runtime", + "polkadot-runtime-constants", + "polkadot-runtime-parachains", + "polkadot-statement-distribution", + "sc-authority-discovery", + "sc-basic-authorship", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-client-db", + "sc-consensus", + "sc-consensus-babe", + "sc-consensus-slots", + "sc-executor", + "sc-finality-grandpa", + "sc-keystore", + "sc-network", + "sc-network-common", + "sc-offchain", + "sc-service", + "sc-sync-state-rpc", + "sc-sysinfo", + "sc-telemetry", + "sc-transaction-pool", + "serde", + "serde_json", + "sp-api", + "sp-authority-discovery", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-consensus-babe", + "sp-core", + "sp-finality-grandpa", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-state-machine", + "sp-storage", + "sp-timestamp", + "sp-transaction-pool", + "sp-trie", + "substrate-prometheus-endpoint", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-statement-distribution" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "arrayvec 0.5.2", + "fatality", + "futures", + "indexmap", + "parity-scale-codec", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-util", + "polkadot-primitives", + "sp-keystore", + "sp-staking", + "thiserror", + "tracing-gum", +] + +[[package]] +name = "polkadot-statement-table" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "parity-scale-codec", + "polkadot-primitives", + "sp-core", +] + +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + +[[package]] +name = "predicates" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6bd09a7f7e68f3f0bf710fb7ab9c4615a488b58b5f653382a687701e458c92" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" + +[[package]] +name = "predicates-tree" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "primitive-types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cfd65aea0c5fa0bfcc7c9e7ca828c921ef778f43d325325ec84bda371bfa75a" +dependencies = [ + "fixed-hash 0.8.0", + "impl-codec", + "impl-rlp", + "impl-serde 0.4.0", + "scale-info", + "uint", +] + +[[package]] +name = "prioritized-metered-channel" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382698e48a268c832d0b181ed438374a6bb708a82a8ca273bb0f61c74cf209c4" +dependencies = [ + "coarsetime", + "crossbeam-queue", + "derive_more", + "futures", + "futures-timer", + "nanorand", + "thiserror", + "tracing", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" +dependencies = [ + "cfg-if 1.0.0", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.11.2", + "thiserror", +] + +[[package]] +name = "prometheus-client" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83cd1b99916654a69008fd66b4f9397fbe08e6e51dfe23d4417acf5d3b8cb87c" +dependencies = [ + "dtoa", + "itoa 1.0.1", + "parking_lot 0.12.1", + "prometheus-client-derive-text-encode", +] + +[[package]] +name = "prometheus-client-derive-text-encode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a455fbcb954c1a7decf3c586e860fd7889cddf4b8e164be736dbac95a953cd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" +dependencies = [ + "bytes", + "heck 0.4.0", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-codec" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011ae9ff8359df7915f97302d591cdd9e0e27fbd5a4ddc5bd13b71079bb20987" +dependencies = [ + "asynchronous-codec", + "bytes", + "prost", + "thiserror", + "unsigned-varint", +] + +[[package]] +name = "prost-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "psm" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd136ff4382c4753fc061cb9e4712ab2af263376b95bbd5bd8cd50c020b78e69" +dependencies = [ + "cc", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quicksink" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project-lite 0.1.12", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg 0.2.1", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", +] + +[[package]] +name = "rand_distr" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.3", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.3", + "redox_syscall", +] + +[[package]] +name = "reed-solomon-novelpoly" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd8f48b2066e9f69ab192797d66da804d1935bf22763204ed3675740cb0f221" +dependencies = [ + "derive_more", + "fs-err", + "itertools", + "static_init 0.5.2", + "thiserror", +] + +[[package]] +name = "ref-cast" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regalloc2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43a209257d978ef079f3d446331d0f1794f5e0fc19b306a199983857833a779" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "relay-bridge-hub-rococo-client" +version = "0.1.0" +dependencies = [ + "bp-bridge-hub-rococo", + "bp-bridge-hub-wococo", + "bp-header-chain", + "bp-messages", + "bp-polkadot-core", + "bp-runtime", + "bridge-runtime-common", + "parity-scale-codec", + "relay-substrate-client", + "scale-info", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", +] + +[[package]] +name = "relay-bridge-hub-wococo-client" +version = "0.1.0" +dependencies = [ + "bp-bridge-hub-wococo", + "bp-messages", + "parity-scale-codec", + "relay-bridge-hub-rococo-client", + "relay-substrate-client", + "scale-info", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "relay-kusama-client" +version = "0.1.0" +dependencies = [ + "bp-kusama", + "frame-support", + "relay-substrate-client", + "relay-utils", + "sp-core", +] + +[[package]] +name = "relay-millau-client" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-millau", + "frame-support", + "frame-system", + "millau-runtime", + "pallet-transaction-payment", + "parity-scale-codec", + "relay-substrate-client", + "relay-utils", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "relay-polkadot-client" +version = "0.1.0" +dependencies = [ + "bp-polkadot", + "frame-support", + "relay-substrate-client", + "relay-utils", + "sp-core", +] + +[[package]] +name = "relay-rialto-client" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-rialto", + "frame-support", + "frame-system", + "pallet-transaction-payment", + "parity-scale-codec", + "relay-substrate-client", + "relay-utils", + "rialto-runtime", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "relay-rialto-parachain-client" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-rialto-parachain", + "frame-support", + "frame-system", + "pallet-transaction-payment", + "parity-scale-codec", + "relay-substrate-client", + "relay-utils", + "rialto-parachain-runtime", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "relay-rococo-client" +version = "0.1.0" +dependencies = [ + "bp-rococo", + "frame-support", + "relay-substrate-client", + "relay-utils", + "sp-core", +] + +[[package]] +name = "relay-substrate-client" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "bp-header-chain", + "bp-messages", + "bp-runtime", + "finality-relay", + "frame-support", + "frame-system", + "futures", + "jsonrpsee", + "log", + "num-traits", + "pallet-balances", + "pallet-bridge-messages", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "rand 0.7.3", + "relay-utils", + "sc-chain-spec", + "sc-rpc-api", + "sc-transaction-pool-api", + "sp-core", + "sp-finality-grandpa", + "sp-rpc", + "sp-runtime", + "sp-storage", + "sp-trie", + "sp-version", + "thiserror", + "tokio", +] + +[[package]] +name = "relay-utils" +version = "0.1.0" +dependencies = [ + "ansi_term 0.12.1", + "anyhow", + "async-std", + "async-trait", + "backoff 0.2.1", + "bp-runtime", + "env_logger 0.8.4", + "futures", + "isahc", + "jsonpath_lib", + "log", + "num-traits", + "serde_json", + "substrate-prometheus-endpoint", + "sysinfo", + "thiserror", + "time 0.3.7", + "tokio", +] + +[[package]] +name = "relay-westend-client" +version = "0.1.0" +dependencies = [ + "bp-westend", + "frame-support", + "relay-substrate-client", + "relay-utils", + "sp-core", +] + +[[package]] +name = "relay-wococo-client" +version = "0.1.0" +dependencies = [ + "bp-wococo", + "frame-support", + "relay-substrate-client", + "relay-utils", + "sp-core", +] + +[[package]] +name = "remote-externalities" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "env_logger 0.9.0", + "log", + "parity-scale-codec", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-version", + "substrate-rpc-client", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error 1.2.3", +] + +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + +[[package]] +name = "rialto-bridge-node" +version = "0.1.0" +dependencies = [ + "beefy-primitives", + "clap 4.0.26", + "frame-benchmarking", + "frame-benchmarking-cli", + "frame-support", + "node-inspect", + "polkadot-node-core-pvf", + "polkadot-primitives", + "polkadot-runtime-parachains", + "polkadot-service", + "rialto-runtime", + "sc-cli", + "sc-executor", + "sc-service", + "serde_json", + "sp-authority-discovery", + "sp-consensus-babe", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "substrate-build-script-utils", +] + +[[package]] +name = "rialto-parachain-collator" +version = "0.1.0" +dependencies = [ + "clap 4.0.26", + "cumulus-client-cli", + "cumulus-client-consensus-aura", + "cumulus-client-consensus-common", + "cumulus-client-network", + "cumulus-client-service", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-relay-chain-inprocess-interface", + "cumulus-relay-chain-interface", + "cumulus-relay-chain-minimal-node", + "frame-benchmarking", + "frame-benchmarking-cli", + "jsonrpsee", + "log", + "pallet-transaction-payment-rpc", + "parity-scale-codec", + "polkadot-cli", + "polkadot-primitives", + "polkadot-service", + "rialto-parachain-runtime", + "sc-basic-authorship", + "sc-chain-spec", + "sc-cli", + "sc-client-api", + "sc-consensus", + "sc-executor", + "sc-network", + "sc-rpc", + "sc-rpc-api", + "sc-service", + "sc-sysinfo", + "sc-telemetry", + "sc-tracing", + "sc-transaction-pool", + "serde", + "sp-api", + "sp-block-builder", + "sp-consensus", + "sp-consensus-aura", + "sp-core", + "sp-keystore", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-timestamp", + "sp-transaction-pool", + "substrate-build-script-utils", + "substrate-frame-rpc-system", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "rialto-parachain-runtime" +version = "0.1.0" +dependencies = [ + "bp-messages", + "bp-millau", + "bp-relayers", + "bp-rialto-parachain", + "bp-runtime", + "bridge-runtime-common", + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-timestamp", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "hex-literal", + "log", + "pallet-aura", + "pallet-balances", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-relayers", + "pallet-randomness-collective-flip", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-xcm", + "parachain-info", + "parity-scale-codec", + "polkadot-parachain", + "scale-info", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", + "substrate-wasm-builder", + "xcm", + "xcm-builder", + "xcm-executor", +] + +[[package]] +name = "rialto-runtime" +version = "0.1.0" +dependencies = [ + "beefy-primitives", + "bp-messages", + "bp-millau", + "bp-relayers", + "bp-rialto", + "bp-runtime", + "bridge-runtime-common", + "env_logger 0.8.4", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "log", + "pallet-authority-discovery", + "pallet-babe", + "pallet-balances", + "pallet-beefy", + "pallet-beefy-mmr", + "pallet-bridge-beefy", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-relayers", + "pallet-grandpa", + "pallet-mmr", + "pallet-session", + "pallet-shift-session-manager", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-xcm", + "parity-scale-codec", + "polkadot-primitives", + "polkadot-runtime-common", + "polkadot-runtime-parachains", + "scale-info", + "sp-api", + "sp-authority-discovery", + "sp-block-builder", + "sp-consensus-babe", + "sp-core", + "sp-inherents", + "sp-io", + "sp-mmr-primitives", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", + "static_assertions", + "substrate-wasm-builder", + "xcm", + "xcm-builder", + "xcm-executor", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rocksdb" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" +dependencies = [ + "libc", + "librocksdb-sys", +] + +[[package]] +name = "rpassword" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b763cb66df1c928432cc35053f8bd4cec3335d8559fc16010017d16b3c1680" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "rtnetlink" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +dependencies = [ + "async-global-executor", + "futures", + "log", + "netlink-packet-route", + "netlink-proto", + "nix", + "thiserror", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.4", +] + +[[package]] +name = "rustix" +version = "0.35.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.36.1", +] + +[[package]] +name = "rustls" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + +[[package]] +name = "rw-stream-sink" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "safe-mix" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3d055a2582e6b00ed7a31c1524040aa391092bf636328350813f3a0605215c" +dependencies = [ + "rustc_version 0.2.3", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sc-allocator" +version = "4.1.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "log", + "sp-core", + "sp-wasm-interface", + "thiserror", +] + +[[package]] +name = "sc-authority-discovery" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "ip_network", + "libp2p", + "log", + "parity-scale-codec", + "prost", + "prost-build", + "rand 0.7.3", + "sc-client-api", + "sc-network-common", + "sp-api", + "sp-authority-discovery", + "sp-blockchain", + "sp-core", + "sp-keystore", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-basic-authorship" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "sc-block-builder", + "sc-client-api", + "sc-proposer-metrics", + "sc-telemetry", + "sc-transaction-pool-api", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-inherents", + "sp-runtime", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "sc-block-builder" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "sc-client-api", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-state-machine", +] + +[[package]] +name = "sc-chain-spec" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "impl-trait-for-tuples", + "memmap2", + "parity-scale-codec", + "sc-chain-spec-derive", + "sc-network-common", + "sc-telemetry", + "serde", + "serde_json", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "sc-chain-spec-derive" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sc-cli" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "chrono", + "clap 4.0.26", + "fdlimit", + "futures", + "libp2p", + "log", + "names", + "parity-scale-codec", + "rand 0.7.3", + "regex", + "rpassword", + "sc-client-api", + "sc-client-db", + "sc-keystore", + "sc-network", + "sc-network-common", + "sc-service", + "sc-telemetry", + "sc-tracing", + "sc-utils", + "serde", + "serde_json", + "sp-blockchain", + "sp-core", + "sp-keyring", + "sp-keystore", + "sp-panic-handler", + "sp-runtime", + "sp-version", + "thiserror", + "tiny-bip39", + "tokio", +] + +[[package]] +name = "sc-client-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "fnv", + "futures", + "hash-db", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-executor", + "sc-transaction-pool-api", + "sc-utils", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-database", + "sp-externalities", + "sp-keystore", + "sp-runtime", + "sp-state-machine", + "sp-storage", + "sp-trie", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "sc-client-db" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "hash-db", + "kvdb", + "kvdb-memorydb", + "kvdb-rocksdb", + "linked-hash-map", + "log", + "parity-db", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-client-api", + "sc-state-db", + "sp-arithmetic", + "sp-blockchain", + "sp-core", + "sp-database", + "sp-runtime", + "sp-state-machine", + "sp-trie", +] + +[[package]] +name = "sc-consensus" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "libp2p", + "log", + "parking_lot 0.12.1", + "sc-client-api", + "sc-utils", + "serde", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", + "sp-state-machine", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-consensus-aura" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures", + "log", + "parity-scale-codec", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-consensus-slots", + "sc-telemetry", + "sp-api", + "sp-application-crypto", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-consensus-aura", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-consensus-babe" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "fork-tree", + "futures", + "log", + "merlin", + "num-bigint 0.2.6", + "num-rational 0.2.4", + "num-traits", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.7.3", + "sc-client-api", + "sc-consensus", + "sc-consensus-epochs", + "sc-consensus-slots", + "sc-keystore", + "sc-telemetry", + "schnorrkel", + "serde", + "sp-api", + "sp-application-crypto", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-consensus-babe", + "sp-consensus-slots", + "sp-consensus-vrf", + "sp-core", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-version", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-consensus-babe-rpc" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "jsonrpsee", + "sc-consensus-babe", + "sc-consensus-epochs", + "sc-rpc-api", + "serde", + "sp-api", + "sp-application-crypto", + "sp-blockchain", + "sp-consensus", + "sp-consensus-babe", + "sp-core", + "sp-keystore", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-consensus-epochs" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "fork-tree", + "parity-scale-codec", + "sc-client-api", + "sc-consensus", + "sp-blockchain", + "sp-runtime", +] + +[[package]] +name = "sc-consensus-slots" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "sc-client-api", + "sc-consensus", + "sc-telemetry", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-state-machine", + "thiserror", +] + +[[package]] +name = "sc-executor" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "lazy_static", + "lru 0.7.8", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-executor-common", + "sc-executor-wasmi", + "sc-executor-wasmtime", + "sp-api", + "sp-core", + "sp-core-hashing-proc-macro", + "sp-externalities", + "sp-io", + "sp-panic-handler", + "sp-runtime-interface", + "sp-tasks", + "sp-trie", + "sp-version", + "sp-wasm-interface", + "tracing", + "wasmi", +] + +[[package]] +name = "sc-executor-common" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "environmental", + "parity-scale-codec", + "sc-allocator", + "sp-maybe-compressed-blob", + "sp-sandbox", + "sp-wasm-interface", + "thiserror", + "wasm-instrument", + "wasmi", +] + +[[package]] +name = "sc-executor-wasmi" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "log", + "parity-scale-codec", + "sc-allocator", + "sc-executor-common", + "sp-runtime-interface", + "sp-sandbox", + "sp-wasm-interface", + "wasmi", +] + +[[package]] +name = "sc-executor-wasmtime" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "log", + "once_cell", + "parity-scale-codec", + "parity-wasm", + "rustix", + "sc-allocator", + "sc-executor-common", + "sp-runtime-interface", + "sp-sandbox", + "sp-wasm-interface", + "wasmtime", +] + +[[package]] +name = "sc-finality-grandpa" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "ahash", + "array-bytes", + "async-trait", + "dyn-clone", + "finality-grandpa", + "fork-tree", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.8.5", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-consensus", + "sc-keystore", + "sc-network", + "sc-network-common", + "sc-network-gossip", + "sc-telemetry", + "sc-utils", + "serde_json", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-finality-grandpa", + "sp-keystore", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-finality-grandpa-rpc" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "finality-grandpa", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-client-api", + "sc-finality-grandpa", + "sc-rpc", + "serde", + "serde_json", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-informant" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "ansi_term 0.12.1", + "futures", + "futures-timer", + "log", + "parity-util-mem", + "sc-client-api", + "sc-network-common", + "sc-transaction-pool-api", + "sp-blockchain", + "sp-runtime", +] + +[[package]] +name = "sc-keystore" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "async-trait", + "parking_lot 0.12.1", + "serde_json", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "thiserror", +] + +[[package]] +name = "sc-network" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "async-trait", + "asynchronous-codec", + "bitflags", + "bytes", + "cid", + "either", + "fnv", + "fork-tree", + "futures", + "futures-timer", + "ip_network", + "libp2p", + "linked-hash-map", + "linked_hash_set", + "log", + "lru 0.7.8", + "parity-scale-codec", + "parking_lot 0.12.1", + "pin-project", + "prost", + "rand 0.7.3", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-network-common", + "sc-peerset", + "sc-utils", + "serde", + "serde_json", + "smallvec", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", + "unsigned-varint", + "zeroize", +] + +[[package]] +name = "sc-network-bitswap" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "cid", + "futures", + "libp2p", + "log", + "prost", + "prost-build", + "sc-client-api", + "sc-network-common", + "sp-blockchain", + "sp-runtime", + "thiserror", + "unsigned-varint", + "void", +] + +[[package]] +name = "sc-network-common" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "bitflags", + "bytes", + "futures", + "futures-timer", + "libp2p", + "linked_hash_set", + "parity-scale-codec", + "prost-build", + "sc-consensus", + "sc-peerset", + "serde", + "smallvec", + "sp-blockchain", + "sp-consensus", + "sp-finality-grandpa", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-network-gossip" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "ahash", + "futures", + "futures-timer", + "libp2p", + "log", + "lru 0.7.8", + "sc-network-common", + "sc-peerset", + "sp-runtime", + "substrate-prometheus-endpoint", + "tracing", +] + +[[package]] +name = "sc-network-light" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "futures", + "libp2p", + "log", + "parity-scale-codec", + "prost", + "prost-build", + "sc-client-api", + "sc-network-common", + "sc-peerset", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-network-sync" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "fork-tree", + "futures", + "libp2p", + "log", + "lru 0.7.8", + "mockall", + "parity-scale-codec", + "prost", + "prost-build", + "sc-client-api", + "sc-consensus", + "sc-network-common", + "sc-peerset", + "sc-utils", + "smallvec", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-network-transactions" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "futures", + "hex", + "libp2p", + "log", + "parity-scale-codec", + "pin-project", + "sc-network-common", + "sc-peerset", + "sp-consensus", + "sp-runtime", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "sc-offchain" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "bytes", + "fnv", + "futures", + "futures-timer", + "hyper", + "hyper-rustls", + "libp2p", + "num_cpus", + "once_cell", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.7.3", + "sc-client-api", + "sc-network-common", + "sc-peerset", + "sc-utils", + "sp-api", + "sp-core", + "sp-offchain", + "sp-runtime", + "threadpool", + "tracing", +] + +[[package]] +name = "sc-peerset" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "libp2p", + "log", + "sc-utils", + "serde_json", + "wasm-timer", +] + +[[package]] +name = "sc-proposer-metrics" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "log", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "sc-rpc" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "hash-db", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-rpc-api", + "sc-tracing", + "sc-transaction-pool-api", + "sc-utils", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-keystore", + "sp-offchain", + "sp-rpc", + "sp-runtime", + "sp-session", + "sp-version", +] + +[[package]] +name = "sc-rpc-api" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-chain-spec", + "sc-transaction-pool-api", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-tracing", + "sp-version", + "thiserror", +] + +[[package]] +name = "sc-rpc-server" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "jsonrpsee", + "log", + "serde_json", + "substrate-prometheus-endpoint", + "tokio", +] + +[[package]] +name = "sc-rpc-spec-v2" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "hex", + "jsonrpsee", + "parity-scale-codec", + "sc-chain-spec", + "sc-transaction-pool-api", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-service" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "directories", + "exit-future", + "futures", + "futures-timer", + "hash-db", + "jsonrpsee", + "log", + "parity-scale-codec", + "parity-util-mem", + "parking_lot 0.12.1", + "pin-project", + "rand 0.7.3", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-client-db", + "sc-consensus", + "sc-executor", + "sc-informant", + "sc-keystore", + "sc-network", + "sc-network-bitswap", + "sc-network-common", + "sc-network-light", + "sc-network-sync", + "sc-network-transactions", + "sc-offchain", + "sc-rpc", + "sc-rpc-server", + "sc-rpc-spec-v2", + "sc-sysinfo", + "sc-telemetry", + "sc-tracing", + "sc-transaction-pool", + "sc-transaction-pool-api", + "sc-utils", + "serde", + "serde_json", + "sp-api", + "sp-application-crypto", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-externalities", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "sp-session", + "sp-state-machine", + "sp-storage", + "sp-tracing", + "sp-transaction-pool", + "sp-transaction-storage-proof", + "sp-trie", + "sp-version", + "static_init 1.0.3", + "substrate-prometheus-endpoint", + "tempfile", + "thiserror", + "tokio", + "tracing", + "tracing-futures", +] + +[[package]] +name = "sc-state-db" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "log", + "parity-scale-codec", + "parity-util-mem", + "parity-util-mem-derive", + "parking_lot 0.12.1", + "sc-client-api", + "sp-core", +] + +[[package]] +name = "sc-sync-state-rpc" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "jsonrpsee", + "parity-scale-codec", + "sc-chain-spec", + "sc-client-api", + "sc-consensus-babe", + "sc-consensus-epochs", + "sc-finality-grandpa", + "serde", + "serde_json", + "sp-blockchain", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-sysinfo" +version = "6.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "libc", + "log", + "rand 0.7.3", + "rand_pcg 0.2.1", + "regex", + "sc-telemetry", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sc-telemetry" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "chrono", + "futures", + "libp2p", + "log", + "parking_lot 0.12.1", + "pin-project", + "rand 0.7.3", + "serde", + "serde_json", + "thiserror", + "wasm-timer", +] + +[[package]] +name = "sc-tracing" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "ansi_term 0.12.1", + "atty", + "chrono", + "lazy_static", + "libc", + "log", + "once_cell", + "parking_lot 0.12.1", + "regex", + "rustc-hash", + "sc-client-api", + "sc-rpc-server", + "sc-tracing-proc-macro", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-tracing", + "thiserror", + "tracing", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "sc-tracing-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sc-transaction-pool" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "linked-hash-map", + "log", + "parity-scale-codec", + "parity-util-mem", + "parking_lot 0.12.1", + "sc-client-api", + "sc-transaction-pool-api", + "sc-utils", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-tracing", + "sp-transaction-pool", + "substrate-prometheus-endpoint", + "thiserror", +] + +[[package]] +name = "sc-transaction-pool-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures", + "log", + "serde", + "sp-blockchain", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sc-utils" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "futures-timer", + "lazy_static", + "log", + "parking_lot 0.12.1", + "prometheus", +] + +[[package]] +name = "scale-info" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8" +dependencies = [ + "bitvec", + "cfg-if 1.0.0", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcd47b380d8c4541044e341dcd9475f55ba37ddc50c908d945fc036a8642496" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + +[[package]] +name = "schnorrkel" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "curve25519-dalek 2.1.3", + "getrandom 0.1.16", + "merlin", + "rand 0.7.3", + "rand_core 0.5.1", + "sha2 0.8.2", + "subtle", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array 0.14.4", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7649a0b3ffb32636e60c7ce0d70511eda9c52c658cd0634e194d5a19943aeff" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7058dc8eaf3f2810d7828680320acda0b25a288f6d288e19278e249bbf74226b" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +dependencies = [ + "indexmap", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "serde_nanos" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e44969a61f5d316be20a42ff97816efb3b407a924d06824c3d8a49fa8450de0e" +dependencies = [ + "serde", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] + +[[package]] +name = "simba" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e82063457853d00243beda9952e910b82593e4b07ae9f721b9278a99a0d3d5c" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", +] + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "slice-group-by" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" + +[[package]] +name = "slot-range-helper" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "enumn", + "parity-scale-codec", + "paste", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "snap" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" + +[[package]] +name = "snow" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774d05a3edae07ce6d68ea6984f3c05e9bba8927e3dd591e3b479e5b03213d0d" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek 4.0.0-pre.1", + "rand_core 0.6.3", + "ring", + "rustc_version 0.4.0", + "sha2 0.10.5", + "subtle", +] + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64", + "bytes", + "flate2", + "futures", + "httparse", + "log", + "rand 0.8.5", + "sha-1 0.9.8", +] + +[[package]] +name = "sp-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "blake2", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-application-crypto" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "5.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-debug-derive", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-authority-discovery" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-authorship" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "parity-scale-codec", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-block-builder" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-blockchain" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures", + "log", + "lru 0.7.8", + "parity-scale-codec", + "parking_lot 0.12.1", + "sp-api", + "sp-consensus", + "sp-database", + "sp-runtime", + "sp-state-machine", + "thiserror", +] + +[[package]] +name = "sp-consensus" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-consensus-aura" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-consensus", + "sp-consensus-slots", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-babe" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "merlin", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-consensus", + "sp-consensus-slots", + "sp-consensus-vrf", + "sp-core", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-slots" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-vrf" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "scale-info", + "schnorrkel", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-core" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "array-bytes", + "base58", + "bitflags", + "blake2", + "byteorder", + "dyn-clonable", + "ed25519-zebra", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde 0.4.0", + "lazy_static", + "libsecp256k1", + "log", + "merlin", + "num-traits", + "parity-scale-codec", + "parity-util-mem", + "parking_lot 0.12.1", + "primitive-types", + "rand 0.7.3", + "regex", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "serde", + "sp-core-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tiny-bip39", + "wasmi", + "zeroize", +] + +[[package]] +name = "sp-core-hashing" +version = "4.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "blake2", + "byteorder", + "digest 0.10.3", + "sha2 0.10.5", + "sha3", + "sp-std", + "twox-hash", +] + +[[package]] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "proc-macro2", + "quote", + "sp-core-hashing", + "syn", +] + +[[package]] +name = "sp-database" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "kvdb", + "parking_lot 0.12.1", +] + +[[package]] +name = "sp-debug-derive" +version = "4.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-externalities" +version = "0.12.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std", + "sp-storage", +] + +[[package]] +name = "sp-finality-grandpa" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "finality-grandpa", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", + "thiserror", +] + +[[package]] +name = "sp-io" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "bytes", + "futures", + "hash-db", + "libsecp256k1", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "secp256k1", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-trie", + "sp-wasm-interface", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keyring" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "lazy_static", + "sp-core", + "sp-runtime", + "strum 0.24.1", +] + +[[package]] +name = "sp-keystore" +version = "0.12.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures", + "merlin", + "parity-scale-codec", + "parking_lot 0.12.1", + "schnorrkel", + "serde", + "sp-core", + "sp-externalities", + "thiserror", +] + +[[package]] +name = "sp-maybe-compressed-blob" +version = "4.1.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "thiserror", + "zstd", +] + +[[package]] +name = "sp-mmr-primitives" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-core", + "sp-debug-derive", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-npos-elections" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-offchain" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "sp-api", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "sp-panic-handler" +version = "4.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-rpc" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "rustc-hash", + "serde", + "sp-core", +] + +[[package]] +name = "sp-runtime" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "rand 0.7.3", + "scale-info", + "serde", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std", + "sp-weights", +] + +[[package]] +name = "sp-runtime-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-sandbox" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "log", + "parity-scale-codec", + "sp-core", + "sp-io", + "sp-std", + "sp-wasm-interface", + "wasmi", +] + +[[package]] +name = "sp-session" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-state-machine" +version = "0.12.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "hash-db", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.7.3", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-std", + "sp-trie", + "thiserror", + "tracing", + "trie-root", +] + +[[package]] +name = "sp-std" +version = "4.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" + +[[package]] +name = "sp-storage" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "impl-serde 0.4.0", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-tasks" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "log", + "sp-core", + "sp-externalities", + "sp-io", + "sp-runtime-interface", + "sp-std", +] + +[[package]] +name = "sp-timestamp" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "futures-timer", + "log", + "parity-scale-codec", + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", + "thiserror", +] + +[[package]] +name = "sp-tracing" +version = "5.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "sp-std", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sp-transaction-pool" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "sp-api", + "sp-runtime", +] + +[[package]] +name = "sp-transaction-storage-proof" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-trie", +] + +[[package]] +name = "sp-trie" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "ahash", + "hash-db", + "hashbrown", + "lazy_static", + "lru 0.7.8", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot 0.12.1", + "scale-info", + "sp-core", + "sp-std", + "thiserror", + "tracing", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "5.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "impl-serde 0.4.0", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-core-hashing-proc-macro", + "sp-runtime", + "sp-std", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-wasm-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std", + "wasmi", + "wasmtime", +] + +[[package]] +name = "sp-weights" +version = "4.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "spin" +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.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de151faef619cb7b5c26b32d42bc7ddccac0d202beb7a84344b44e9232b92f7" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "static_init" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11b73400442027c4adedda20a9f9b7945234a5bd8d5f7e86da22bd5d0622369c" +dependencies = [ + "cfg_aliases", + "libc", + "parking_lot 0.11.2", + "static_init_macro 0.5.0", +] + +[[package]] +name = "static_init" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" +dependencies = [ + "bitflags", + "cfg_aliases", + "libc", + "parking_lot 0.11.2", + "parking_lot_core 0.8.5", + "static_init_macro 1.0.2", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2261c91034a1edc3fc4d1b80e89d82714faede0515c14a75da10cb941546bbf" +dependencies = [ + "cfg_aliases", + "memchr", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "static_init_macro" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" +dependencies = [ + "cfg_aliases", + "memchr", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "statrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" +dependencies = [ + "approx", + "lazy_static", + "nalgebra", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "storage-proof-fuzzer" +version = "0.1.0" +dependencies = [ + "bp-runtime", + "env_logger 0.8.4", + "honggfuzz", + "log", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "structopt" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" +dependencies = [ + "clap 2.33.3", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strum" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +dependencies = [ + "strum_macros 0.21.1", +] + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros 0.24.0", +] + +[[package]] +name = "strum_macros" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strum_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "substrate-bip39" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" +dependencies = [ + "hmac 0.11.0", + "pbkdf2 0.8.0", + "schnorrkel", + "sha2 0.9.8", + "zeroize", +] + +[[package]] +name = "substrate-build-script-utils" +version = "3.0.0" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "platforms", +] + +[[package]] +name = "substrate-frame-rpc-system" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "frame-system-rpc-runtime-api", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-client-api", + "sc-rpc-api", + "sc-transaction-pool-api", + "serde_json", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "substrate-prometheus-endpoint" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "futures-util", + "hyper", + "log", + "prometheus", + "thiserror", + "tokio", +] + +[[package]] +name = "substrate-relay" +version = "1.0.1" +dependencies = [ + "anyhow", + "async-std", + "async-trait", + "bp-bridge-hub-rococo", + "bp-bridge-hub-wococo", + "bp-header-chain", + "bp-messages", + "bp-millau", + "bp-polkadot-core", + "bp-rialto", + "bp-rialto-parachain", + "bp-rococo", + "bp-runtime", + "bp-test-utils", + "bp-westend", + "bp-wococo", + "bridge-runtime-common", + "finality-grandpa", + "frame-support", + "futures", + "hex", + "hex-literal", + "log", + "messages-relay", + "millau-runtime", + "num-format", + "num-traits", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "parachains-relay", + "parity-scale-codec", + "polkadot-parachain", + "polkadot-primitives", + "polkadot-runtime-common", + "polkadot-runtime-parachains", + "relay-bridge-hub-rococo-client", + "relay-bridge-hub-wococo-client", + "relay-millau-client", + "relay-rialto-client", + "relay-rialto-parachain-client", + "relay-rococo-client", + "relay-substrate-client", + "relay-utils", + "relay-westend-client", + "relay-wococo-client", + "rialto-parachain-runtime", + "rialto-runtime", + "sp-core", + "sp-keyring", + "sp-runtime", + "sp-version", + "structopt", + "strum 0.21.0", + "substrate-relay-helper", + "tempfile", + "xcm", +] + +[[package]] +name = "substrate-relay-helper" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "async-trait", + "bp-header-chain", + "bp-messages", + "bp-millau", + "bp-parachains", + "bp-polkadot-core", + "bp-rialto", + "bp-rococo", + "bp-runtime", + "bp-wococo", + "bridge-runtime-common", + "finality-grandpa", + "finality-relay", + "frame-support", + "frame-system", + "futures", + "log", + "messages-relay", + "num-traits", + "pallet-balances", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-transaction-payment", + "parachains-relay", + "parity-scale-codec", + "relay-rialto-client", + "relay-rococo-client", + "relay-substrate-client", + "relay-utils", + "relay-wococo-client", + "rialto-runtime", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "substrate-rpc-client" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "async-trait", + "jsonrpsee", + "log", + "sc-rpc-api", + "serde", + "sp-runtime", +] + +[[package]] +name = "substrate-state-trie-migration-rpc" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-client-api", + "sc-rpc-api", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", + "trie-db", +] + +[[package]] +name = "substrate-wasm-builder" +version = "5.0.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "ansi_term 0.12.1", + "build-helper", + "cargo_metadata", + "filetime", + "sp-maybe-compressed-blob", + "strum 0.24.1", + "tempfile", + "toml", + "walkdir", + "wasm-opt", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "sysinfo" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de94457a09609f33fec5e7fceaf907488967c6c7c75d64da6a7ce6ffdb8b5abd" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "core-foundation-sys", + "doc-comment", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + +[[package]] +name = "system-configuration" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75182f12f490e953596550b65ee31bda7c8e043d9386174b353bda50838c3fd" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "thrift" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b82ca8f46f95b3ce96081fe3dd89160fdea970c254bb72925255d1b62aae692e" +dependencies = [ + "byteorder", + "integer-encoding", + "log", + "ordered-float", + "threadpool", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.2+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" +dependencies = [ + "cc", + "fs_extra", + "libc", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +dependencies = [ + "itoa 1.0.1", + "libc", + "num_threads", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.8", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot 0.12.1", + "pin-project-lite 0.2.9", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite 0.2.9", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.2.9", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite 0.2.9", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.9", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-gum" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "polkadot-node-jaeger", + "polkadot-primitives", + "tracing", + "tracing-gum-proc-macro", +] + +[[package]] +name = "tracing-gum-proc-macro" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "expander 0.0.6", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term 0.12.1", + "chrono", + "lazy_static", + "matchers", + "parking_lot 0.11.2", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "trie-db" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004e1e8f92535694b4cb1444dc5a8073ecf0815e3357f729638b9f8fc4062908" +dependencies = [ + "hash-db", + "hashbrown", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" +dependencies = [ + "hash-db", +] + +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand 0.8.5", + "smallvec", + "thiserror", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if 1.0.0", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot 0.12.1", + "resolv-conf", + "smallvec", + "thiserror", + "tracing", + "trust-dns-proto", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "try-runtime-cli" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech//substrate?branch=sv-locked-for-gav-xcm-v3-and-bridges#87f3fdea8f227d33322c439d45a9e1796637e972" +dependencies = [ + "clap 4.0.26", + "log", + "parity-scale-codec", + "remote-externalities", + "sc-chain-spec", + "sc-cli", + "sc-executor", + "sc-service", + "serde", + "sp-core", + "sp-externalities", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-state-machine", + "sp-version", + "sp-weights", + "substrate-rpc-client", + "zstd", +] + +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 1.0.0", + "digest 0.10.3", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "uint" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "unsigned-varint" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures-io", + "futures-util", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna 0.3.0", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "wasm-instrument" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa1dafb3e60065305741e83db35c6c2584bb3725b692b5b66148a38d72ace6cd" +dependencies = [ + "parity-wasm", +] + +[[package]] +name = "wasm-opt" +version = "0.110.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68e8037b4daf711393f4be2056246d12d975651b14d581520ad5d1f19219cec" +dependencies = [ + "anyhow", + "libc", + "strum 0.24.1", + "strum_macros 0.24.0", + "tempfile", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-cxx-sys" +version = "0.110.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91adbad477e97bba3fbd21dd7bfb594e7ad5ceb9169ab1c93ab9cb0ada636b6f" +dependencies = [ + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-sys" +version = "0.110.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4fa5a322a4e6ac22fd141f498d56afbdbf9df5debeac32380d2dcaa3e06941" +dependencies = [ + "anyhow", + "cc", + "cxx", + "cxx-build", + "regex", +] + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmi" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c326c93fbf86419608361a2c925a31754cf109da1b8b55737070b4d6669422" +dependencies = [ + "parity-wasm", + "wasmi-validation", + "wasmi_core", +] + +[[package]] +name = "wasmi-validation" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ff416ad1ff0c42e5a926ed5d5fab74c0f098749aa0ad8b2a34b982ce0e867b" +dependencies = [ + "parity-wasm", +] + +[[package]] +name = "wasmi_core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d20cb3c59b788653d99541c646c561c9dd26506f25c0cebfe810659c54c6d7" +dependencies = [ + "downcast-rs", + "libm", + "memory_units", + "num-rational 0.4.0", + "num-traits", +] + +[[package]] +name = "wasmparser" +version = "0.89.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5d3e08b13876f96dd55608d03cd4883a0545884932d5adf11925876c96daef" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmtime" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f511c4917c83d04da68333921107db75747c4e11a2f654a8e909cc5e0520dc" +dependencies = [ + "anyhow", + "bincode", + "cfg-if 1.0.0", + "indexmap", + "libc", + "log", + "object 0.29.0", + "once_cell", + "paste", + "psm", + "rayon", + "serde", + "target-lexicon", + "wasmparser", + "wasmtime-cache", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-jit", + "wasmtime-runtime", + "windows-sys 0.36.1", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39bf3debfe744bf19dd3732990ce6f8c0ced7439e2370ba4e1d8f5a3660a3178" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "wasmtime-cache" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece42fa4676a263f7558cdaaf5a71c2592bebcbac22a0580e33cf3406c103da2" +dependencies = [ + "anyhow", + "base64", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix", + "serde", + "sha2 0.9.8", + "toml", + "windows-sys 0.36.1", + "zstd", +] + +[[package]] +name = "wasmtime-cranelift" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058217e28644b012bdcdf0e445f58d496d78c2e0b6a6dd93558e701591dad705" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "object 0.29.0", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7af06848df28b7661471d9a80d30a973e0f401f2e3ed5396ad7e225ed217047" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli", + "indexmap", + "log", + "object 0.29.0", + "serde", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-jit" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9028fb63a54185b3c192b7500ef8039c7bb8d7f62bfc9e7c258483a33a3d13bb" +dependencies = [ + "addr2line", + "anyhow", + "bincode", + "cfg-if 1.0.0", + "cpp_demangle", + "gimli", + "log", + "object 0.29.0", + "rustc-demangle", + "rustix", + "serde", + "target-lexicon", + "thiserror", + "wasmtime-environ", + "wasmtime-jit-debug", + "wasmtime-runtime", + "windows-sys 0.36.1", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e82d4ef93296785de7efca92f7679dc67fe68a13b625a5ecc8d7503b377a37" +dependencies = [ + "object 0.29.0", + "once_cell", + "rustix", +] + +[[package]] +name = "wasmtime-runtime" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0e9bea7d517d114fe66b930b2124ee086516ee93eeebfd97f75f366c5b0553" +dependencies = [ + "anyhow", + "cc", + "cfg-if 1.0.0", + "indexmap", + "libc", + "log", + "mach", + "memfd", + "memoffset", + "paste", + "rand 0.8.5", + "rustix", + "thiserror", + "wasmtime-asm-macros", + "wasmtime-environ", + "wasmtime-jit-debug", + "windows-sys 0.36.1", +] + +[[package]] +name = "wasmtime-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b83e93ed41b8fdc936244cfd5e455480cf1eca1fd60c78a0040038b4ce5075" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser", +] + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +dependencies = [ + "webpki", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + +[[package]] +name = "x25519-dalek" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +dependencies = [ + "curve25519-dalek 3.2.0", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "xcm" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "derivative", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "xcm-procedural", +] + +[[package]] +name = "xcm-builder" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-transaction-payment", + "parity-scale-codec", + "polkadot-parachain", + "scale-info", + "sp-arithmetic", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + +[[package]] +name = "xcm-executor" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "frame-support", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", +] + +[[package]] +name = "xcm-procedural" +version = "0.9.31" +source = "git+https://github.com/paritytech//polkadot?branch=locked-for-gav-xcm-v3-and-bridges#9fc6b88ccc7abc1418ff5260e8cc492e647306a0" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "yamux" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "zeroize" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.1+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +dependencies = [ + "cc", + "libc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000000..9ee19ed7828 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,263 @@ +[workspace] +resolver = "2" + +members = [ + "bin/*/node", + "bin/*/runtime", + "fuzz/*", + "modules/*", + "primitives/*", + "relays/*", +] + +# we need to be able to work with XCMv3, but it is not yet in Polkadot master +# => manual patch is required. Because of https://github.com/rust-lang/cargo/issues/5478 +# we need to use double slash in the repo name. +# +# Once XCMv3 PR is merged, we may remove both Substrate and Polkadot patch section. + +[patch."https://github.com/paritytech/substrate"] +beefy-gadget = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +beefy-gadget-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +beefy-merkle-tree = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +beefy-primitives = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +fork-tree = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-benchmarking-cli = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-election-provider-solution-type = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-election-provider-support = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-executive = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-support = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-support-procedural = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-support-procedural-tools = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-support-procedural-tools-derive = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-system = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-system-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +frame-try-runtime = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +node-inspect = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-aura = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-authority-discovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-authorship = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-babe = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-bags-list = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-balances = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-beefy = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-beefy-mmr = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-bounties = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-child-bounties = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-collective = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-conviction-voting = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-democracy = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-election-provider-multi-phase = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-elections-phragmen = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-fast-unstake = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-gilt = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-grandpa = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-identity = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-im-online = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-indices = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-membership = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-mmr = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-mmr-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-multisig = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-nomination-pools = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-nomination-pools-runtime-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-offences = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-preimage = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-proxy = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-randomness-collective-flip = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-ranked-collective = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-recovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-referenda = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-scheduler = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-session = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-society = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-staking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-staking-reward-curve = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-staking-reward-fn = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-sudo = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-timestamp = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-tips = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-transaction-payment = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-transaction-payment-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-treasury = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-utility = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-vesting = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +pallet-whitelist = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +remote-externalities = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-allocator = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-authority-discovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-basic-authorship = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-block-builder = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-chain-spec = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-chain-spec-derive = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-cli = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-client-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-client-db = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-aura = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-babe = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-babe-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-epochs = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-consensus-slots = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-executor = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-executor-common = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-executor-wasmi = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-executor-wasmtime = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-finality-grandpa = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-finality-grandpa-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-informant = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-keystore = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network-common = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network-gossip = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network-light = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-network-sync = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-offchain = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-peerset = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-proposer-metrics = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-rpc-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-rpc-server = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-service = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-state-db = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-sync-state-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-sysinfo = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-telemetry = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-tracing = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-tracing-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-transaction-pool = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-transaction-pool-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sc-utils = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-api = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-api-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-application-crypto = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-arithmetic = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-authority-discovery = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-authorship = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-block-builder = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-blockchain = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus-aura = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus-babe = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus-slots = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-consensus-vrf = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-core = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-core-hashing = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-core-hashing-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-database = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-debug-derive = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-externalities = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-finality-grandpa = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-inherents = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-io = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-keyring = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-keystore = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-maybe-compressed-blob = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-mmr-primitives = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-npos-elections = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-offchain = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-panic-handler = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-runtime = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-runtime-interface = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-runtime-interface-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-session = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-staking = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-state-machine = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-std = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-storage = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-tasks = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-timestamp = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-tracing = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-transaction-pool = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-transaction-storage-proof = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-trie = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-version = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-version-proc-macro = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +sp-wasm-interface = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-build-script-utils = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-frame-rpc-system = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-prometheus-endpoint = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-state-trie-migration-rpc = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +substrate-wasm-builder = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } +try-runtime-cli = { git = "https://github.com/paritytech//substrate", branch = "sv-locked-for-gav-xcm-v3-and-bridges" } + +[patch."https://github.com/paritytech/polkadot"] +kusama-runtime = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +kusama-runtime-constants = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +pallet-xcm = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-approval-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-availability-bitfield-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-availability-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-availability-recovery = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-cli = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-client = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-collator-protocol = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-core-primitives = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-dispute-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-erasure-coding = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-gossip-support = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-network-bridge = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-collation-generation = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-approval-voting = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-av-store = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-backing = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-bitfield-signing = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-candidate-validation = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-chain-api = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-chain-selection = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-dispute-coordinator = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-parachains-inherent = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-provisioner = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-pvf = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-pvf-checker = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-core-runtime-api = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-jaeger = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-metrics = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-network-protocol = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-primitives = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-subsystem = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-subsystem-types = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-node-subsystem-util = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-overseer = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-parachain = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-performance-test = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-primitives = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-rpc = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime-common = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime-constants = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime-metrics = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-runtime-parachains = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-service = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-statement-distribution = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +polkadot-statement-table = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +slot-range-helper = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +tracing-gum = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +tracing-gum-proc-macro = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +xcm = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +xcm-builder = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +xcm-executor = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } +xcm-procedural = { git = "https://github.com/paritytech//polkadot", branch = "locked-for-gav-xcm-v3-and-bridges" } + +[patch."https://github.com/paritytech/cumulus"] +cumulus-client-consensus-aura = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-client-consensus-common = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-client-cli = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-client-network = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-client-service = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-primitives-core = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-relay-chain-interface = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-relay-chain-inprocess-interface = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-relay-chain-minimal-node = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-pallet-aura-ext = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-pallet-parachain-system = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-pallet-xcm = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +cumulus-primitives-timestamp = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } +parachain-info = { git = "https://github.com/paritytech//cumulus", branch = "cumulus-locked-for-gav-xcm-v3-and-bridges" } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..a64b59e7081 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,72 @@ +# Builds images used by the bridge. +# +# In particular, it can be used to build Substrate nodes and bridge relayers. The binary that gets +# built can be specified with the `PROJECT` build-arg. For example, to build the `substrate-relay` +# you would do the following: +# +# `docker build . -t local/substrate-relay --build-arg=PROJECT=substrate-relay` +# +# See the `deployments/README.md` for all the available `PROJECT` values. + +FROM docker.io/paritytech/bridges-ci:production as builder +USER root +WORKDIR /parity-bridges-common + +COPY . . + +ARG PROJECT=substrate-relay +RUN cargo build --release --verbose -p ${PROJECT} && \ + strip ./target/release/${PROJECT} + +# In this final stage we copy over the final binary and do some checks +# to make sure that everything looks good. +FROM docker.io/library/ubuntu:20.04 as runtime + +# show backtraces +ENV RUST_BACKTRACE 1 +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -eux; \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + curl ca-certificates libssl-dev && \ + update-ca-certificates && \ + groupadd -g 1000 user && \ + useradd -u 1000 -g user -s /bin/sh -m user && \ + # apt clean up + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# switch to non-root user +USER user + +WORKDIR /home/user + +ARG PROJECT=substrate-relay + +COPY --chown=user:user --from=builder /parity-bridges-common/target/release/${PROJECT} ./ +COPY --chown=user:user --from=builder /parity-bridges-common/deployments/local-scripts/bridge-entrypoint.sh ./ + +# check if executable works in this container +RUN ./${PROJECT} --version + +ENV PROJECT=$PROJECT +ENTRYPOINT ["/home/user/bridge-entrypoint.sh"] + +# metadata +ARG VCS_REF=master +ARG BUILD_DATE="" +ARG VERSION="" + +LABEL org.opencontainers.image.title="${PROJECT}" \ + org.opencontainers.image.description="${PROJECT} - component of Parity Bridges Common" \ + org.opencontainers.image.source="https://github.com/paritytech/parity-bridges-common/blob/${VCS_REF}/Dockerfile" \ + org.opencontainers.image.url="https://github.com/paritytech/parity-bridges-common/blob/${VCS_REF}/Dockerfile" \ + org.opencontainers.image.documentation="https://github.com/paritytech/parity-bridges-common/blob/${VCS_REF}/README.md" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.version="${VERSION}" \ + org.opencontainers.image.revision="${VCS_REF}" \ + org.opencontainers.image.authors="devops-team@parity.io" \ + org.opencontainers.image.vendor="Parity Technologies" \ + org.opencontainers.image.licenses="GPL-3.0 License" diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..733c072369c --- /dev/null +++ b/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/README.md b/README.md new file mode 100644 index 00000000000..d45b328b2bd --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +# Parity Bridges Common + +This is a collection of components for building bridges. + +These components include Substrate pallets for syncing headers, passing arbitrary messages, as well +as libraries for building relayers to provide cross-chain communication capabilities. + +Three bridge nodes are also available. The nodes can be used to run test networks which bridge other +Substrate chains. + +🚧 The bridges are currently under construction - a hardhat is recommended beyond this point 🚧 + +**IMPORTANT**: this documentation is outdated and it is mostly related to the previous version of our +bridge. Right there's an ongoing work to make our bridge work with XCM messages. Old bridge is still +available at [encoded-calls-messaging](https://github.com/paritytech/parity-bridges-common/releases/tag/encoded-calls-messaging) +tag. + +## Contents + +- [Installation](#installation) +- [High-Level Architecture](#high-level-architecture) +- [Project Layout](#project-layout) +- [Running the Bridge](#running-the-bridge) +- [How to send a message](#how-to-send-a-message) +- [Community](#community) + +## Installation + +To get up and running you need both stable and nightly Rust. Rust nightly is used to build the Web +Assembly (WASM) runtime for the node. You can configure the WASM support as so: + +```bash +rustup install nightly +rustup target add wasm32-unknown-unknown --toolchain nightly +``` + +Once this is configured you can build and test the repo as follows: + +``` +git clone https://github.com/paritytech/parity-bridges-common.git +cd parity-bridges-common +cargo build --all +cargo test --all +``` + +Also you can build the repo with +[Parity CI Docker image](https://github.com/paritytech/scripts/tree/master/dockerfiles/bridges-ci): + +```bash +docker pull paritytech/bridges-ci:production +mkdir ~/cache +chown 1000:1000 ~/cache #processes in the container runs as "nonroot" user with UID 1000 +docker run --rm -it -w /shellhere/parity-bridges-common \ + -v /home/$(whoami)/cache/:/cache/ \ + -v "$(pwd)":/shellhere/parity-bridges-common \ + -e CARGO_HOME=/cache/cargo/ \ + -e SCCACHE_DIR=/cache/sccache/ \ + -e CARGO_TARGET_DIR=/cache/target/ paritytech/bridges-ci:production cargo build --all +#artifacts can be found in ~/cache/target +``` + +If you want to reproduce other steps of CI process you can use the following +[guide](https://github.com/paritytech/scripts#reproduce-ci-locally). + +If you need more information about setting up your development environment [Substrate's +Installation page](https://docs.substrate.io/main-docs/install/) is a good +resource. + +## High-Level Architecture + +This repo has support for bridging foreign chains together using a combination of Substrate pallets +and external processes called relayers. A bridge chain is one that is able to follow the consensus +of a foreign chain independently. For example, consider the case below where we want to bridge two +Substrate based chains. + +``` ++---------------+ +---------------+ +| | | | +| Rialto | | Millau | +| | | | ++-------+-------+ +-------+-------+ + ^ ^ + | +---------------+ | + | | | | + +-----> | Bridge Relay | <-------+ + | | + +---------------+ +``` + +The Millau chain must be able to accept Rialto headers and verify their integrity. It does this by +using a runtime module designed to track GRANDPA finality. Since two blockchains can't interact +directly they need an external service, called a relayer, to communicate. The relayer will subscribe +to new Rialto headers via RPC and submit them to the Millau chain for verification. + +Take a look at [Bridge High Level Documentation](./docs/high-level-overview.md) for more in-depth +description of the bridge interaction. + +## Project Layout + +Here's an overview of how the project is laid out. The main bits are the `node`, which is the actual +"blockchain", the `modules` which are used to build the blockchain's logic (a.k.a the runtime) and +the `relays` which are used to pass messages between chains. + +``` +├── bin // Node and Runtime for the various Substrate chains +│ └── ... +├── deployments // Useful tools for deploying test networks +│ └── ... +├── diagrams // Pretty pictures of the project architecture +│ └── ... +├── modules // Substrate Runtime Modules (a.k.a Pallets) +│ ├── grandpa // On-Chain GRANDPA Light Client +│ ├── messages // Cross Chain Message Passing +│ ├── dispatch // Target Chain Message Execution +│ └── ... +├── primitives // Code shared between modules, runtimes, and relays +│ └── ... +├── relays // Application for sending headers and messages between chains +│ └── ... +└── scripts // Useful development and maintenance scripts +``` + +## Running the Bridge + +To run the Bridge you need to be able to connect the bridge relay node to the RPC interface of nodes +on each side of the bridge (source and target chain). + +There are 2 ways to run the bridge, described below: + +- building & running from source +- running a Docker Compose setup (recommended). + +### Using the Source + +First you'll need to build the bridge nodes and relay. This can be done as follows: + +```bash +# In `parity-bridges-common` folder +cargo build -p rialto-bridge-node +cargo build -p millau-bridge-node +cargo build -p substrate-relay +``` + +### Running a Dev network + +We will launch a dev network to demonstrate how to relay a message between two Substrate based +chains (named Rialto and Millau). + +To do this we will need two nodes, two relayers which will relay headers, and two relayers which +will relay messages. + +#### Running from local scripts + +To run a simple dev network you can use the scripts located in the +[`deployments/local-scripts` folder](./deployments/local-scripts). + +First, we must run the two Substrate nodes. + +```bash +# In `parity-bridges-common` folder +./deployments/local-scripts/run-rialto-node.sh +./deployments/local-scripts/run-millau-node.sh +``` + +After the nodes are up we can run the header relayers. + +```bash +./deployments/local-scripts/relay-millau-to-rialto.sh +./deployments/local-scripts/relay-rialto-to-millau.sh +``` + +At this point you should see the relayer submitting headers from the Millau Substrate chain to the +Rialto Substrate chain. + +``` +# Header Relayer Logs +[Millau_to_Rialto_Sync] [date] DEBUG bridge Going to submit finality proof of Millau header #147 to Rialto +[...] [date] INFO bridge Synced 147 of 147 headers +[...] [date] DEBUG bridge Going to submit finality proof of Millau header #148 to Rialto +[...] [date] INFO bridge Synced 148 of 149 headers +``` + +Finally, we can run the message relayers. + +```bash +./deployments/local-scripts/relay-messages-millau-to-rialto.sh +./deployments/local-scripts/relay-messages-rialto-to-millau.sh +``` + +You will also see the message lane relayers listening for new messages. + +``` +# Message Relayer Logs +[Millau_to_Rialto_MessageLane_00000000] [date] DEBUG bridge Asking Millau::ReceivingConfirmationsDelivery about best message nonces +[...] [date] INFO bridge Synced Some(2) of Some(3) nonces in Millau::MessagesDelivery -> Rialto::MessagesDelivery race +[...] [date] DEBUG bridge Asking Millau::MessagesDelivery about message nonces +[...] [date] DEBUG bridge Received best nonces from Millau::ReceivingConfirmationsDelivery: TargetClientNonces { latest_nonce: 0, nonces_data: () } +[...] [date] DEBUG bridge Asking Millau::ReceivingConfirmationsDelivery about finalized message nonces +[...] [date] DEBUG bridge Received finalized nonces from Millau::ReceivingConfirmationsDelivery: TargetClientNonces { latest_nonce: 0, nonces_data: () } +[...] [date] DEBUG bridge Received nonces from Millau::MessagesDelivery: SourceClientNonces { new_nonces: {}, confirmed_nonce: Some(0) } +[...] [date] DEBUG bridge Asking Millau node about its state +[...] [date] DEBUG bridge Received state from Millau node: ClientState { best_self: HeaderId(1593, 0xacac***), best_finalized_self: HeaderId(1590, 0x0be81d...), best_finalized_peer_at_best_self: HeaderId(0, 0xdcdd89...) } +``` + +To send a message see the ["How to send a message" section](#how-to-send-a-message). + +### Full Network Docker Compose Setup + +For a more sophisticated deployment which includes bidirectional header sync, message passing, +monitoring dashboards, etc. see the [Deployments README](./deployments/README.md). + +You should note that you can find images for all the bridge components published on +[Docker Hub](https://hub.docker.com/u/paritytech). + +To run a Rialto node for example, you can use the following command: + +```bash +docker run -p 30333:30333 -p 9933:9933 -p 9944:9944 \ + -it paritytech/rialto-bridge-node --dev --tmp \ + --rpc-cors=all --unsafe-rpc-external --unsafe-ws-external +``` + +### How to send a message + +In this section we'll show you how to quickly send a bridge message, if you want to +interact with and test the bridge see more details in [send message](./docs/send-message.md) + +```bash +# In `parity-bridges-common` folder +./scripts/send-message-from-millau-rialto.sh remark +``` + +After sending a message you will see the following logs showing a message was successfully sent: + +``` +INFO bridge Sending message to Rialto. Size: 286. Dispatch weight: 1038000. Fee: 275,002,568 +INFO bridge Signed Millau Call: 0x7904... +TRACE bridge Sent transaction to Millau node: 0x5e68... +``` + +## Community + +Main hangout for the community is [Element](https://element.io/) (formerly Riot). Element is a chat +server like, for example, Discord. Most discussions around Polkadot and Substrate happen +in various Element "rooms" (channels). So, joining Element might be a good idea, anyway. + +If you are interested in information exchange and development of Polkadot related bridges please +feel free to join the [Polkadot Bridges](https://app.element.io/#/room/#bridges:web3.foundation) +Element channel. + +The [Substrate Technical](https://app.element.io/#/room/#substrate-technical:matrix.org) Element +channel is most suited for discussions regarding Substrate itself. diff --git a/bin/.keep b/bin/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bin/millau/node/Cargo.toml b/bin/millau/node/Cargo.toml new file mode 100644 index 00000000000..a8d03c6f1a4 --- /dev/null +++ b/bin/millau/node/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "millau-bridge-node" +description = "Substrate node compatible with Millau runtime" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +build = "build.rs" +repository = "https://github.com/paritytech/parity-bridges-common/" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +clap = { version = "4.0.9", features = ["derive"] } +jsonrpsee = { version = "0.15.1", features = ["server"] } +serde_json = "1.0.79" + +# Bridge dependencies + +millau-runtime = { path = "../runtime" } + +# Substrate Dependencies + +beefy-gadget = { git = "https://github.com/paritytech/substrate", branch = "master" } +beefy-gadget-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" } +beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "master" } +node-inspect = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-mmr-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master", features = ["wasmtime"] } +sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-finality-grandpa-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" } +substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[build-dependencies] +substrate-build-script-utils = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = [] +runtime-benchmarks = [ + "millau-runtime/runtime-benchmarks", +] diff --git a/bin/millau/node/build.rs b/bin/millau/node/build.rs new file mode 100644 index 00000000000..d9b50049e26 --- /dev/null +++ b/bin/millau/node/build.rs @@ -0,0 +1,23 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; + +fn main() { + generate_cargo_keys(); + + rerun_if_git_head_changed(); +} diff --git a/bin/millau/node/src/chain_spec.rs b/bin/millau/node/src/chain_spec.rs new file mode 100644 index 00000000000..2ded668fdee --- /dev/null +++ b/bin/millau/node/src/chain_spec.rs @@ -0,0 +1,235 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use beefy_primitives::crypto::AuthorityId as BeefyId; +use millau_runtime::{ + AccountId, AuraConfig, BalancesConfig, BeefyConfig, BridgeRialtoMessagesConfig, + BridgeRialtoParachainMessagesConfig, BridgeWestendGrandpaConfig, GenesisConfig, GrandpaConfig, + SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY, +}; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::{sr25519, Pair, Public}; +use sp_finality_grandpa::AuthorityId as GrandpaId; +use sp_runtime::traits::{IdentifyAccount, Verify}; + +/// "Names" of the authorities accounts at local testnet. +const LOCAL_AUTHORITIES_ACCOUNTS: [&str; 5] = ["Alice", "Bob", "Charlie", "Dave", "Eve"]; +/// "Names" of the authorities accounts at development testnet. +const DEV_AUTHORITIES_ACCOUNTS: [&str; 1] = [LOCAL_AUTHORITIES_ACCOUNTS[0]]; +/// "Names" of all possible authorities accounts. +const ALL_AUTHORITIES_ACCOUNTS: [&str; 5] = LOCAL_AUTHORITIES_ACCOUNTS; +/// "Name" of the `sudo` account. +const SUDO_ACCOUNT: &str = "Sudo"; +/// "Name" of the account, which owns the with-Westend GRANDPA pallet. +const WESTEND_GRANDPA_PALLET_OWNER: &str = "Westend.GrandpaOwner"; +/// "Name" of the account, which owns the with-Rialto messages pallet. +const RIALTO_MESSAGES_PALLET_OWNER: &str = "Rialto.MessagesOwner"; +/// "Name" of the account, which owns the with-RialtoParachain messages pallet. +const RIALTO_PARACHAIN_MESSAGES_PALLET_OWNER: &str = "RialtoParachain.MessagesOwner"; + +/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. +pub type ChainSpec = sc_service::GenericChainSpec; + +/// The chain specification option. This is expected to come in from the CLI and +/// is little more than one of a number of alternatives which can easily be converted +/// from a string (`--chain=...`) into a `ChainSpec`. +#[derive(Clone, Debug)] +pub enum Alternative { + /// Whatever the current runtime is, with just Alice as an auth. + Development, + /// Whatever the current runtime is, with simple Alice/Bob/Charlie/Dave/Eve auths. + LocalTestnet, +} + +/// Helper function to generate a crypto pair from seed +pub fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{seed}"), None) + .expect("static values are valid; qed") + .public() +} + +type AccountPublic = ::Signer; + +/// Helper function to generate an account ID from seed +pub fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() +} + +/// Helper function to generate an authority key for Aura +pub fn get_authority_keys_from_seed(s: &str) -> (AccountId, AuraId, BeefyId, GrandpaId) { + ( + get_account_id_from_seed::(s), + get_from_seed::(s), + get_from_seed::(s), + get_from_seed::(s), + ) +} + +impl Alternative { + /// Get an actual chain config from one of the alternatives. + pub(crate) fn load(self) -> ChainSpec { + let properties = Some( + serde_json::json!({ + "tokenDecimals": 9, + "tokenSymbol": "MLAU" + }) + .as_object() + .expect("Map given; qed") + .clone(), + ); + match self { + Alternative::Development => ChainSpec::from_genesis( + "Millau Development", + "millau_dev", + sc_service::ChainType::Development, + || { + testnet_genesis( + DEV_AUTHORITIES_ACCOUNTS + .into_iter() + .map(get_authority_keys_from_seed) + .collect(), + get_account_id_from_seed::(SUDO_ACCOUNT), + endowed_accounts(), + true, + ) + }, + vec![], + None, + None, + None, + properties, + None, + ), + Alternative::LocalTestnet => ChainSpec::from_genesis( + "Millau Local", + "millau_local", + sc_service::ChainType::Local, + || { + testnet_genesis( + LOCAL_AUTHORITIES_ACCOUNTS + .into_iter() + .map(get_authority_keys_from_seed) + .collect(), + get_account_id_from_seed::(SUDO_ACCOUNT), + endowed_accounts(), + true, + ) + }, + vec![], + None, + None, + None, + properties, + None, + ), + } + } +} + +/// We're using the same set of endowed accounts on all Millau chains (dev/local) to make +/// sure that all accounts, required for bridge to be functional (e.g. relayers fund account, +/// accounts used by relayers in our test deployments, accounts used for demonstration +/// purposes), are all available on these chains. +fn endowed_accounts() -> Vec { + let all_authorities = ALL_AUTHORITIES_ACCOUNTS.iter().flat_map(|x| { + [ + get_account_id_from_seed::(x), + get_account_id_from_seed::(&format!("{x}//stash")), + ] + }); + vec![ + // Sudo account + get_account_id_from_seed::(SUDO_ACCOUNT), + // Regular (unused) accounts + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Ferdie//stash"), + // Accounts, used by Westend<>Millau bridge + get_account_id_from_seed::(WESTEND_GRANDPA_PALLET_OWNER), + get_account_id_from_seed::("Westend.HeadersRelay1"), + get_account_id_from_seed::("Westend.HeadersRelay2"), + get_account_id_from_seed::("Westend.WestmintHeaders1"), + get_account_id_from_seed::("Westend.WestmintHeaders2"), + // Accounts, used by Rialto<>Millau bridge + get_account_id_from_seed::(RIALTO_MESSAGES_PALLET_OWNER), + get_account_id_from_seed::("Rialto.HeadersAndMessagesRelay"), + get_account_id_from_seed::("Rialto.OutboundMessagesRelay.Lane00000001"), + get_account_id_from_seed::("Rialto.InboundMessagesRelay.Lane00000001"), + get_account_id_from_seed::("Rialto.MessagesSender"), + // Accounts, used by RialtoParachain<>Millau bridge + get_account_id_from_seed::(RIALTO_PARACHAIN_MESSAGES_PALLET_OWNER), + get_account_id_from_seed::("RialtoParachain.HeadersAndMessagesRelay1"), + get_account_id_from_seed::("RialtoParachain.HeadersAndMessagesRelay2"), + get_account_id_from_seed::("RialtoParachain.RialtoHeadersRelay1"), + get_account_id_from_seed::("RialtoParachain.RialtoHeadersRelay2"), + get_account_id_from_seed::("RialtoParachain.MessagesSender"), + ] + .into_iter() + .chain(all_authorities) + .collect() +} + +fn session_keys(aura: AuraId, beefy: BeefyId, grandpa: GrandpaId) -> SessionKeys { + SessionKeys { aura, beefy, grandpa } +} + +fn testnet_genesis( + initial_authorities: Vec<(AccountId, AuraId, BeefyId, GrandpaId)>, + root_key: AccountId, + endowed_accounts: Vec, + _enable_println: bool, +) -> GenesisConfig { + GenesisConfig { + system: SystemConfig { + code: WASM_BINARY.expect("Millau development WASM not available").to_vec(), + }, + balances: BalancesConfig { + balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 50)).collect(), + }, + aura: AuraConfig { authorities: Vec::new() }, + beefy: BeefyConfig { authorities: Vec::new() }, + grandpa: GrandpaConfig { authorities: Vec::new() }, + sudo: SudoConfig { key: Some(root_key) }, + session: SessionConfig { + keys: initial_authorities + .iter() + .map(|x| { + (x.0.clone(), x.0.clone(), session_keys(x.1.clone(), x.2.clone(), x.3.clone())) + }) + .collect::>(), + }, + bridge_westend_grandpa: BridgeWestendGrandpaConfig { + // for our deployments to avoid multiple same-nonces transactions: + // //Alice is already used to initialize Rialto<->Millau bridge + // => let's use //Westend.GrandpaOwner to initialize Westend->Millau bridge + owner: Some(get_account_id_from_seed::(WESTEND_GRANDPA_PALLET_OWNER)), + ..Default::default() + }, + bridge_rialto_messages: BridgeRialtoMessagesConfig { + owner: Some(get_account_id_from_seed::(RIALTO_MESSAGES_PALLET_OWNER)), + ..Default::default() + }, + bridge_rialto_parachain_messages: BridgeRialtoParachainMessagesConfig { + owner: Some(get_account_id_from_seed::( + RIALTO_PARACHAIN_MESSAGES_PALLET_OWNER, + )), + ..Default::default() + }, + xcm_pallet: Default::default(), + } +} diff --git a/bin/millau/node/src/cli.rs b/bin/millau/node/src/cli.rs new file mode 100644 index 00000000000..0280254bcad --- /dev/null +++ b/bin/millau/node/src/cli.rs @@ -0,0 +1,72 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use clap::Parser; +use sc_cli::RunCmd; + +#[derive(Debug, Parser)] +pub struct Cli { + #[structopt(subcommand)] + pub subcommand: Option, + + #[structopt(flatten)] + pub run: RunCmd, +} + +/// Possible subcommands of the main binary. +#[derive(Debug, Parser)] +pub enum Subcommand { + /// Key management CLI utilities + #[clap(subcommand)] + Key(sc_cli::KeySubcommand), + + /// Verify a signature for a message, provided on `STDIN`, with a given (public or secret) key. + Verify(sc_cli::VerifyCmd), + + /// Generate a seed that provides a vanity address. + Vanity(sc_cli::VanityCmd), + + /// Sign a message, with a given (secret) key. + Sign(sc_cli::SignCmd), + + /// Build a chain specification. + BuildSpec(sc_cli::BuildSpecCmd), + + /// Validate blocks. + CheckBlock(sc_cli::CheckBlockCmd), + + /// Export blocks. + ExportBlocks(sc_cli::ExportBlocksCmd), + + /// Export the state of a given block into a chain spec. + ExportState(sc_cli::ExportStateCmd), + + /// Import blocks. + ImportBlocks(sc_cli::ImportBlocksCmd), + + /// Remove the whole chain. + PurgeChain(sc_cli::PurgeChainCmd), + + /// Revert the chain to a previous state. + Revert(sc_cli::RevertCmd), + + /// Inspect blocks or extrinsics. + Inspect(node_inspect::cli::InspectCmd), + + /// Benchmark runtime pallets. + #[clap(subcommand)] + Benchmark(frame_benchmarking_cli::BenchmarkCmd), +} diff --git a/bin/millau/node/src/command.rs b/bin/millau/node/src/command.rs new file mode 100644 index 00000000000..b8dff87f8f2 --- /dev/null +++ b/bin/millau/node/src/command.rs @@ -0,0 +1,159 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{ + cli::{Cli, Subcommand}, + service, + service::new_partial, +}; +use frame_benchmarking_cli::BenchmarkCmd; +use millau_runtime::{Block, RuntimeApi}; +use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; +use sc_service::PartialComponents; + +impl SubstrateCli for Cli { + fn impl_name() -> String { + "Millau Bridge Node".into() + } + + fn impl_version() -> String { + env!("CARGO_PKG_VERSION").into() + } + + fn description() -> String { + "Millau Bridge Node".into() + } + + fn author() -> String { + "Parity Technologies".into() + } + + fn support_url() -> String { + "https://github.com/paritytech/parity-bridges-common/".into() + } + + fn copyright_start_year() -> i32 { + 2019 + } + + fn executable_name() -> String { + "millau-bridge-node".into() + } + + fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { + &millau_runtime::VERSION + } + + fn load_spec(&self, id: &str) -> Result, String> { + Ok(Box::new( + match id { + "" | "dev" => crate::chain_spec::Alternative::Development, + "local" => crate::chain_spec::Alternative::LocalTestnet, + _ => return Err(format!("Unsupported chain specification: {id}")), + } + .load(), + )) + } +} + +/// Parse and run command line arguments +pub fn run() -> sc_cli::Result<()> { + let cli = Cli::from_args(); + // make sure to set correct crypto version. + sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom( + millau_runtime::SS58Prefix::get() as u16, + )); + + match &cli.subcommand { + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + match cmd { + BenchmarkCmd::Pallet(cmd) => + if cfg!(feature = "runtime-benchmarks") { + runner + .sync_run(|config| cmd.run::(config)) + } else { + println!( + "Benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + ); + Ok(()) + }, + _ => Err("Unsupported benchmarking subcommand".into()), + } + }, + Some(Subcommand::Key(cmd)) => cmd.run(&cli), + Some(Subcommand::Sign(cmd)) => cmd.run(), + Some(Subcommand::Verify(cmd)) => cmd.run(), + Some(Subcommand::Vanity(cmd)) => cmd.run(), + Some(Subcommand::BuildSpec(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) + }, + Some(Subcommand::CheckBlock(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, import_queue, .. } = + new_partial(&config)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::ExportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = new_partial(&config)?; + Ok((cmd.run(client, config.database), task_manager)) + }) + }, + Some(Subcommand::ExportState(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = new_partial(&config)?; + Ok((cmd.run(client, config.chain_spec), task_manager)) + }) + }, + Some(Subcommand::ImportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, import_queue, .. } = + new_partial(&config)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::PurgeChain(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.database)) + }, + Some(Subcommand::Revert(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?; + Ok((cmd.run(client, backend, None), task_manager)) + }) + }, + Some(Subcommand::Inspect(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner + .sync_run(|config| cmd.run::(config)) + }, + None => { + let runner = cli.create_runner(&cli.run)?; + runner.run_node_until_exit(|config| async move { + service::new_full(config).map_err(sc_cli::Error::Service) + }) + }, + } +} diff --git a/bin/millau/node/src/lib.rs b/bin/millau/node/src/lib.rs new file mode 100644 index 00000000000..382d1c2d7fb --- /dev/null +++ b/bin/millau/node/src/lib.rs @@ -0,0 +1,32 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate Node Template CLI library. +#![warn(missing_docs)] + +mod chain_spec; +#[macro_use] +mod service; +mod cli; +mod command; + +/// Node run result. +pub type Result = sc_cli::Result<()>; + +/// Run node. +pub fn run() -> Result { + command::run() +} diff --git a/bin/millau/node/src/main.rs b/bin/millau/node/src/main.rs new file mode 100644 index 00000000000..cf6dd9f733a --- /dev/null +++ b/bin/millau/node/src/main.rs @@ -0,0 +1,30 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Millau bridge node. + +#![warn(missing_docs)] + +mod chain_spec; +#[macro_use] +mod service; +mod cli; +mod command; + +/// Run the Millau Node +fn main() -> sc_cli::Result<()> { + command::run() +} diff --git a/bin/millau/node/src/service.rs b/bin/millau/node/src/service.rs new file mode 100644 index 00000000000..e23e1fa56ca --- /dev/null +++ b/bin/millau/node/src/service.rs @@ -0,0 +1,461 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Service and ServiceFactory implementation. Specialized wrapper over substrate service. + +use jsonrpsee::RpcModule; +use millau_runtime::{self, opaque::Block, RuntimeApi}; +use sc_client_api::BlockBackend; +use sc_consensus_aura::{CompatibilityMode, ImportQueueParams, SlotProportion, StartAuraParams}; +pub use sc_executor::NativeElseWasmExecutor; +use sc_finality_grandpa::SharedVoterState; +use sc_keystore::LocalKeystore; +use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; +use sc_telemetry::{Telemetry, TelemetryWorker}; +use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; +use std::{sync::Arc, time::Duration}; + +// Our native executor instance. +pub struct ExecutorDispatch; + +impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { + /// Only enable the benchmarking host functions when we actually want to benchmark. + #[cfg(feature = "runtime-benchmarks")] + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + /// Otherwise we only use the default Substrate host functions. + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); + + fn dispatch(method: &str, data: &[u8]) -> Option> { + millau_runtime::api::dispatch(method, data) + } + + fn native_version() -> sc_executor::NativeVersion { + millau_runtime::native_version() + } +} + +type FullClient = + sc_service::TFullClient>; +type FullBackend = sc_service::TFullBackend; +type FullSelectChain = sc_consensus::LongestChain; + +#[allow(clippy::type_complexity)] +pub fn new_partial( + config: &Configuration, +) -> Result< + sc_service::PartialComponents< + FullClient, + FullBackend, + FullSelectChain, + sc_consensus::DefaultImportQueue, + sc_transaction_pool::FullPool, + ( + sc_finality_grandpa::GrandpaBlockImport< + FullBackend, + Block, + FullClient, + FullSelectChain, + >, + sc_finality_grandpa::LinkHalf, + beefy_gadget::BeefyVoterLinks, + beefy_gadget::BeefyRPCLinks, + Option, + ), + >, + ServiceError, +> { + if config.keystore_remote.is_some() { + return Err(ServiceError::Other("Remote Keystores are not supported.".into())) + } + + let telemetry = config + .telemetry_endpoints + .clone() + .filter(|x| !x.is_empty()) + .map(|endpoints| -> Result<_, sc_telemetry::Error> { + let worker = TelemetryWorker::new(16)?; + let telemetry = worker.handle().new_telemetry(endpoints); + Ok((worker, telemetry)) + }) + .transpose()?; + + let executor = NativeElseWasmExecutor::::new( + config.wasm_method, + config.default_heap_pages, + config.max_runtime_instances, + config.runtime_cache_size, + ); + + let (client, backend, keystore_container, task_manager) = + sc_service::new_full_parts::( + config, + telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), + executor, + )?; + let client = Arc::new(client); + + let telemetry = telemetry.map(|(worker, telemetry)| { + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); + telemetry + }); + + let select_chain = sc_consensus::LongestChain::new(backend.clone()); + + let transaction_pool = sc_transaction_pool::BasicPool::new_full( + config.transaction_pool.clone(), + config.role.is_authority().into(), + config.prometheus_registry(), + task_manager.spawn_essential_handle(), + client.clone(), + ); + + let (grandpa_block_import, grandpa_link) = sc_finality_grandpa::block_import( + client.clone(), + &(client.clone() as Arc<_>), + select_chain.clone(), + telemetry.as_ref().map(|x| x.handle()), + )?; + + let (beefy_block_import, beefy_voter_links, beefy_rpc_links) = + beefy_gadget::beefy_block_import_and_links( + grandpa_block_import.clone(), + backend.clone(), + client.clone(), + ); + + let slot_duration = sc_consensus_aura::slot_duration(&*client)?; + + let import_queue = + sc_consensus_aura::import_queue::(ImportQueueParams { + block_import: beefy_block_import, + justification_import: Some(Box::new(grandpa_block_import.clone())), + client: client.clone(), + create_inherent_data_providers: move |_, ()| async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + }, + spawner: &task_manager.spawn_essential_handle(), + registry: config.prometheus_registry(), + check_for_equivocation: Default::default(), + telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: CompatibilityMode::None, + })?; + + Ok(sc_service::PartialComponents { + client, + backend, + task_manager, + import_queue, + keystore_container, + select_chain, + transaction_pool, + other: (grandpa_block_import, grandpa_link, beefy_voter_links, beefy_rpc_links, telemetry), + }) +} + +fn remote_keystore(_url: &str) -> Result, &'static str> { + // FIXME: here would the concrete keystore be built, + // must return a concrete type (NOT `LocalKeystore`) that + // implements `CryptoStore` and `SyncCryptoStore` + Err("Remote Keystore not supported.") +} + +/// Builds a new service for a full client. +pub fn new_full(mut config: Configuration) -> Result { + let sc_service::PartialComponents { + client, + backend, + mut task_manager, + import_queue, + mut keystore_container, + select_chain, + transaction_pool, + other: (block_import, grandpa_link, beefy_voter_links, beefy_rpc_links, mut telemetry), + } = new_partial(&config)?; + + if let Some(url) = &config.keystore_remote { + match remote_keystore(url) { + Ok(k) => keystore_container.set_remote_keystore(k), + Err(e) => + return Err(ServiceError::Other(format!( + "Error hooking up remote keystore for {}: {}", + url, e + ))), + }; + } + + let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); + + // Note: GrandPa is pushed before the Polkadot-specific protocols. This doesn't change + // anything in terms of behaviour, but makes the logs more consistent with the other + // Substrate nodes. + let grandpa_protocol_name = sc_finality_grandpa::protocol_standard_name( + &client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"), + &config.chain_spec, + ); + config + .network + .extra_sets + .push(sc_finality_grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone())); + + let beefy_gossip_proto_name = + beefy_gadget::gossip_protocol_name(genesis_hash, config.chain_spec.fork_id()); + // `beefy_on_demand_justifications_handler` is given to `beefy-gadget` task to be run, + // while `beefy_req_resp_cfg` is added to `config.network.request_response_protocols`. + let (beefy_on_demand_justifications_handler, beefy_req_resp_cfg) = + beefy_gadget::communication::request_response::BeefyJustifsRequestHandler::new( + genesis_hash, + config.chain_spec.fork_id(), + client.clone(), + ); + config + .network + .extra_sets + .push(beefy_gadget::communication::beefy_peers_set_config(beefy_gossip_proto_name.clone())); + config.network.request_response_protocols.push(beefy_req_resp_cfg); + + let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new( + backend.clone(), + grandpa_link.shared_authority_set().clone(), + Vec::default(), + )); + + let (network, system_rpc_tx, tx_handler_controller, network_starter) = + sc_service::build_network(sc_service::BuildNetworkParams { + config: &config, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + spawn_handle: task_manager.spawn_handle(), + import_queue, + block_announce_validator_builder: None, + warp_sync: Some(warp_sync), + })?; + + if config.offchain_worker.enabled { + sc_service::build_offchain_workers( + &config, + task_manager.spawn_handle(), + client.clone(), + network.clone(), + ); + } + + let role = config.role.clone(); + let force_authoring = config.force_authoring; + let backoff_authoring_blocks: Option<()> = None; + let name = config.network.node_name.clone(); + let enable_grandpa = !config.disable_grandpa; + let prometheus_registry = config.prometheus_registry().cloned(); + let shared_voter_state = SharedVoterState::empty(); + + let rpc_extensions_builder = { + use sc_finality_grandpa::FinalityProofProvider as GrandpaFinalityProofProvider; + + use beefy_gadget_rpc::{Beefy, BeefyApiServer}; + use pallet_mmr_rpc::{Mmr, MmrApiServer}; + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use sc_finality_grandpa_rpc::{Grandpa, GrandpaApiServer}; + use sc_rpc::DenyUnsafe; + use substrate_frame_rpc_system::{System, SystemApiServer}; + + let backend = backend.clone(); + let client = client.clone(); + let pool = transaction_pool.clone(); + + let justification_stream = grandpa_link.justification_stream(); + let shared_authority_set = grandpa_link.shared_authority_set().clone(); + let shared_voter_state = shared_voter_state.clone(); + + let finality_proof_provider = GrandpaFinalityProofProvider::new_for_service( + backend, + Some(shared_authority_set.clone()), + ); + + Box::new(move |_, subscription_executor: sc_rpc::SubscriptionTaskExecutor| { + let mut io = RpcModule::new(()); + let map_err = |e| sc_service::Error::Other(format!("{e}")); + io.merge(System::new(client.clone(), pool.clone(), DenyUnsafe::No).into_rpc()) + .map_err(map_err)?; + io.merge(TransactionPayment::new(client.clone()).into_rpc()).map_err(map_err)?; + io.merge( + Grandpa::new( + subscription_executor.clone(), + shared_authority_set.clone(), + shared_voter_state.clone(), + justification_stream.clone(), + finality_proof_provider.clone(), + ) + .into_rpc(), + ) + .map_err(map_err)?; + io.merge( + Beefy::::new( + beefy_rpc_links.from_voter_justif_stream.clone(), + beefy_rpc_links.from_voter_best_beefy_stream.clone(), + subscription_executor, + ) + .map_err(|e| sc_service::Error::Other(format!("{e}")))? + .into_rpc(), + ) + .map_err(map_err)?; + io.merge(Mmr::new(client.clone()).into_rpc()).map_err(map_err)?; + Ok(io) + }) + }; + + let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { + network: network.clone(), + client: client.clone(), + keystore: keystore_container.sync_keystore(), + task_manager: &mut task_manager, + transaction_pool: transaction_pool.clone(), + rpc_builder: rpc_extensions_builder.clone(), + backend: backend.clone(), + system_rpc_tx, + config, + tx_handler_controller, + telemetry: telemetry.as_mut(), + })?; + + if role.is_authority() { + let proposer_factory = sc_basic_authorship::ProposerFactory::new( + task_manager.spawn_handle(), + client.clone(), + transaction_pool, + prometheus_registry.as_ref(), + telemetry.as_ref().map(|x| x.handle()), + ); + + let slot_duration = sc_consensus_aura::slot_duration(&*client)?; + + let aura = sc_consensus_aura::start_aura::( + StartAuraParams { + slot_duration, + client: client.clone(), + select_chain, + block_import, + proposer_factory, + create_inherent_data_providers: move |_, ()| async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + }, + force_authoring, + backoff_authoring_blocks, + keystore: keystore_container.sync_keystore(), + sync_oracle: network.clone(), + justification_sync_link: network.clone(), + block_proposal_slot_portion: SlotProportion::new(2f32 / 3f32), + max_block_proposal_slot_portion: None, + telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: CompatibilityMode::None, + }, + )?; + + // the AURA authoring task is considered essential, i.e. if it + // fails we take down the service with it. + task_manager + .spawn_essential_handle() + .spawn_blocking("aura", Some("block-authoring"), aura); + } + + // if the node isn't actively participating in consensus then it doesn't + // need a keystore, regardless of which protocol we use below. + let keystore = + if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None }; + + let justifications_protocol_name = beefy_on_demand_justifications_handler.protocol_name(); + let payload_provider = beefy_primitives::mmr::MmrRootProvider::new(client.clone()); + let beefy_params = beefy_gadget::BeefyParams { + client: client.clone(), + backend, + payload_provider, + runtime: client, + key_store: keystore.clone(), + network_params: beefy_gadget::BeefyNetworkParams { + network: network.clone(), + gossip_protocol_name: beefy_gossip_proto_name, + justifications_protocol_name, + _phantom: core::marker::PhantomData::, + }, + min_block_delta: 2, + prometheus_registry: prometheus_registry.clone(), + links: beefy_voter_links, + on_demand_justifications_handler: beefy_on_demand_justifications_handler, + }; + + // Start the BEEFY bridge gadget. + task_manager.spawn_essential_handle().spawn_blocking( + "beefy-gadget", + None, + beefy_gadget::start_beefy_gadget::<_, _, _, _, _, _>(beefy_params), + ); + + let grandpa_config = sc_finality_grandpa::Config { + // FIXME #1578 make this available through chainspec + gossip_duration: Duration::from_millis(333), + justification_period: 512, + name: Some(name), + observer_enabled: false, + keystore, + local_role: role, + telemetry: telemetry.as_ref().map(|x| x.handle()), + protocol_name: grandpa_protocol_name, + }; + + if enable_grandpa { + // start the full GRANDPA voter + // NOTE: non-authorities could run the GRANDPA observer protocol, but at + // this point the full voter should provide better guarantees of block + // and vote data availability than the observer. The observer has not + // been tested extensively yet and having most nodes in a network run it + // could lead to finality stalls. + let grandpa_config = sc_finality_grandpa::GrandpaParams { + config: grandpa_config, + link: grandpa_link, + network, + voting_rule: sc_finality_grandpa::VotingRulesBuilder::default().build(), + prometheus_registry, + shared_voter_state, + telemetry: telemetry.as_ref().map(|x| x.handle()), + }; + + // the GRANDPA voter task is considered infallible, i.e. + // if it fails we take down the service with it. + task_manager.spawn_essential_handle().spawn_blocking( + "grandpa-voter", + None, + sc_finality_grandpa::run_grandpa_voter(grandpa_config)?, + ); + } + + network_starter.start_network(); + Ok(task_manager) +} diff --git a/bin/millau/runtime/Cargo.toml b/bin/millau/runtime/Cargo.toml new file mode 100644 index 00000000000..b297dedfa53 --- /dev/null +++ b/bin/millau/runtime/Cargo.toml @@ -0,0 +1,145 @@ +[package] +name = "millau-runtime" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +repository = "https://github.com/paritytech/parity-bridges-common/" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +hex-literal = "0.3" +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Bridge dependencies + +bp-messages = { path = "../../../primitives/messages", default-features = false } +bp-millau = { path = "../../../primitives/chain-millau", default-features = false } +bp-polkadot-core = { path = "../../../primitives/polkadot-core", default-features = false } +bp-relayers = { path = "../../../primitives/relayers", default-features = false } +bp-rialto = { path = "../../../primitives/chain-rialto", default-features = false } +bp-rialto-parachain = { path = "../../../primitives/chain-rialto-parachain", default-features = false } +bp-runtime = { path = "../../../primitives/runtime", default-features = false } +bp-westend = { path = "../../../primitives/chain-westend", default-features = false } +bridge-runtime-common = { path = "../../runtime-common", default-features = false } +pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false } +pallet-bridge-messages = { path = "../../../modules/messages", default-features = false } +pallet-bridge-parachains = { path = "../../../modules/parachains", default-features = false } +pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false } +pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false } + +# Substrate Dependencies + +beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +frame-executive = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-beefy = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-mmr-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Polkadot Dependencies +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } + +[dev-dependencies] +bridge-runtime-common = { path = "../../runtime-common", features = ["integrity-test"] } +env_logger = "0.8" +static_assertions = "1.1" + +[build-dependencies] +substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = ["std"] +std = [ + "beefy-primitives/std", + "bp-messages/std", + "bp-millau/std", + "bp-polkadot-core/std", + "bp-relayers/std", + "bp-rialto/std", + "bp-rialto-parachain/std", + "bp-runtime/std", + "bp-westend/std", + "bridge-runtime-common/std", + "codec/std", + "frame-executive/std", + "frame-support/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "log/std", + "pallet-aura/std", + "pallet-balances/std", + "pallet-beefy/std", + "pallet-beefy-mmr/std", + "pallet-bridge-grandpa/std", + "pallet-bridge-messages/std", + "pallet-bridge-parachains/std", + "pallet-bridge-relayers/std", + "pallet-grandpa/std", + "pallet-mmr/std", + "pallet-randomness-collective-flip/std", + "pallet-session/std", + "pallet-shift-session-manager/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "pallet-xcm/std", + "scale-info/std", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-inherents/std", + "sp-io/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-transaction-pool/std", + "sp-version/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", +] +runtime-benchmarks = [ + "bridge-runtime-common/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-bridge-messages/runtime-benchmarks", + "pallet-bridge-parachains/runtime-benchmarks", + "pallet-bridge-relayers/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] diff --git a/bin/millau/runtime/build.rs b/bin/millau/runtime/build.rs new file mode 100644 index 00000000000..cc865704327 --- /dev/null +++ b/bin/millau/runtime/build.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use substrate_wasm_builder::WasmBuilder; + +fn main() { + WasmBuilder::new() + .with_current_project() + .import_memory() + .export_heap_base() + .build() +} diff --git a/bin/millau/runtime/src/lib.rs b/bin/millau/runtime/src/lib.rs new file mode 100644 index 00000000000..b1f1e33d18a --- /dev/null +++ b/bin/millau/runtime/src/lib.rs @@ -0,0 +1,1119 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! The Millau runtime. This can be compiled with `#[no_std]`, ready for Wasm. + +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] +// Runtime-generated enums +#![allow(clippy::large_enum_variant)] +// From construct_runtime macro +#![allow(clippy::from_over_into)] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +pub mod rialto_messages; +pub mod rialto_parachain_messages; +pub mod xcm_config; + +use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet}; +use bp_runtime::{HeaderId, HeaderIdProvider}; +use codec::Decode; +use pallet_grandpa::{ + fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, +}; +use pallet_transaction_payment::{FeeDetails, Multiplier, RuntimeDispatchInfo}; +use sp_api::impl_runtime_apis; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_mmr_primitives::{DataOrHash, EncodableOpaqueLeaf, Error as MmrError, Proof as MmrProof}; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::{Block as BlockT, IdentityLookup, Keccak256, NumberFor, OpaqueKeys}, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, FixedPointNumber, Perquintill, +}; +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +// to be able to use Millau runtime in `bridge-runtime-common` tests +pub use bridge_runtime_common; + +// A few exports that help ease life for downstream crates. +pub use frame_support::{ + construct_runtime, + dispatch::DispatchClass, + parameter_types, + traits::{Currency, ExistenceRequirement, Imbalance, KeyOwnerProofSystem}, + weights::{ + constants::WEIGHT_PER_SECOND, ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, + }, + RuntimeDebug, StorageValue, +}; + +pub use frame_system::Call as SystemCall; +pub use pallet_balances::Call as BalancesCall; +pub use pallet_bridge_grandpa::Call as BridgeGrandpaCall; +pub use pallet_bridge_messages::Call as MessagesCall; +pub use pallet_bridge_parachains::Call as BridgeParachainsCall; +pub use pallet_sudo::Call as SudoCall; +pub use pallet_timestamp::Call as TimestampCall; +pub use pallet_xcm::Call as XcmCall; + +use bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +pub use sp_runtime::{Perbill, Permill}; + +/// An index to a block. +pub type BlockNumber = bp_millau::BlockNumber; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = bp_millau::Signature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = bp_millau::AccountId; + +/// The type for looking up accounts. We don't expect more than 4 billion of them, but you +/// never know... +pub type AccountIndex = u32; + +/// Balance of an account. +pub type Balance = bp_millau::Balance; + +/// Index of a transaction in the chain. +pub type Index = bp_millau::Index; + +/// A hash of some data used by the chain. +pub type Hash = bp_millau::Hash; + +/// Hashing algorithm used by the chain. +pub type Hashing = bp_millau::Hasher; + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + use super::*; + + pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + + /// Opaque block header type. + pub type Header = generic::Header; + /// Opaque block type. + pub type Block = generic::Block; + /// Opaque block identifier type. + pub type BlockId = generic::BlockId; +} + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + pub beefy: Beefy, + pub grandpa: Grandpa, + } +} + +/// This runtime version. +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("millau-runtime"), + impl_name: create_runtime_str!("millau-runtime"), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 0, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const BlockHashCount: BlockNumber = 250; + pub const Version: RuntimeVersion = VERSION; + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 60_000_000, // ~0.06 ms = ~60 µs + write: 200_000_000, // ~0.2 ms = 200 µs + }; + pub const SS58Prefix: u8 = 60; +} + +impl frame_system::Config for Runtime { + /// The basic call filter to use in dispatchable. + type BaseCallFilter = frame_support::traits::Everything; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type RuntimeCall = RuntimeCall; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = IdentityLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = Hashing; + /// The header type. + type Header = generic::Header; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + /// The ubiquitous origin type. + type RuntimeOrigin = RuntimeOrigin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// Version of the runtime. + type Version = Version; + /// Provides information about the pallet setup in the runtime. + type PalletInfo = PalletInfo; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// Block and extrinsics weights: base values and limits. + type BlockWeights = bp_millau::BlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = bp_millau::BlockLength; + /// The weight of database operations that the runtime can invoke. + type DbWeight = DbWeight; + /// The designated SS58 prefix of this chain. + type SS58Prefix = SS58Prefix; + /// The set code logic, just the default since we're not a parachain. + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_randomness_collective_flip::Config for Runtime {} + +parameter_types! { + pub const MaxAuthorities: u32 = 10; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type MaxAuthorities = MaxAuthorities; + type DisabledValidators = (); +} + +impl pallet_beefy::Config for Runtime { + type BeefyId = BeefyId; + type MaxAuthorities = MaxAuthorities; + type OnNewValidatorSet = MmrLeaf; +} + +impl pallet_grandpa::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type KeyOwnerProofSystem = (); + type KeyOwnerProof = + >::Proof; + type KeyOwnerIdentification = >::IdentificationTuple; + type HandleEquivocation = (); + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + type WeightInfo = (); + type MaxAuthorities = MaxAuthorities; +} + +/// MMR helper types. +mod mmr { + use super::Runtime; + pub use pallet_mmr::primitives::*; + use sp_runtime::traits::Keccak256; + + pub type Leaf = <::LeafData as LeafDataProvider>::LeafData; + pub type Hash = ::Output; + pub type Hashing = ::Hashing; +} + +impl pallet_mmr::Config for Runtime { + const INDEXING_PREFIX: &'static [u8] = b"mmr"; + type Hashing = Keccak256; + type Hash = mmr::Hash; + type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; + type WeightInfo = (); + type LeafData = pallet_beefy_mmr::Pallet; +} + +parameter_types! { + /// Version of the produced MMR leaf. + /// + /// The version consists of two parts; + /// - `major` (3 bits) + /// - `minor` (5 bits) + /// + /// `major` should be updated only if decoding the previous MMR Leaf format from the payload + /// is not possible (i.e. backward incompatible change). + /// `minor` should be updated if fields are added to the previous MMR Leaf, which given SCALE + /// encoding does not prevent old leafs from being decoded. + /// + /// Hence we expect `major` to be changed really rarely (think never). + /// See [`MmrLeafVersion`] type documentation for more details. + pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(0, 0); +} + +pub struct BeefyDummyDataProvider; + +impl beefy_primitives::mmr::BeefyDataProvider<()> for BeefyDummyDataProvider { + fn extra_data() {} +} + +impl pallet_beefy_mmr::Config for Runtime { + type LeafVersion = LeafVersion; + type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; + type LeafExtra = (); + type BeefyDataProvider = BeefyDummyDataProvider; +} + +parameter_types! { + pub const MinimumPeriod: u64 = bp_millau::SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the UNIX epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = MinimumPeriod; + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + type WeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: bp_millau::Balance = 500; + // For weight estimation, we assume that the most locks on an individual account will be 50. + // This number may need to be adjusted in the future if this assumption no longer holds true. + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const TransactionBaseFee: Balance = 0; + pub const TransactionByteFee: Balance = 1; + pub const OperationalFeeMultiplier: u8 = 5; + // values for following parameters are copied from polkadot repo, but it is fine + // not to sync them - we're not going to make Rialto a full copy of one of Polkadot-like chains + pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); + pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000); + pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); + pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); +} + +impl pallet_transaction_payment::Config for Runtime { + type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; + type OperationalFeeMultiplier = OperationalFeeMultiplier; + type WeightToFee = bp_millau::WeightToFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = pallet_transaction_payment::TargetedFeeAdjustment< + Runtime, + TargetBlockFullness, + AdjustmentVariable, + MinimumMultiplier, + MaximumMultiplier, + >; + type RuntimeEvent = RuntimeEvent; +} + +impl pallet_sudo::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; +} + +parameter_types! { + /// Authorities are changing every 5 minutes. + pub const Period: BlockNumber = bp_millau::SESSION_LENGTH; + pub const Offset: BlockNumber = 0; +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + type ValidatorIdOf = (); + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = pallet_shift_session_manager::Pallet; + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + type WeightInfo = (); +} + +parameter_types! { + // This is a pretty unscientific cap. + // + // Note that once this is hit the pallet will essentially throttle incoming requests down to one + // call per block. + pub const MaxRequests: u32 = 50; +} + +impl pallet_bridge_relayers::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reward = Balance; + type PaymentProcedure = bp_relayers::MintReward, AccountId>; + type WeightInfo = (); +} + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + /// Number of headers to keep in benchmarks. + /// + /// In benchmarks we always populate with full number of `HeadersToKeep` to make sure that + /// pruning is taken into account. + /// + /// Note: This is lower than regular value, to speed up benchmarking setup. + pub const HeadersToKeep: u32 = 1024; + /// Maximal number of authorities at Rialto. + /// + /// In benchmarks we're using sets of up to `1024` authorities to prepare for possible + /// upgrades in the future and see if performance degrades when number of authorities + /// grow. + pub const MaxAuthoritiesAtRialto: u32 = pallet_bridge_grandpa::benchmarking::MAX_VALIDATOR_SET_SIZE; +} + +#[cfg(not(feature = "runtime-benchmarks"))] +parameter_types! { + /// Number of headers to keep. + /// + /// Assuming the worst case of every header being finalized, we will keep headers at least for a + /// week. + pub const HeadersToKeep: u32 = 7 * bp_rialto::DAYS; + /// Maximal number of authorities at Rialto. + pub const MaxAuthoritiesAtRialto: u32 = bp_rialto::MAX_AUTHORITIES_COUNT; +} + +parameter_types! { + /// Maximal size of SCALE-encoded Rialto header. + pub const MaxRialtoHeaderSize: u32 = bp_rialto::MAX_HEADER_SIZE; + + /// Maximal number of authorities at Westend. + pub const MaxAuthoritiesAtWestend: u32 = bp_westend::MAX_AUTHORITIES_COUNT; + /// Maximal size of SCALE-encoded Westend header. + pub const MaxWestendHeaderSize: u32 = bp_westend::MAX_HEADER_SIZE; +} + +pub type RialtoGrandpaInstance = (); +impl pallet_bridge_grandpa::Config for Runtime { + type BridgedChain = bp_rialto::Rialto; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type MaxBridgedAuthorities = MaxAuthoritiesAtRialto; + type MaxBridgedHeaderSize = MaxRialtoHeaderSize; + + type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; +} + +pub type WestendGrandpaInstance = pallet_bridge_grandpa::Instance1; +impl pallet_bridge_grandpa::Config for Runtime { + type BridgedChain = bp_westend::Westend; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type MaxBridgedAuthorities = MaxAuthoritiesAtWestend; + type MaxBridgedHeaderSize = MaxWestendHeaderSize; + + type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; +} + +impl pallet_shift_session_manager::Config for Runtime {} + +parameter_types! { + pub const MaxMessagesToPruneAtOnce: bp_messages::MessageNonce = 8; + pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_rialto::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + pub const RootAccountForPayments: Option = None; + pub const RialtoChainId: bp_runtime::ChainId = bp_runtime::RIALTO_CHAIN_ID; + pub const RialtoParachainChainId: bp_runtime::ChainId = bp_runtime::RIALTO_PARACHAIN_CHAIN_ID; + pub RialtoActiveOutboundLanes: &'static [bp_messages::LaneId] = &[rialto_messages::XCM_LANE]; + pub RialtoParachainActiveOutboundLanes: &'static [bp_messages::LaneId] = &[rialto_parachain_messages::XCM_LANE]; +} + +/// Instance of the messages pallet used to relay messages to/from Rialto chain. +pub type WithRialtoMessagesInstance = (); + +impl pallet_bridge_messages::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; + type ActiveOutboundLanes = RialtoActiveOutboundLanes; + type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = crate::rialto_messages::ToRialtoMaximalOutboundPayloadSize; + type OutboundPayload = crate::rialto_messages::ToRialtoMessagePayload; + + type InboundPayload = crate::rialto_messages::FromRialtoMessagePayload; + type InboundRelayer = bp_rialto::AccountId; + + type TargetHeaderChain = crate::rialto_messages::Rialto; + type LaneMessageVerifier = crate::rialto_messages::ToRialtoMessageVerifier; + type MessageDeliveryAndDispatchPayment = + pallet_bridge_relayers::MessageDeliveryAndDispatchPaymentAdapter< + Runtime, + WithRialtoMessagesInstance, + >; + + type SourceHeaderChain = crate::rialto_messages::Rialto; + type MessageDispatch = crate::rialto_messages::FromRialtoMessageDispatch; + type BridgedChainId = RialtoChainId; +} + +/// Instance of the messages pallet used to relay messages to/from RialtoParachain chain. +pub type WithRialtoParachainMessagesInstance = pallet_bridge_messages::Instance1; + +impl pallet_bridge_messages::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; + type ActiveOutboundLanes = RialtoParachainActiveOutboundLanes; + type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = + crate::rialto_parachain_messages::ToRialtoParachainMaximalOutboundPayloadSize; + type OutboundPayload = crate::rialto_parachain_messages::ToRialtoParachainMessagePayload; + + type InboundPayload = crate::rialto_parachain_messages::FromRialtoParachainMessagePayload; + type InboundRelayer = bp_rialto_parachain::AccountId; + + type TargetHeaderChain = crate::rialto_parachain_messages::RialtoParachain; + type LaneMessageVerifier = crate::rialto_parachain_messages::ToRialtoParachainMessageVerifier; + type MessageDeliveryAndDispatchPayment = + pallet_bridge_relayers::MessageDeliveryAndDispatchPaymentAdapter< + Runtime, + WithRialtoParachainMessagesInstance, + >; + + type SourceHeaderChain = crate::rialto_parachain_messages::RialtoParachain; + type MessageDispatch = crate::rialto_parachain_messages::FromRialtoParachainMessageDispatch; + type BridgedChainId = RialtoParachainChainId; +} + +parameter_types! { + pub const RialtoParasPalletName: &'static str = bp_rialto::PARAS_PALLET_NAME; + pub const WestendParasPalletName: &'static str = bp_westend::PARAS_PALLET_NAME; + pub const MaxRialtoParaHeadSize: u32 = bp_rialto::MAX_NESTED_PARACHAIN_HEAD_SIZE; + pub const MaxWestendParaHeadSize: u32 = bp_westend::MAX_NESTED_PARACHAIN_HEAD_SIZE; +} + +/// Instance of the with-Rialto parachains pallet. +pub type WithRialtoParachainsInstance = (); + +impl pallet_bridge_parachains::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight; + type BridgesGrandpaPalletInstance = RialtoGrandpaInstance; + type ParasPalletName = RialtoParasPalletName; + type TrackedParachains = frame_support::traits::Everything; + type HeadsToKeep = HeadersToKeep; + type MaxParaHeadSize = MaxRialtoParaHeadSize; +} + +/// Instance of the with-Westend parachains pallet. +pub type WithWestendParachainsInstance = pallet_bridge_parachains::Instance1; + +impl pallet_bridge_parachains::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight; + type BridgesGrandpaPalletInstance = WestendGrandpaInstance; + type ParasPalletName = WestendParasPalletName; + type TrackedParachains = frame_support::traits::Everything; + type HeadsToKeep = HeadersToKeep; + type MaxParaHeadSize = MaxWestendParaHeadSize; +} + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = opaque::Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, + + // Must be before session. + Aura: pallet_aura::{Pallet, Config}, + + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + + // Consensus support. + Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event}, + ShiftSessionManager: pallet_shift_session_manager::{Pallet}, + RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, + + // BEEFY Bridges support. + Beefy: pallet_beefy::{Pallet, Storage, Config}, + Mmr: pallet_mmr::{Pallet, Storage}, + MmrLeaf: pallet_beefy_mmr::{Pallet, Storage}, + + // Rialto bridge modules. + BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event}, + BridgeRialtoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage}, + BridgeRialtoMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event, Config}, + + // Westend bridge modules. + BridgeWestendGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Config, Storage}, + BridgeWestendParachains: pallet_bridge_parachains::::{Pallet, Call, Storage, Event}, + + // RialtoParachain bridge modules. + BridgeRialtoParachains: pallet_bridge_parachains::{Pallet, Call, Storage, Event}, + BridgeRialtoParachainMessages: pallet_bridge_messages::::{Pallet, Call, Storage, Event, Config}, + + // Pallet for sending XCM. + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 99, + } +); + +generate_bridge_reject_obsolete_headers_and_messages! { + RuntimeCall, AccountId, + // Grandpa + BridgeRialtoGrandpa, BridgeWestendGrandpa, + // Parachains + BridgeRialtoParachains, + //Messages + BridgeRialtoMessages, BridgeRialtoParachainMessages +} + +/// The address format for describing accounts. +pub type Address = AccountId; +/// Block header type as expected by this runtime. +pub type Header = generic::Header; +/// Block type as expected by this runtime. +pub type Block = generic::Block; +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + BridgeRejectObsoleteHeadersAndMessages, +); +/// The payload being signed in transactions. +pub type SignedPayload = generic::SignedPayload; +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = generic::CheckedExtrinsic; +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, +>; + +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block); + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Index { + System::account_nonce(account) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + } + + fn authorities() -> Vec { + Aura::authorities().to_vec() + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< + Block, + Balance, + > for Runtime { + fn query_info(uxt: ::Extrinsic, len: u32) -> RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details(uxt: ::Extrinsic, len: u32) -> FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, sp_core::crypto::KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl beefy_primitives::BeefyApi for Runtime { + fn validator_set() -> Option> { + Beefy::validator_set() + } + } + + impl sp_mmr_primitives::MmrApi for Runtime { + fn generate_proof(block_number: BlockNumber) + -> Result<(EncodableOpaqueLeaf, MmrProof), MmrError> + { + Mmr::generate_batch_proof(vec![block_number]) + .and_then(|(leaves, proof)| Ok(( + mmr::EncodableOpaqueLeaf::from_leaf(&leaves[0]), + mmr::BatchProof::into_single_leaf_proof(proof)? + ))) + } + + fn verify_proof(leaf: EncodableOpaqueLeaf, proof: MmrProof) + -> Result<(), MmrError> + { + let leaf: mmr::Leaf = leaf + .into_opaque_leaf() + .try_decode() + .ok_or(MmrError::Verify)?; + Mmr::verify_leaves(vec![leaf], mmr::Proof::into_batch_proof(proof)) + } + + fn verify_proof_stateless( + root: mmr::Hash, + leaf: EncodableOpaqueLeaf, + proof: MmrProof + ) -> Result<(), MmrError> { + let node = DataOrHash::Data(leaf.into_opaque_leaf()); + pallet_mmr::verify_leaves_proof::( + root, + vec![node], + pallet_mmr::primitives::Proof::into_batch_proof(proof), + ) + } + + fn mmr_root() -> Result { + Ok(Mmr::mmr_root()) + } + + fn generate_batch_proof(block_numbers: Vec) + -> Result<(Vec, mmr::BatchProof), mmr::Error> + { + Mmr::generate_batch_proof(block_numbers) + .map(|(leaves, proof)| (leaves.into_iter().map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf)).collect(), proof)) + } + + fn generate_historical_batch_proof( + block_numbers: Vec, + best_known_block_number: BlockNumber + ) -> Result<(Vec, mmr::BatchProof), mmr::Error> { + Mmr::generate_historical_batch_proof(block_numbers, best_known_block_number).map( + |(leaves, proof)| { + ( + leaves + .into_iter() + .map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf)) + .collect(), + proof, + ) + }, + ) + } + + fn verify_batch_proof(leaves: Vec, proof: mmr::BatchProof) + -> Result<(), mmr::Error> + { + let leaves = leaves.into_iter().map(|leaf| + leaf.into_opaque_leaf() + .try_decode() + .ok_or(mmr::Error::Verify)).collect::, mmr::Error>>()?; + Mmr::verify_leaves(leaves, proof) + } + + fn verify_batch_proof_stateless( + root: mmr::Hash, + leaves: Vec, + proof: mmr::BatchProof + ) -> Result<(), mmr::Error> { + let nodes = leaves.into_iter().map(|leaf|mmr::DataOrHash::Data(leaf.into_opaque_leaf())).collect(); + pallet_mmr::verify_leaves_proof::(root, nodes, proof) + } + } + + impl fg_primitives::GrandpaApi for Runtime { + fn current_set_id() -> fg_primitives::SetId { + Grandpa::current_set_id() + } + + fn grandpa_authorities() -> GrandpaAuthorityList { + Grandpa::grandpa_authorities() + } + + fn submit_report_equivocation_unsigned_extrinsic( + equivocation_proof: fg_primitives::EquivocationProof< + ::Hash, + NumberFor, + >, + key_owner_proof: fg_primitives::OpaqueKeyOwnershipProof, + ) -> Option<()> { + let key_owner_proof = key_owner_proof.decode()?; + + Grandpa::submit_unsigned_equivocation_report( + equivocation_proof, + key_owner_proof, + ) + } + + fn generate_key_ownership_proof( + _set_id: fg_primitives::SetId, + _authority_id: GrandpaId, + ) -> Option { + // NOTE: this is the only implementation possible since we've + // defined our key owner proof type as a bottom type (i.e. a type + // with no values). + None + } + } + + impl bp_rialto::RialtoFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeRialtoGrandpa::best_finalized().map(|header| header.id()) + } + } + + impl bp_westend::WestendFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeWestendGrandpa::best_finalized().map(|header| header.id()) + } + } + + impl bp_westend::WestmintFinalityApi for Runtime { + fn best_finalized() -> Option> { + // the parachains finality pallet is never decoding parachain heads, so it is + // only done in the integration code + use bp_westend::WESTMINT_PARACHAIN_ID; + let encoded_head = pallet_bridge_parachains::Pallet::< + Runtime, + WithWestendParachainsInstance, + >::best_parachain_head(WESTMINT_PARACHAIN_ID.into())?; + let head = bp_westend::Header::decode(&mut &encoded_head.0[..]).ok()?; + Some(head.id()) + } + } + + impl bp_rialto_parachain::RialtoParachainFinalityApi for Runtime { + fn best_finalized() -> Option> { + // the parachains finality pallet is never decoding parachain heads, so it is + // only done in the integration code + let encoded_head = pallet_bridge_parachains::Pallet::< + Runtime, + WithRialtoParachainsInstance, + >::best_parachain_head(bp_rialto_parachain::RIALTO_PARACHAIN_ID.into())?; + let head = bp_rialto_parachain::Header::decode(&mut &encoded_head.0[..]).ok()?; + Some(head.id()) + } + } + + impl bp_rialto::ToRialtoOutboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + begin: bp_messages::MessageNonce, + end: bp_messages::MessageNonce, + ) -> Vec { + bridge_runtime_common::messages_api::outbound_message_details::< + Runtime, + WithRialtoMessagesInstance, + >(lane, begin, end) + } + } + + impl bp_rialto::FromRialtoInboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, + ) -> Vec { + bridge_runtime_common::messages_api::inbound_message_details::< + Runtime, + WithRialtoMessagesInstance, + >(lane, messages) + } + } + + impl bp_rialto_parachain::ToRialtoParachainOutboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + begin: bp_messages::MessageNonce, + end: bp_messages::MessageNonce, + ) -> Vec { + bridge_runtime_common::messages_api::outbound_message_details::< + Runtime, + WithRialtoParachainMessagesInstance, + >(lane, begin, end) + } + } + + impl bp_rialto_parachain::FromRialtoParachainInboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, + ) -> Vec { + bridge_runtime_common::messages_api::inbound_message_details::< + Runtime, + WithRialtoParachainMessagesInstance, + >(lane, messages) + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + + use pallet_bridge_messages::benchmarking::Pallet as MessagesBench; + use pallet_bridge_parachains::benchmarking::Pallet as ParachainsBench; + + let mut list = Vec::::new(); + + list_benchmark!(list, extra, pallet_bridge_messages, MessagesBench::); + list_benchmark!(list, extra, pallet_bridge_grandpa, BridgeRialtoGrandpa); + list_benchmark!(list, extra, pallet_bridge_parachains, ParachainsBench::); + list_benchmark!(list, extra, pallet_bridge_relayers, BridgeRelayers); + + let storage_info = AllPalletsWithSystem::storage_info(); + + return (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig, + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, TrackedStorageKey, add_benchmark}; + + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + // Caller 0 Account + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da946c154ffd9992e395af90b5b13cc6f295c77033fce8a9045824a6690bbf99c6db269502f0a8d1d2a008542d5690a0749").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + + use bridge_runtime_common::messages_benchmarking::{prepare_message_delivery_proof, prepare_message_proof}; + use pallet_bridge_messages::benchmarking::{ + Pallet as MessagesBench, + Config as MessagesConfig, + MessageDeliveryProofParams, + MessageProofParams, + }; + use pallet_bridge_parachains::benchmarking::{ + Pallet as ParachainsBench, + Config as ParachainsConfig, + }; + use rialto_messages::WithRialtoMessageBridge; + + impl MessagesConfig for Runtime { + fn bridged_relayer_id() -> Self::InboundRelayer { + [0u8; 32].into() + } + + fn endow_account(account: &Self::AccountId) { + pallet_balances::Pallet::::make_free_balance_be( + account, + Balance::MAX / 100, + ); + } + + fn prepare_message_proof( + params: MessageProofParams, + ) -> (rialto_messages::FromRialtoMessagesProof, Weight) { + prepare_message_proof::( + params, + ) + } + + fn prepare_message_delivery_proof( + params: MessageDeliveryProofParams, + ) -> rialto_messages::ToRialtoMessagesDeliveryProof { + prepare_message_delivery_proof::( + params, + ) + } + + fn is_message_dispatched(_nonce: bp_messages::MessageNonce) -> bool { + true + } + } + + impl ParachainsConfig for Runtime { + fn prepare_parachain_heads_proof( + parachains: &[bp_polkadot_core::parachains::ParaId], + parachain_head_size: u32, + proof_size: bp_runtime::StorageProofSize, + ) -> ( + pallet_bridge_parachains::RelayBlockNumber, + pallet_bridge_parachains::RelayBlockHash, + bp_polkadot_core::parachains::ParaHeadsProof, + Vec<(bp_polkadot_core::parachains::ParaId, bp_polkadot_core::parachains::ParaHash)>, + ) { + bridge_runtime_common::parachains_benchmarking::prepare_parachain_heads_proof::( + parachains, + parachain_head_size, + proof_size, + ) + } + } + + add_benchmark!( + params, + batches, + pallet_bridge_messages, + MessagesBench:: + ); + add_benchmark!(params, batches, pallet_bridge_grandpa, BridgeRialtoGrandpa); + add_benchmark!( + params, + batches, + pallet_bridge_parachains, + ParachainsBench:: + ); + add_benchmark!(params, batches, pallet_bridge_relayers, BridgeRelayers); + + Ok(batches) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn call_size() { + const BRIDGES_PALLETS_MAX_CALL_SIZE: usize = 200; + assert!( + core::mem::size_of::>() <= + BRIDGES_PALLETS_MAX_CALL_SIZE + ); + assert!( + core::mem::size_of::>() <= + BRIDGES_PALLETS_MAX_CALL_SIZE + ); + const MAX_CALL_SIZE: usize = 230; // value from polkadot-runtime tests + assert!(core::mem::size_of::() <= MAX_CALL_SIZE); + } +} diff --git a/bin/millau/runtime/src/rialto_messages.rs b/bin/millau/runtime/src/rialto_messages.rs new file mode 100644 index 00000000000..ff4b6ba1dda --- /dev/null +++ b/bin/millau/runtime/src/rialto_messages.rs @@ -0,0 +1,244 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Everything required to serve Millau <-> Rialto messages. + +use crate::{RialtoGrandpaInstance, Runtime, RuntimeCall, RuntimeOrigin}; + +use bp_messages::{ + source_chain::TargetHeaderChain, + target_chain::{ProvedMessages, SourceHeaderChain}, + InboundLaneData, LaneId, Message, MessageNonce, +}; +use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_CHAIN_ID}; +use bridge_runtime_common::messages::{self, MessageBridge}; +use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; + +/// Default lane that is used to send messages to Rialto. +pub const XCM_LANE: LaneId = [0, 0, 0, 0]; +/// Weight of 2 XCM instructions is for simple `Trap(42)` program, coming through bridge +/// (it is prepended with `UniversalOrigin` instruction). It is used just for simplest manual +/// tests, confirming that we don't break encoding somewhere between. +pub const BASE_XCM_WEIGHT_TWICE: u64 = 2 * crate::xcm_config::BASE_XCM_WEIGHT; + +parameter_types! { + /// Weight credit for our test messages. + /// + /// 2 XCM instructions is for simple `Trap(42)` program, coming through bridge + /// (it is prepended with `UniversalOrigin` instruction). + pub const WeightCredit: Weight = Weight::from_ref_time(BASE_XCM_WEIGHT_TWICE); +} + +/// Message payload for Millau -> Rialto messages. +pub type ToRialtoMessagePayload = messages::source::FromThisChainMessagePayload; + +/// Message verifier for Millau -> Rialto messages. +pub type ToRialtoMessageVerifier = + messages::source::FromThisChainMessageVerifier; + +/// Message payload for Rialto -> Millau messages. +pub type FromRialtoMessagePayload = messages::target::FromBridgedChainMessagePayload; + +/// Messages proof for Rialto -> Millau messages. +pub type FromRialtoMessagesProof = messages::target::FromBridgedChainMessagesProof; + +/// Messages delivery proof for Millau -> Rialto messages. +pub type ToRialtoMessagesDeliveryProof = + messages::source::FromBridgedChainMessagesDeliveryProof; + +/// Call-dispatch based message dispatch for Rialto -> Millau messages. +pub type FromRialtoMessageDispatch = messages::target::FromBridgedChainMessageDispatch< + WithRialtoMessageBridge, + xcm_executor::XcmExecutor, + crate::xcm_config::XcmWeigher, + WeightCredit, +>; + +/// Maximal outbound payload size of Millau -> Rialto messages. +pub type ToRialtoMaximalOutboundPayloadSize = + messages::source::FromThisChainMaximalOutboundPayloadSize; + +/// Millau <-> Rialto message bridge. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct WithRialtoMessageBridge; + +impl MessageBridge for WithRialtoMessageBridge { + const THIS_CHAIN_ID: ChainId = MILLAU_CHAIN_ID; + const BRIDGED_CHAIN_ID: ChainId = RIALTO_CHAIN_ID; + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = bp_millau::WITH_MILLAU_MESSAGES_PALLET_NAME; + + type ThisChain = Millau; + type BridgedChain = Rialto; + type BridgedHeaderChain = + pallet_bridge_grandpa::GrandpaChainHeaders; +} + +/// Millau chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct Millau; + +impl messages::UnderlyingChainProvider for Millau { + type Chain = bp_millau::Millau; +} + +impl messages::ThisChainWithMessages for Millau { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + + fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool { + true + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + MessageNonce::MAX + } +} + +/// Rialto chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct Rialto; + +impl messages::UnderlyingChainProvider for Rialto { + type Chain = bp_rialto::Rialto; +} + +impl messages::BridgedChainWithMessages for Rialto { + fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { + true + } +} + +impl TargetHeaderChain for Rialto { + type Error = &'static str; + // The proof is: + // - hash of the header this proof has been created with; + // - the storage proof or one or several keys; + // - id of the lane we prove state of. + type MessagesDeliveryProof = ToRialtoMessagesDeliveryProof; + + fn verify_message(payload: &ToRialtoMessagePayload) -> Result<(), Self::Error> { + messages::source::verify_chain_message::(payload) + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error> { + messages::source::verify_messages_delivery_proof::(proof) + } +} + +impl SourceHeaderChain for Rialto { + type Error = &'static str; + // The proof is: + // - hash of the header this proof has been created with; + // - the storage proof or one or several keys; + // - id of the lane we prove messages for; + // - inclusive range of messages nonces that are proved. + type MessagesProof = FromRialtoMessagesProof; + + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result, Self::Error> { + messages::target::verify_messages_proof::(proof, messages_count) + .map_err(Into::into) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Runtime, WithRialtoMessagesInstance}; + + use bp_runtime::Chain; + use bridge_runtime_common::{ + assert_complete_bridge_types, + integrity::{ + assert_complete_bridge_constants, AssertBridgeMessagesPalletConstants, + AssertBridgePalletNames, AssertChainConstants, AssertCompleteBridgeConstants, + }, + messages, + }; + + #[test] + fn ensure_millau_message_lane_weights_are_correct() { + type Weights = pallet_bridge_messages::weights::BridgeWeight; + + pallet_bridge_messages::ensure_weights_are_correct::(); + + let max_incoming_message_proof_size = bp_rialto::EXTRA_STORAGE_PROOF_SIZE.saturating_add( + messages::target::maximal_incoming_message_size(bp_millau::Millau::max_extrinsic_size()), + ); + pallet_bridge_messages::ensure_able_to_receive_message::( + bp_millau::Millau::max_extrinsic_size(), + bp_millau::Millau::max_extrinsic_weight(), + max_incoming_message_proof_size, + messages::target::maximal_incoming_message_dispatch_weight( + bp_millau::Millau::max_extrinsic_weight(), + ), + ); + + let max_incoming_inbound_lane_data_proof_size = + bp_messages::InboundLaneData::<()>::encoded_size_hint_u32( + bp_millau::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as _, + ); + pallet_bridge_messages::ensure_able_to_receive_confirmation::( + bp_millau::Millau::max_extrinsic_size(), + bp_millau::Millau::max_extrinsic_weight(), + max_incoming_inbound_lane_data_proof_size, + bp_millau::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + bp_millau::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + ); + } + + #[test] + fn ensure_bridge_integrity() { + assert_complete_bridge_types!( + runtime: Runtime, + with_bridged_chain_grandpa_instance: RialtoGrandpaInstance, + with_bridged_chain_messages_instance: WithRialtoMessagesInstance, + bridge: WithRialtoMessageBridge, + this_chain: bp_millau::Millau, + bridged_chain: bp_rialto::Rialto, + ); + + assert_complete_bridge_constants::< + Runtime, + RialtoGrandpaInstance, + WithRialtoMessagesInstance, + WithRialtoMessageBridge, + bp_millau::Millau, + >(AssertCompleteBridgeConstants { + this_chain_constants: AssertChainConstants { + block_length: bp_millau::BlockLength::get(), + block_weights: bp_millau::BlockWeights::get(), + }, + messages_pallet_constants: AssertBridgeMessagesPalletConstants { + max_unrewarded_relayers_in_bridged_confirmation_tx: + bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + max_unconfirmed_messages_in_bridged_confirmation_tx: + bp_rialto::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + bridged_chain_id: bp_runtime::RIALTO_CHAIN_ID, + }, + pallet_names: AssertBridgePalletNames { + with_this_chain_messages_pallet_name: bp_millau::WITH_MILLAU_MESSAGES_PALLET_NAME, + with_bridged_chain_grandpa_pallet_name: bp_rialto::WITH_RIALTO_GRANDPA_PALLET_NAME, + with_bridged_chain_messages_pallet_name: + bp_rialto::WITH_RIALTO_MESSAGES_PALLET_NAME, + }, + }); + } +} diff --git a/bin/millau/runtime/src/rialto_parachain_messages.rs b/bin/millau/runtime/src/rialto_parachain_messages.rs new file mode 100644 index 00000000000..b6e8f52d342 --- /dev/null +++ b/bin/millau/runtime/src/rialto_parachain_messages.rs @@ -0,0 +1,167 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Everything required to serve Millau <-> RialtoParachain messages. + +use crate::{Runtime, RuntimeCall, RuntimeOrigin, WithRialtoParachainsInstance}; + +use bp_messages::{ + source_chain::TargetHeaderChain, + target_chain::{ProvedMessages, SourceHeaderChain}, + InboundLaneData, LaneId, Message, MessageNonce, +}; +use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_PARACHAIN_CHAIN_ID}; +use bridge_runtime_common::messages::{self, MessageBridge}; +use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; + +/// Default lane that is used to send messages to Rialto parachain. +pub const XCM_LANE: LaneId = [0, 0, 0, 0]; +/// Weight of 2 XCM instructions is for simple `Trap(42)` program, coming through bridge +/// (it is prepended with `UniversalOrigin` instruction). It is used just for simplest manual +/// tests, confirming that we don't break encoding somewhere between. +pub const BASE_XCM_WEIGHT_TWICE: u64 = 2 * crate::xcm_config::BASE_XCM_WEIGHT; + +parameter_types! { + /// Weight credit for our test messages. + /// + /// 2 XCM instructions is for simple `Trap(42)` program, coming through bridge + /// (it is prepended with `UniversalOrigin` instruction). + pub const WeightCredit: Weight = Weight::from_ref_time(BASE_XCM_WEIGHT_TWICE); +} + +/// Message payload for Millau -> RialtoParachain messages. +pub type ToRialtoParachainMessagePayload = messages::source::FromThisChainMessagePayload; + +/// Message verifier for Millau -> RialtoParachain messages. +pub type ToRialtoParachainMessageVerifier = + messages::source::FromThisChainMessageVerifier; + +/// Message payload for RialtoParachain -> Millau messages. +pub type FromRialtoParachainMessagePayload = + messages::target::FromBridgedChainMessagePayload; + +/// Messages proof for RialtoParachain -> Millau messages. +type FromRialtoParachainMessagesProof = + messages::target::FromBridgedChainMessagesProof; + +/// Messages delivery proof for Millau -> RialtoParachain messages. +type ToRialtoParachainMessagesDeliveryProof = + messages::source::FromBridgedChainMessagesDeliveryProof; + +/// Call-dispatch based message dispatch for RialtoParachain -> Millau messages. +pub type FromRialtoParachainMessageDispatch = messages::target::FromBridgedChainMessageDispatch< + WithRialtoParachainMessageBridge, + xcm_executor::XcmExecutor, + crate::xcm_config::XcmWeigher, + WeightCredit, +>; + +/// Maximal outbound payload size of Millau -> RialtoParachain messages. +pub type ToRialtoParachainMaximalOutboundPayloadSize = + messages::source::FromThisChainMaximalOutboundPayloadSize; + +/// Millau <-> RialtoParachain message bridge. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct WithRialtoParachainMessageBridge; + +impl MessageBridge for WithRialtoParachainMessageBridge { + const THIS_CHAIN_ID: ChainId = MILLAU_CHAIN_ID; + const BRIDGED_CHAIN_ID: ChainId = RIALTO_PARACHAIN_CHAIN_ID; + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = bp_millau::WITH_MILLAU_MESSAGES_PALLET_NAME; + + type ThisChain = Millau; + type BridgedChain = RialtoParachain; + type BridgedHeaderChain = pallet_bridge_parachains::ParachainHeaders< + Runtime, + WithRialtoParachainsInstance, + bp_rialto_parachain::RialtoParachain, + >; +} + +/// Millau chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct Millau; + +impl messages::UnderlyingChainProvider for Millau { + type Chain = bp_millau::Millau; +} + +impl messages::ThisChainWithMessages for Millau { + type RuntimeCall = RuntimeCall; + type RuntimeOrigin = RuntimeOrigin; + + fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool { + true + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + MessageNonce::MAX + } +} + +/// RialtoParachain chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct RialtoParachain; + +impl messages::UnderlyingChainProvider for RialtoParachain { + type Chain = bp_rialto_parachain::RialtoParachain; +} + +impl messages::BridgedChainWithMessages for RialtoParachain { + fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { + true + } +} + +impl TargetHeaderChain for RialtoParachain { + type Error = &'static str; + // The proof is: + // - hash of the header this proof has been created with; + // - the storage proof or one or several keys; + // - id of the lane we prove state of. + type MessagesDeliveryProof = ToRialtoParachainMessagesDeliveryProof; + + fn verify_message(payload: &ToRialtoParachainMessagePayload) -> Result<(), Self::Error> { + messages::source::verify_chain_message::(payload) + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error> { + messages::source::verify_messages_delivery_proof::(proof) + } +} + +impl SourceHeaderChain for RialtoParachain { + type Error = &'static str; + // The proof is: + // - hash of the header this proof has been created with; + // - the storage proof or one or several keys; + // - id of the lane we prove messages for; + // - inclusive range of messages nonces that are proved. + type MessagesProof = FromRialtoParachainMessagesProof; + + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result, Self::Error> { + messages::target::verify_messages_proof::( + proof, + messages_count, + ) + .map_err(Into::into) + } +} diff --git a/bin/millau/runtime/src/xcm_config.rs b/bin/millau/runtime/src/xcm_config.rs new file mode 100644 index 00000000000..41f336e61b8 --- /dev/null +++ b/bin/millau/runtime/src/xcm_config.rs @@ -0,0 +1,326 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! XCM configurations for the Millau runtime. + +use super::{ + rialto_messages::{WithRialtoMessageBridge, XCM_LANE}, + rialto_parachain_messages::{WithRialtoParachainMessageBridge, XCM_LANE as XCM_LANE_PARACHAIN}, + AccountId, AllPalletsWithSystem, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + WithRialtoMessagesInstance, WithRialtoParachainMessagesInstance, XcmPallet, +}; +use bp_messages::LaneId; +use bp_millau::WeightToFee; +use bp_rialto_parachain::RIALTO_PARACHAIN_ID; +use bridge_runtime_common::{ + messages::source::{XcmBridge, XcmBridgeAdapter}, + CustomNetworkId, +}; +use frame_support::{ + parameter_types, + traits::{Everything, Nothing}, +}; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, + CurrencyAdapter as XcmCurrencyAdapter, IsConcrete, MintLocation, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, +}; + +parameter_types! { + /// The location of the `MLAU` token, from the context of this chain. Since this token is native to this + /// chain, we make it synonymous with it and thus it is the `Here` location, which means "equivalent to + /// the context". + pub const TokenLocation: MultiLocation = Here.into_location(); + /// The Millau network ID. + pub const ThisNetwork: NetworkId = CustomNetworkId::Millau.as_network_id(); + /// The Rialto network ID. + pub const RialtoNetwork: NetworkId = CustomNetworkId::Rialto.as_network_id(); + /// The RialtoParachain network ID. + pub const RialtoParachainNetwork: NetworkId = CustomNetworkId::RialtoParachain.as_network_id(); + + /// Our XCM location ancestry - i.e. our location within the Consensus Universe. + /// + /// Since Kusama is a top-level relay-chain with its own consensus, it's just our network ID. + pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); + /// The check account, which holds any native assets that have been teleported out and not back in (yet). + pub CheckAccount: (AccountId, MintLocation) = (XcmPallet::check_account(), MintLocation::Local); +} + +/// The canonical means of converting a `MultiLocation` into an `AccountId`, used when we want to +/// determine the sovereign account controlled by a location. +pub type SovereignAccountOf = ( + // We can directly alias an `AccountId32` into a local account. + AccountId32Aliases, +); + +/// Our asset transactor. This is what allows us to interest with the runtime facilities from the +/// point of view of XCM-only concepts like `MultiLocation` and `MultiAsset`. +/// +/// Ours is only aware of the Balances pallet, which is mapped to `TokenLocation`. +pub type LocalAssetTransactor = XcmCurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // We can convert the MultiLocations with our converter above: + SovereignAccountOf, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We track our teleports in/out to keep total issuance correct. + CheckAccount, +>; + +/// The means that we convert the XCM message origin location into a local dispatch origin. +type LocalOriginConverter = ( + // A `Signed` origin of the sovereign account that the original location controls. + SovereignSignedViaLocation, + // The AccountId32 location type can be expressed natively as a `Signed` origin. + SignedAccountId32AsNative, +); + +/// The amount of weight an XCM operation takes. This is a safe overestimate. +pub const BASE_XCM_WEIGHT: u64 = 1_000_000_000; + +parameter_types! { + /// The amount of weight an XCM operation takes. This is a safe overestimate. + pub const BaseXcmWeight: u64 = BASE_XCM_WEIGHT; + /// Maximum number of instructions in a single XCM fragment. A sanity check against weight + /// calculations getting too crazy. + pub const MaxInstructions: u32 = 100; +} + +/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our +/// individual routers. +pub type XcmRouter = ( + // Router to send messages to Rialto. + XcmBridgeAdapter, + // Router to send messages to RialtoParachains. + XcmBridgeAdapter, +); + +parameter_types! { + pub const MaxAssetsIntoHolding: u32 = 64; +} + +/// The barriers one of which must be passed for an XCM message to be executed. +pub type Barrier = ( + // Weight that is paid for may be consumed. + TakeWeightCredit, + // If the message is one that immediately attemps to pay for execution, then allow it. + AllowTopLevelPaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, +); + +/// XCM weigher type. +pub type XcmWeigher = xcm_builder::FixedWeightBounds; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = XcmWeigher; + // The weight trader piggybacks on the existing transaction-fee conversion logic. + type Trader = UsingComponents; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; +} + +/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior +/// location of this chain. +pub type LocalOriginToLocation = ( + // Usual Signed origin to be used in XCM as a corresponding AccountId32 + SignedToAccountId32, +); + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + // We don't allow any messages to be sent via the transaction yet. This is basically safe to + // enable, (safe the possibility of someone spamming the parachain if they're willing to pay + // the DOT to send from the Relay-chain). But it's useless until we bring in XCM v3 which will + // make `DescendOrigin` a bit more useful. + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // Anyone can execute XCM messages locally. + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = xcm_executor::XcmExecutor; + // Anyone is able to use teleportation regardless of who they are and what they want to + // teleport. + type XcmTeleportFilter = Everything; + // Anyone is able to use reserve transfers regardless of who they are and what they want to + // transfer. + type XcmReserveTransferFilter = Everything; + type Weigher = XcmWeigher; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = frame_support::traits::ConstU32<8>; +} + +/// With-Rialto bridge. +pub struct ToRialtoBridge; + +impl XcmBridge for ToRialtoBridge { + type MessageBridge = WithRialtoMessageBridge; + type MessageSender = pallet_bridge_messages::Pallet; + + fn universal_location() -> InteriorMultiLocation { + UniversalLocation::get() + } + + fn verify_destination(dest: &MultiLocation) -> bool { + matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == RialtoNetwork::get()) + } + + fn build_destination() -> MultiLocation { + let dest: InteriorMultiLocation = RialtoNetwork::get().into(); + let here = UniversalLocation::get(); + dest.relative_to(&here) + } + + fn xcm_lane() -> LaneId { + XCM_LANE + } +} + +/// With-RialtoParachain bridge. +pub struct ToRialtoParachainBridge; + +impl XcmBridge for ToRialtoParachainBridge { + type MessageBridge = WithRialtoParachainMessageBridge; + type MessageSender = + pallet_bridge_messages::Pallet; + + fn universal_location() -> InteriorMultiLocation { + UniversalLocation::get() + } + + fn verify_destination(dest: &MultiLocation) -> bool { + matches!(*dest, MultiLocation { parents: 1, interior: X2(GlobalConsensus(r), Parachain(RIALTO_PARACHAIN_ID)) } if r == RialtoNetwork::get()) + } + + fn build_destination() -> MultiLocation { + let dest: InteriorMultiLocation = RialtoParachainNetwork::get().into(); + let here = UniversalLocation::get(); + dest.relative_to(&here) + } + + fn xcm_lane() -> LaneId { + XCM_LANE_PARACHAIN + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::rialto_messages::WeightCredit; + use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, + MessageKey, + }; + use bp_runtime::messages::MessageDispatchResult; + use bridge_runtime_common::messages::target::FromBridgedChainMessageDispatch; + use codec::Encode; + + fn new_test_ext() -> sp_io::TestExternalities { + sp_io::TestExternalities::new( + frame_system::GenesisConfig::default().build_storage::().unwrap(), + ) + } + + #[test] + fn xcm_messages_are_sent_using_bridge_router() { + new_test_ext().execute_with(|| { + let xcm: Xcm<()> = vec![Instruction::Trap(42)].into(); + let expected_fee = MultiAssets::from((Here, 1_000_000_u128)); + let expected_hash = + ([0u8, 0u8, 0u8, 0u8], 1u64).using_encoded(sp_io::hashing::blake2_256); + + // message 1 to Rialto + let dest = (Parent, X1(GlobalConsensus(RialtoNetwork::get()))); + let send_result = send_xcm::(dest.into(), xcm.clone()); + assert_eq!(send_result, Ok((expected_hash, expected_fee.clone()))); + + // message 2 to RialtoParachain (expected hash is the same, since other lane is used) + let dest = + (Parent, X2(GlobalConsensus(RialtoNetwork::get()), Parachain(RIALTO_PARACHAIN_ID))); + let send_result = send_xcm::(dest.into(), xcm); + assert_eq!(send_result, Ok((expected_hash, expected_fee))); + }) + } + + #[test] + fn xcm_messages_from_rialto_are_dispatched() { + type XcmExecutor = xcm_executor::XcmExecutor; + type MessageDispatcher = FromBridgedChainMessageDispatch< + WithRialtoMessageBridge, + XcmExecutor, + XcmWeigher, + WeightCredit, + >; + + new_test_ext().execute_with(|| { + let location: MultiLocation = + (Parent, X1(GlobalConsensus(RialtoNetwork::get()))).into(); + let xcm: Xcm = vec![Instruction::Trap(42)].into(); + + let mut incoming_message = DispatchMessage { + key: MessageKey { lane_id: [0, 0, 0, 0], nonce: 1 }, + data: DispatchMessageData { payload: Ok((location, xcm).into()) }, + }; + + let dispatch_weight = MessageDispatcher::dispatch_weight(&mut incoming_message); + assert_eq!( + dispatch_weight, + frame_support::weights::Weight::from_ref_time(1_000_000_000) + ); + + let dispatch_result = + MessageDispatcher::dispatch(&AccountId::from([0u8; 32]), incoming_message); + assert_eq!( + dispatch_result, + MessageDispatchResult { + unspent_weight: frame_support::weights::Weight::from_ref_time(0), + dispatch_fee_paid_during_dispatch: false, + } + ); + }) + } +} diff --git a/bin/rialto-parachain/node/Cargo.toml b/bin/rialto-parachain/node/Cargo.toml new file mode 100644 index 00000000000..03d1a28c6a4 --- /dev/null +++ b/bin/rialto-parachain/node/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = "rialto-parachain-collator" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +repository = "https://github.com/paritytech/parity-bridges-common/" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[build-dependencies] +substrate-build-script-utils = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[[bin]] +name = 'rialto-parachain-collator' + +[features] +default = [] +runtime-benchmarks = ['rialto-parachain-runtime/runtime-benchmarks'] + +[dependencies] +clap = { version = "4.0.9", features = ["derive"] } +log = '0.4.17' +codec = { package = 'parity-scale-codec', version = '3.1.5' } +serde = { version = '1.0', features = ['derive'] } + +# RPC related Dependencies +jsonrpsee = { version = "0.15.1", features = ["server"] } + +# Local Dependencies +rialto-parachain-runtime = { path = '../runtime' } + +# Substrate Dependencies +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "master" } + +pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" } + +substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "master" } + +## Substrate Client Dependencies +sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-service = { git = "https://github.com/paritytech/substrate", branch = "master", features = ['wasmtime'] } +sc-sysinfo = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-tracing = { git = "https://github.com/paritytech/substrate", branch = "master" } + +## Substrate Primitive Dependencies +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" } + +# Cumulus dependencies +cumulus-client-consensus-aura = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-client-consensus-common = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-client-cli = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-client-network = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-client-service = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-relay-chain-interface = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-relay-chain-inprocess-interface = { git = "https://github.com/paritytech/cumulus", branch = "master" } +cumulus-relay-chain-minimal-node = { git = "https://github.com/paritytech/cumulus", branch = "master" } + +# Polkadot dependencies +polkadot-cli = { git = "https://github.com/paritytech/polkadot", branch = "master" } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" } +polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "master" } diff --git a/bin/rialto-parachain/node/build.rs b/bin/rialto-parachain/node/build.rs new file mode 100644 index 00000000000..8ba8a31e9a7 --- /dev/null +++ b/bin/rialto-parachain/node/build.rs @@ -0,0 +1,22 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; + +fn main() { + generate_cargo_keys(); + rerun_if_git_head_changed(); +} diff --git a/bin/rialto-parachain/node/src/chain_spec.rs b/bin/rialto-parachain/node/src/chain_spec.rs new file mode 100644 index 00000000000..bfce4f717c6 --- /dev/null +++ b/bin/rialto-parachain/node/src/chain_spec.rs @@ -0,0 +1,198 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use cumulus_primitives_core::ParaId; +use rialto_parachain_runtime::{AccountId, AuraId, BridgeMillauMessagesConfig, Signature}; +use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; +use sc_service::ChainType; +use serde::{Deserialize, Serialize}; +use sp_core::{sr25519, Pair, Public}; +use sp_runtime::traits::{IdentifyAccount, Verify}; + +/// "Names" of the authorities accounts at local testnet. +const LOCAL_AUTHORITIES_ACCOUNTS: [&str; 2] = ["Alice", "Bob"]; +/// "Names" of the authorities accounts at development testnet. +const DEV_AUTHORITIES_ACCOUNTS: [&str; 2] = LOCAL_AUTHORITIES_ACCOUNTS; +/// "Names" of all possible authorities accounts. +const ALL_AUTHORITIES_ACCOUNTS: [&str; 2] = LOCAL_AUTHORITIES_ACCOUNTS; +/// "Name" of the `sudo` account. +const SUDO_ACCOUNT: &str = "Sudo"; +/// "Name" of the account, which owns the with-Millau messages pallet. +const MILLAU_MESSAGES_PALLET_OWNER: &str = "Millau.MessagesOwner"; + +/// Specialized `ChainSpec` for the normal parachain runtime. +pub type ChainSpec = + sc_service::GenericChainSpec; + +/// Helper function to generate a crypto pair from seed +pub fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{seed}"), None) + .expect("static values are valid; qed") + .public() +} + +/// The extensions for the [`ChainSpec`]. +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension, +)] +#[serde(deny_unknown_fields)] +pub struct Extensions { + /// The relay chain of the Parachain. + pub relay_chain: String, + /// The id of the Parachain. + pub para_id: u32, +} + +impl Extensions { + /// Try to get the extension from the given `ChainSpec`. + pub fn try_get(chain_spec: &dyn sc_service::ChainSpec) -> Option<&Self> { + sc_chain_spec::get_extension(chain_spec.extensions()) + } +} + +type AccountPublic = ::Signer; + +/// Helper function to generate an account ID from seed +pub fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() +} + +/// We're using the same set of endowed accounts on all RialtoParachain chains (dev/local) to make +/// sure that all accounts, required for bridge to be functional (e.g. relayers fund account, +/// accounts used by relayers in our test deployments, accounts used for demonstration +/// purposes), are all available on these chains. +fn endowed_accounts() -> Vec { + let all_authorities = ALL_AUTHORITIES_ACCOUNTS.iter().flat_map(|x| { + [ + get_account_id_from_seed::(x), + get_account_id_from_seed::(&format!("{x}//stash")), + ] + }); + vec![ + // Sudo account + get_account_id_from_seed::(SUDO_ACCOUNT), + // Regular (unused) accounts + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + // Accounts, used by RialtoParachain<>Millau bridge + get_account_id_from_seed::(MILLAU_MESSAGES_PALLET_OWNER), + get_account_id_from_seed::("Millau.HeadersAndMessagesRelay1"), + get_account_id_from_seed::("Millau.HeadersAndMessagesRelay2"), + get_account_id_from_seed::("Millau.MessagesSender"), + ] + .into_iter() + .chain(all_authorities) + .collect() +} + +pub fn development_config(id: ParaId) -> ChainSpec { + // Give your base currency a unit name and decimal places + let mut properties = sc_chain_spec::Properties::new(); + properties.insert("tokenSymbol".into(), "UNIT".into()); + properties.insert("tokenDecimals".into(), 12.into()); + + ChainSpec::from_genesis( + // Name + "Development", + // ID + "dev", + ChainType::Local, + move || { + testnet_genesis( + get_account_id_from_seed::(SUDO_ACCOUNT), + DEV_AUTHORITIES_ACCOUNTS.into_iter().map(get_from_seed::).collect(), + endowed_accounts(), + id, + ) + }, + vec![], + None, + None, + None, + None, + Extensions { + relay_chain: "rococo-local".into(), // You MUST set this to the correct network! + para_id: id.into(), + }, + ) +} + +pub fn local_testnet_config(id: ParaId) -> ChainSpec { + // Give your base currency a unit name and decimal places + let mut properties = sc_chain_spec::Properties::new(); + properties.insert("tokenSymbol".into(), "UNIT".into()); + properties.insert("tokenDecimals".into(), 12.into()); + + ChainSpec::from_genesis( + // Name + "Local Testnet", + // ID + "local_testnet", + ChainType::Local, + move || { + testnet_genesis( + get_account_id_from_seed::(SUDO_ACCOUNT), + LOCAL_AUTHORITIES_ACCOUNTS.into_iter().map(get_from_seed::).collect(), + endowed_accounts(), + id, + ) + }, + Vec::new(), + None, + None, + None, + None, + Extensions { + relay_chain: "rococo-local".into(), // You MUST set this to the correct network! + para_id: id.into(), + }, + ) +} + +fn testnet_genesis( + root_key: AccountId, + initial_authorities: Vec, + endowed_accounts: Vec, + id: ParaId, +) -> rialto_parachain_runtime::GenesisConfig { + rialto_parachain_runtime::GenesisConfig { + system: rialto_parachain_runtime::SystemConfig { + code: rialto_parachain_runtime::WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + }, + balances: rialto_parachain_runtime::BalancesConfig { + balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), + }, + sudo: rialto_parachain_runtime::SudoConfig { key: Some(root_key) }, + parachain_info: rialto_parachain_runtime::ParachainInfoConfig { parachain_id: id }, + aura: rialto_parachain_runtime::AuraConfig { authorities: initial_authorities }, + aura_ext: Default::default(), + bridge_millau_messages: BridgeMillauMessagesConfig { + owner: Some(get_account_id_from_seed::(MILLAU_MESSAGES_PALLET_OWNER)), + ..Default::default() + }, + } +} diff --git a/bin/rialto-parachain/node/src/cli.rs b/bin/rialto-parachain/node/src/cli.rs new file mode 100644 index 00000000000..51fa1e9776c --- /dev/null +++ b/bin/rialto-parachain/node/src/cli.rs @@ -0,0 +1,140 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::chain_spec; +use clap::Parser; +use std::path::PathBuf; + +/// Sub-commands supported by the collator. +#[derive(Debug, Parser)] +pub enum Subcommand { + /// Export the genesis state of the parachain. + #[clap(name = "export-genesis-state")] + ExportGenesisState(ExportGenesisStateCommand), + + /// Export the genesis wasm of the parachain. + #[clap(name = "export-genesis-wasm")] + ExportGenesisWasm(ExportGenesisWasmCommand), + + /// Build a chain specification. + BuildSpec(sc_cli::BuildSpecCmd), + + /// Validate blocks. + CheckBlock(sc_cli::CheckBlockCmd), + + /// Export blocks. + ExportBlocks(sc_cli::ExportBlocksCmd), + + /// Export the state of a given block into a chain spec. + ExportState(sc_cli::ExportStateCmd), + + /// Import blocks. + ImportBlocks(sc_cli::ImportBlocksCmd), + + /// Remove the whole chain. + PurgeChain(cumulus_client_cli::PurgeChainCmd), + + /// Revert the chain to a previous state. + Revert(sc_cli::RevertCmd), + + /// The custom benchmark subcommand benchmarking runtime pallets. + #[clap(subcommand)] + Benchmark(frame_benchmarking_cli::BenchmarkCmd), +} + +/// Command for exporting the genesis state of the parachain +#[derive(Debug, Parser)] +pub struct ExportGenesisStateCommand { + /// Output file name or stdout if unspecified. + #[clap(action)] + pub output: Option, + + /// Id of the parachain this state is for. + /// + /// Default: 100 + #[clap(long, conflicts_with = "chain")] + pub parachain_id: Option, + + /// Write output in binary. Default is to write in hex. + #[clap(short, long)] + pub raw: bool, + + /// The name of the chain for that the genesis state should be exported. + #[clap(long, conflicts_with = "parachain-id")] + pub chain: Option, +} + +/// Command for exporting the genesis wasm file. +#[derive(Debug, Parser)] +pub struct ExportGenesisWasmCommand { + /// Output file name or stdout if unspecified. + #[clap(action)] + pub output: Option, + + /// Write output in binary. Default is to write in hex. + #[clap(short, long)] + pub raw: bool, + + /// The name of the chain for that the genesis wasm file should be exported. + #[clap(long)] + pub chain: Option, +} + +#[derive(Debug, Parser)] +#[clap( + propagate_version = true, + args_conflicts_with_subcommands = true, + subcommand_negates_reqs = true +)] +pub struct Cli { + #[clap(subcommand)] + pub subcommand: Option, + + #[clap(long)] + pub parachain_id: Option, + + #[clap(flatten)] + pub run: cumulus_client_cli::RunCmd, + + /// Relaychain arguments + #[clap(raw = true)] + pub relaychain_args: Vec, +} + +#[derive(Debug)] +pub struct RelayChainCli { + /// The actual relay chain CLI object. + pub base: polkadot_cli::RunCmd, + + /// Optional chain id that should be passed to the relay chain. + pub chain_id: Option, + + /// The base path that should be used by the relay chain. + pub base_path: Option, +} + +impl RelayChainCli { + /// Parse the relay chain CLI parameters using the para chain `Configuration`. + pub fn new<'a>( + para_config: &sc_service::Configuration, + relay_chain_args: impl Iterator, + ) -> Self { + let extension = chain_spec::Extensions::try_get(&*para_config.chain_spec); + let chain_id = extension.map(|e| e.relay_chain.clone()); + let base_path = para_config.base_path.as_ref().map(|x| x.path().join("rialto-bridge-node")); + Self { base_path, chain_id, base: polkadot_cli::RunCmd::parse_from(relay_chain_args) } + } +} diff --git a/bin/rialto-parachain/node/src/command.rs b/bin/rialto-parachain/node/src/command.rs new file mode 100644 index 00000000000..dd9e95abbe5 --- /dev/null +++ b/bin/rialto-parachain/node/src/command.rs @@ -0,0 +1,446 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{ + chain_spec, + cli::{Cli, RelayChainCli, Subcommand}, + service::{new_partial, ParachainRuntimeExecutor}, +}; +use codec::Encode; +use cumulus_client_cli::generate_genesis_block; +use cumulus_primitives_core::ParaId; +use frame_benchmarking_cli::BenchmarkCmd; +use log::info; +use rialto_parachain_runtime::{Block, RuntimeApi}; +use sc_cli::{ + ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, + NetworkParams, Result, RuntimeVersion, SharedParams, SubstrateCli, +}; +use sc_service::config::{BasePath, PrometheusConfig}; +use sp_core::hexdisplay::HexDisplay; +use sp_runtime::traits::{AccountIdConversion, Block as BlockT}; +use std::{io::Write, net::SocketAddr}; + +fn load_spec( + id: &str, + para_id: ParaId, +) -> std::result::Result, String> { + Ok(match id { + "dev" => Box::new(chain_spec::development_config(para_id)), + "" | "local" => Box::new(chain_spec::local_testnet_config(para_id)), + path => Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), + }) +} + +impl SubstrateCli for Cli { + fn impl_name() -> String { + "Parachain Collator Template".into() + } + + fn impl_version() -> String { + env!("SUBSTRATE_CLI_IMPL_VERSION").into() + } + + fn description() -> String { + format!( + "Parachain Collator Template\n\nThe command-line arguments provided first will be \ + passed to the parachain node, while the arguments provided after -- will be passed \ + to the relaychain node.\n\n\ + {} [parachain-args] -- [relaychain-args]", + Self::executable_name() + ) + } + + fn author() -> String { + env!("CARGO_PKG_AUTHORS").into() + } + + fn support_url() -> String { + "https://github.com/substrate-developer-hub/substrate-parachain-template/issues/new".into() + } + + fn copyright_start_year() -> i32 { + 2017 + } + + fn load_spec(&self, id: &str) -> std::result::Result, String> { + load_spec(id, self.parachain_id.unwrap_or(2000).into()) + } + + fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { + &rialto_parachain_runtime::VERSION + } +} + +impl SubstrateCli for RelayChainCli { + fn impl_name() -> String { + "Parachain Collator Template".into() + } + + fn impl_version() -> String { + env!("SUBSTRATE_CLI_IMPL_VERSION").into() + } + + fn description() -> String { + "Parachain Collator Template\n\nThe command-line arguments provided first will be \ + passed to the parachain node, while the arguments provided after -- will be passed \ + to the relaychain node.\n\n\ + parachain-collator [parachain-args] -- [relaychain-args]" + .into() + } + + fn author() -> String { + env!("CARGO_PKG_AUTHORS").into() + } + + fn support_url() -> String { + "https://github.com/substrate-developer-hub/substrate-parachain-template/issues/new".into() + } + + fn copyright_start_year() -> i32 { + 2017 + } + + fn load_spec(&self, id: &str) -> std::result::Result, String> { + polkadot_cli::Cli::from_iter([RelayChainCli::executable_name()].iter()).load_spec(id) + } + + fn native_runtime_version(chain_spec: &Box) -> &'static RuntimeVersion { + polkadot_cli::Cli::native_runtime_version(chain_spec) + } +} + +fn extract_genesis_wasm(chain_spec: &dyn sc_service::ChainSpec) -> Result> { + let mut storage = chain_spec.build_storage()?; + + storage + .top + .remove(sp_core::storage::well_known_keys::CODE) + .ok_or_else(|| "Could not find wasm file in genesis state!".into()) +} + +macro_rules! construct_async_run { + (|$components:ident, $cli:ident, $cmd:ident, $config:ident| $( $code:tt )* ) => {{ + let runner = $cli.create_runner($cmd)?; + runner.async_run(|$config| { + let $components = new_partial::< + RuntimeApi, + ParachainRuntimeExecutor, + _ + >( + &$config, + crate::service::parachain_build_import_queue, + )?; + let task_manager = $components.task_manager; + { $( $code )* }.map(|v| (v, task_manager)) + }) + }} +} + +/// Parse command line arguments into service configuration. +pub fn run() -> Result<()> { + let cli = Cli::from_args(); + sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom( + rialto_parachain_runtime::SS58Prefix::get() as u16, + )); + + match &cli.subcommand { + Some(Subcommand::BuildSpec(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) + }, + Some(Subcommand::CheckBlock(cmd)) => { + construct_async_run!(|components, cli, cmd, config| { + Ok(cmd.run(components.client, components.import_queue)) + }) + }, + Some(Subcommand::ExportBlocks(cmd)) => { + construct_async_run!(|components, cli, cmd, config| Ok( + cmd.run(components.client, config.database) + )) + }, + Some(Subcommand::ExportState(cmd)) => { + construct_async_run!(|components, cli, cmd, config| Ok( + cmd.run(components.client, config.chain_spec) + )) + }, + Some(Subcommand::ImportBlocks(cmd)) => { + construct_async_run!(|components, cli, cmd, config| { + Ok(cmd.run(components.client, components.import_queue)) + }) + }, + Some(Subcommand::PurgeChain(cmd)) => { + let runner = cli.create_runner(cmd)?; + + runner.sync_run(|config| { + let polkadot_cli = RelayChainCli::new( + &config, + [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args.iter()), + ); + + let polkadot_config = SubstrateCli::create_configuration( + &polkadot_cli, + &polkadot_cli, + config.tokio_handle.clone(), + ) + .map_err(|err| format!("Relay chain argument error: {err}"))?; + + cmd.run(config, polkadot_config) + }) + }, + Some(Subcommand::Revert(cmd)) => { + construct_async_run!(|components, cli, cmd, config| Ok(cmd.run( + components.client, + components.backend, + None + ))) + }, + Some(Subcommand::ExportGenesisState(params)) => { + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_profiling(sc_tracing::TracingReceiver::Log, ""); + let _ = builder.init(); + + let spec = load_spec( + ¶ms.chain.clone().unwrap_or_default(), + params.parachain_id.expect("Missing ParaId").into(), + )?; + let state_version = Cli::native_runtime_version(&spec).state_version(); + let block: Block = generate_genesis_block(&*spec, state_version)?; + let raw_header = block.header().encode(); + let output_buf = if params.raw { + raw_header + } else { + format!("0x{:?}", HexDisplay::from(&block.header().encode())).into_bytes() + }; + + if let Some(output) = ¶ms.output { + std::fs::write(output, output_buf)?; + } else { + std::io::stdout().write_all(&output_buf)?; + } + + Ok(()) + }, + Some(Subcommand::ExportGenesisWasm(params)) => { + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_profiling(sc_tracing::TracingReceiver::Log, ""); + let _ = builder.init(); + + let raw_wasm_blob = + extract_genesis_wasm(&*cli.load_spec(¶ms.chain.clone().unwrap_or_default())?)?; + let output_buf = if params.raw { + raw_wasm_blob + } else { + format!("0x{:?}", HexDisplay::from(&raw_wasm_blob)).into_bytes() + }; + + if let Some(output) = ¶ms.output { + std::fs::write(output, output_buf)?; + } else { + std::io::stdout().write_all(&output_buf)?; + } + + Ok(()) + }, + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + match cmd { + BenchmarkCmd::Pallet(cmd) => + if cfg!(feature = "runtime-benchmarks") { + runner.sync_run(|config| cmd.run::(config)) + } else { + println!( + "Benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + ); + Ok(()) + }, + _ => Err("Unsupported benchmarking subcommand".into()), + } + }, + None => { + let runner = cli.create_runner(&cli.run.normalize())?; + let collator_options = cli.run.collator_options(); + + runner.run_node_until_exit(|config| async move { + let para_id = + chain_spec::Extensions::try_get(&*config.chain_spec).map(|e| e.para_id); + + let polkadot_cli = RelayChainCli::new( + &config, + [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args.iter()), + ); + + let id = ParaId::from(cli.parachain_id.or(para_id).expect("Missing ParaId")); + + let parachain_account = + AccountIdConversion::::into_account_truncating(&id); + + let state_version = + RelayChainCli::native_runtime_version(&config.chain_spec).state_version(); + let block: Block = generate_genesis_block(&*config.chain_spec, state_version) + .map_err(|e| format!("{e:?}"))?; + let genesis_state = format!("0x{:?}", HexDisplay::from(&block.header().encode())); + + let polkadot_config = SubstrateCli::create_configuration( + &polkadot_cli, + &polkadot_cli, + config.tokio_handle.clone(), + ) + .map_err(|err| format!("Relay chain argument error: {err}"))?; + + info!("Parachain id: {:?}", id); + info!("Parachain Account: {}", parachain_account); + info!("Parachain genesis state: {}", genesis_state); + info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); + + crate::service::start_node(config, polkadot_config, collator_options, id) + .await + .map(|r| r.0) + .map_err(Into::into) + }) + }, + } +} + +impl DefaultConfigurationValues for RelayChainCli { + fn p2p_listen_port() -> u16 { + 30334 + } + + fn rpc_ws_listen_port() -> u16 { + 9945 + } + + fn rpc_http_listen_port() -> u16 { + 9934 + } + + fn prometheus_listen_port() -> u16 { + 9616 + } +} + +impl CliConfiguration for RelayChainCli { + fn shared_params(&self) -> &SharedParams { + self.base.base.shared_params() + } + + fn import_params(&self) -> Option<&ImportParams> { + self.base.base.import_params() + } + + fn network_params(&self) -> Option<&NetworkParams> { + self.base.base.network_params() + } + + fn keystore_params(&self) -> Option<&KeystoreParams> { + self.base.base.keystore_params() + } + + fn base_path(&self) -> Result> { + Ok(self + .shared_params() + .base_path()? + .or_else(|| self.base_path.clone().map(Into::into))) + } + + fn rpc_http(&self, default_listen_port: u16) -> Result> { + self.base.base.rpc_http(default_listen_port) + } + + fn rpc_ipc(&self) -> Result> { + self.base.base.rpc_ipc() + } + + fn rpc_ws(&self, default_listen_port: u16) -> Result> { + self.base.base.rpc_ws(default_listen_port) + } + + fn prometheus_config( + &self, + default_listen_port: u16, + chain_spec: &Box, + ) -> Result> { + self.base.base.prometheus_config(default_listen_port, chain_spec) + } + + fn init( + &self, + _support_url: &String, + _impl_version: &String, + _logger_hook: F, + _config: &sc_service::Configuration, + ) -> Result<()> + where + F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration), + { + unreachable!("PolkadotCli is never initialized; qed"); + } + + fn chain_id(&self, is_dev: bool) -> Result { + let chain_id = self.base.base.chain_id(is_dev)?; + + Ok(if chain_id.is_empty() { self.chain_id.clone().unwrap_or_default() } else { chain_id }) + } + + fn role(&self, is_dev: bool) -> Result { + self.base.base.role(is_dev) + } + + fn transaction_pool(&self, is_dev: bool) -> Result { + self.base.base.transaction_pool(is_dev) + } + + fn rpc_methods(&self) -> Result { + self.base.base.rpc_methods() + } + + fn rpc_ws_max_connections(&self) -> Result> { + self.base.base.rpc_ws_max_connections() + } + + fn rpc_cors(&self, is_dev: bool) -> Result>> { + self.base.base.rpc_cors(is_dev) + } + + fn default_heap_pages(&self) -> Result> { + self.base.base.default_heap_pages() + } + + fn force_authoring(&self) -> Result { + self.base.base.force_authoring() + } + + fn disable_grandpa(&self) -> Result { + self.base.base.disable_grandpa() + } + + fn max_runtime_instances(&self) -> Result> { + self.base.base.max_runtime_instances() + } + + fn announce_block(&self) -> Result { + self.base.base.announce_block() + } + + fn telemetry_endpoints( + &self, + chain_spec: &Box, + ) -> Result> { + self.base.base.telemetry_endpoints(chain_spec) + } +} diff --git a/bin/rialto-parachain/node/src/lib.rs b/bin/rialto-parachain/node/src/lib.rs new file mode 100644 index 00000000000..3ec291596b7 --- /dev/null +++ b/bin/rialto-parachain/node/src/lib.rs @@ -0,0 +1,18 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +pub mod chain_spec; +pub mod service; diff --git a/bin/rialto-parachain/node/src/main.rs b/bin/rialto-parachain/node/src/main.rs new file mode 100644 index 00000000000..2b4e0b438d1 --- /dev/null +++ b/bin/rialto-parachain/node/src/main.rs @@ -0,0 +1,29 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate Parachain Node Template CLI + +#![warn(missing_docs)] + +mod chain_spec; +#[macro_use] +mod service; +mod cli; +mod command; + +fn main() -> sc_cli::Result<()> { + command::run() +} diff --git a/bin/rialto-parachain/node/src/service.rs b/bin/rialto-parachain/node/src/service.rs new file mode 100644 index 00000000000..7dc25f6550d --- /dev/null +++ b/bin/rialto-parachain/node/src/service.rs @@ -0,0 +1,540 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rialto parachain node service. +//! +//! The code is mostly copy of `polkadot-parachains/src/service.rs` file from Cumulus +//! repository with some parts removed. We have added two RPC extensions to the original +//! service: `pallet_transaction_payment_rpc::TransactionPaymentApi` and +//! `substrate_frame_rpc_system::SystemApi`. + +// std +use std::{sync::Arc, time::Duration}; + +// Local Runtime Types +use rialto_parachain_runtime::RuntimeApi; + +// Cumulus Imports +use cumulus_client_cli::CollatorOptions; +use cumulus_client_consensus_aura::{AuraConsensus, BuildAuraConsensusParams, SlotProportion}; +use cumulus_client_consensus_common::{ParachainBlockImport, ParachainConsensus}; +use cumulus_client_network::BlockAnnounceValidator; +use cumulus_client_service::{ + prepare_node_config, start_collator, start_full_node, StartCollatorParams, StartFullNodeParams, +}; +use cumulus_primitives_core::ParaId; +use cumulus_relay_chain_inprocess_interface::build_inprocess_relay_chain; +use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface, RelayChainResult}; +use cumulus_relay_chain_minimal_node::build_minimal_relay_chain_node; +use polkadot_service::CollatorPair; + +// Substrate Imports +use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; +use sc_network::{NetworkBlock, NetworkService}; +use sc_service::{Configuration, PartialComponents, TFullBackend, TFullClient, TaskManager}; +use sc_telemetry::{Telemetry, TelemetryHandle, TelemetryWorker, TelemetryWorkerHandle}; +use sp_api::ConstructRuntimeApi; +use sp_keystore::SyncCryptoStorePtr; +use sp_runtime::traits::BlakeTwo256; +use substrate_prometheus_endpoint::Registry; + +// Runtime type overrides +type BlockNumber = u32; +type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +type Hash = sp_core::H256; + +pub type ParachainRuntimeExecutor = ExecutorDispatch; + +// Our native executor instance. +pub struct ExecutorDispatch; + +impl NativeExecutionDispatch for ExecutorDispatch { + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + + fn dispatch(method: &str, data: &[u8]) -> Option> { + rialto_parachain_runtime::api::dispatch(method, data) + } + + fn native_version() -> sc_executor::NativeVersion { + rialto_parachain_runtime::native_version() + } +} + +/// Starts a `ServiceBuilder` for a full service. +/// +/// Use this macro if you don't actually need the full service, but just the builder in order to +/// be able to perform chain operations. +#[allow(clippy::type_complexity)] +pub fn new_partial( + config: &Configuration, + build_import_queue: BIQ, +) -> Result< + PartialComponents< + TFullClient>, + TFullBackend, + (), + sc_consensus::DefaultImportQueue< + Block, + TFullClient>, + >, + sc_transaction_pool::FullPool< + Block, + TFullClient>, + >, + (Option, Option), + >, + sc_service::Error, +> +where + RuntimeApi: ConstructRuntimeApi>> + + Send + + Sync + + 'static, + RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue + + sp_api::Metadata + + sp_session::SessionKeys + + sp_api::ApiExt< + Block, + StateBackend = sc_client_api::StateBackendFor, Block>, + > + sp_offchain::OffchainWorkerApi + + sp_block_builder::BlockBuilder, + sc_client_api::StateBackendFor, Block>: sp_api::StateBackend, + Executor: NativeExecutionDispatch + 'static, + BIQ: FnOnce( + Arc>>, + &Configuration, + Option, + &TaskManager, + ) -> Result< + sc_consensus::DefaultImportQueue< + Block, + TFullClient>, + >, + sc_service::Error, + >, +{ + let telemetry = config + .telemetry_endpoints + .clone() + .filter(|x| !x.is_empty()) + .map(|endpoints| -> Result<_, sc_telemetry::Error> { + let worker = TelemetryWorker::new(16)?; + let telemetry = worker.handle().new_telemetry(endpoints); + Ok((worker, telemetry)) + }) + .transpose()?; + + let executor = sc_executor::NativeElseWasmExecutor::::new( + config.wasm_method, + config.default_heap_pages, + config.max_runtime_instances, + config.runtime_cache_size, + ); + + let (client, backend, keystore_container, task_manager) = + sc_service::new_full_parts::( + config, + telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), + executor, + )?; + let client = Arc::new(client); + + let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle()); + + let telemetry = telemetry.map(|(worker, telemetry)| { + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); + telemetry + }); + + let transaction_pool = sc_transaction_pool::BasicPool::new_full( + config.transaction_pool.clone(), + config.role.is_authority().into(), + config.prometheus_registry(), + task_manager.spawn_essential_handle(), + client.clone(), + ); + + let import_queue = build_import_queue( + client.clone(), + config, + telemetry.as_ref().map(|telemetry| telemetry.handle()), + &task_manager, + )?; + + let params = PartialComponents { + backend, + client, + import_queue, + keystore_container, + task_manager, + transaction_pool, + select_chain: (), + other: (telemetry, telemetry_worker_handle), + }; + + Ok(params) +} + +async fn build_relay_chain_interface( + polkadot_config: Configuration, + parachain_config: &Configuration, + telemetry_worker_handle: Option, + task_manager: &mut TaskManager, + collator_options: CollatorOptions, + hwbench: Option, +) -> RelayChainResult<(Arc<(dyn RelayChainInterface + 'static)>, Option)> { + match collator_options.relay_chain_rpc_url { + Some(relay_chain_url) => + build_minimal_relay_chain_node(polkadot_config, task_manager, relay_chain_url).await, + None => build_inprocess_relay_chain( + polkadot_config, + parachain_config, + telemetry_worker_handle, + task_manager, + hwbench, + ), + } +} + +/// Start a node with the given parachain `Configuration` and relay chain `Configuration`. +/// +/// This is the actual implementation that is abstract over the executor and the runtime api. +#[sc_tracing::logging::prefix_logs_with("Parachain")] +async fn start_node_impl( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + id: ParaId, + rpc_ext_builder: RB, + build_import_queue: BIQ, + build_consensus: BIC, +) -> sc_service::error::Result<( + TaskManager, + Arc>>, +)> +where + RuntimeApi: ConstructRuntimeApi>> + + Send + + Sync + + 'static, + RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue + + sp_api::Metadata + + sp_session::SessionKeys + + sp_api::ApiExt< + Block, + StateBackend = sc_client_api::StateBackendFor, Block>, + > + sp_offchain::OffchainWorkerApi + + sp_block_builder::BlockBuilder + + cumulus_primitives_core::CollectCollationInfo, + sc_client_api::StateBackendFor, Block>: sp_api::StateBackend, + Executor: NativeExecutionDispatch + 'static, + RB: Fn( + sc_rpc_api::DenyUnsafe, + Arc>>, + Arc< + sc_transaction_pool::FullPool< + Block, + TFullClient>, + >, + >, + ) -> Result, sc_service::Error> + + Send + + Clone + + 'static, + BIQ: FnOnce( + Arc>>, + &Configuration, + Option, + &TaskManager, + ) -> Result< + sc_consensus::DefaultImportQueue< + Block, + TFullClient>, + >, + sc_service::Error, + >, + BIC: FnOnce( + Arc>>, + Option<&Registry>, + Option, + &TaskManager, + Arc, + Arc< + sc_transaction_pool::FullPool< + Block, + TFullClient>, + >, + >, + Arc>, + SyncCryptoStorePtr, + bool, + ) -> Result>, sc_service::Error>, +{ + let parachain_config = prepare_node_config(parachain_config); + + let params = new_partial::(¶chain_config, build_import_queue)?; + let (mut telemetry, telemetry_worker_handle) = params.other; + + let mut task_manager = params.task_manager; + let (relay_chain_interface, collator_key) = build_relay_chain_interface( + polkadot_config, + ¶chain_config, + telemetry_worker_handle, + &mut task_manager, + collator_options, + None, + ) + .await + .map_err(|e| match e { + RelayChainError::ServiceError(polkadot_service::Error::Sub(x)) => x, + s => s.to_string().into(), + })?; + + let client = params.client.clone(); + let backend = params.backend.clone(); + let block_announce_validator = BlockAnnounceValidator::new(relay_chain_interface.clone(), id); + + let force_authoring = parachain_config.force_authoring; + let validator = parachain_config.role.is_authority(); + let prometheus_registry = parachain_config.prometheus_registry().cloned(); + let transaction_pool = params.transaction_pool.clone(); + let import_queue = cumulus_client_service::SharedImportQueue::new(params.import_queue); + let (network, system_rpc_tx, tx_handler_controller, start_network) = + sc_service::build_network(sc_service::BuildNetworkParams { + config: ¶chain_config, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + spawn_handle: task_manager.spawn_handle(), + import_queue: import_queue.clone(), + block_announce_validator_builder: Some(Box::new(|_| { + Box::new(block_announce_validator) + })), + warp_sync: None, + })?; + + let rpc_client = client.clone(); + let rpc_transaction_pool = transaction_pool.clone(); + let rpc_extensions_builder = Box::new(move |deny_unsafe, _| { + rpc_ext_builder(deny_unsafe, rpc_client.clone(), rpc_transaction_pool.clone()) + }); + + sc_service::spawn_tasks(sc_service::SpawnTasksParams { + rpc_builder: rpc_extensions_builder.clone(), + client: client.clone(), + transaction_pool: transaction_pool.clone(), + task_manager: &mut task_manager, + config: parachain_config, + keystore: params.keystore_container.sync_keystore(), + backend: backend.clone(), + network: network.clone(), + system_rpc_tx, + tx_handler_controller, + telemetry: telemetry.as_mut(), + })?; + + let announce_block = { + let network = network.clone(); + Arc::new(move |hash, data| network.announce_block(hash, data)) + }; + + let relay_chain_slot_duration = Duration::from_secs(6); + + if validator { + let parachain_consensus = build_consensus( + client.clone(), + prometheus_registry.as_ref(), + telemetry.as_ref().map(|t| t.handle()), + &task_manager, + relay_chain_interface.clone(), + transaction_pool, + network, + params.keystore_container.sync_keystore(), + force_authoring, + )?; + + let spawner = task_manager.spawn_handle(); + + let params = StartCollatorParams { + para_id: id, + block_status: client.clone(), + announce_block, + client: client.clone(), + task_manager: &mut task_manager, + relay_chain_interface, + spawner, + parachain_consensus, + import_queue, + collator_key: collator_key.expect("Command line arguments do not allow this. qed"), + relay_chain_slot_duration, + }; + + start_collator(params).await?; + } else { + let params = StartFullNodeParams { + client: client.clone(), + announce_block, + task_manager: &mut task_manager, + para_id: id, + relay_chain_interface, + relay_chain_slot_duration, + import_queue, + }; + + start_full_node(params)?; + } + + start_network.start_network(); + + Ok((task_manager, client)) +} + +/// Build the import queue for the the parachain runtime. +#[allow(clippy::type_complexity)] +pub fn parachain_build_import_queue( + client: Arc>>, + config: &Configuration, + telemetry: Option, + task_manager: &TaskManager, +) -> Result< + sc_consensus::DefaultImportQueue< + Block, + TFullClient>, + >, + sc_service::Error, +> { + let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; + + cumulus_client_consensus_aura::import_queue::< + sp_consensus_aura::sr25519::AuthorityPair, + _, + _, + _, + _, + _, + >(cumulus_client_consensus_aura::ImportQueueParams { + block_import: ParachainBlockImport::new(client.clone()), + client, + create_inherent_data_providers: move |_, _| async move { + let time = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *time, + slot_duration, + ); + + Ok((slot, time)) + }, + registry: config.prometheus_registry(), + spawner: &task_manager.spawn_essential_handle(), + telemetry, + }) + .map_err(Into::into) +} + +/// Start a normal parachain node. +pub async fn start_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + id: ParaId, +) -> sc_service::error::Result<( + TaskManager, + Arc>>, +)> { + start_node_impl::( + parachain_config, + polkadot_config, + collator_options, + id, + |_deny_unsafe, client, pool| { + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use sc_rpc::DenyUnsafe; + use substrate_frame_rpc_system::{System, SystemApiServer}; + + let mut io = jsonrpsee::RpcModule::new(()); + let map_err = |e| sc_service::Error::Other(format!("{e}")); + io.merge(System::new(client.clone(), pool, DenyUnsafe::No).into_rpc()) + .map_err(map_err)?; + io.merge(TransactionPayment::new(client).into_rpc()).map_err(map_err)?; + Ok(io) + }, + parachain_build_import_queue, + |client, + prometheus_registry, + telemetry, + task_manager, + relay_chain_interface, + transaction_pool, + sync_oracle, + keystore, + force_authoring| { + let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; + + let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( + task_manager.spawn_handle(), + client.clone(), + transaction_pool, + prometheus_registry, + telemetry.clone(), + ); + + Ok(AuraConsensus::build::( + BuildAuraConsensusParams { + proposer_factory, + create_inherent_data_providers: move |_, (relay_parent, validation_data)| { + let relay_chain_interface = relay_chain_interface.clone(); + async move { + let parachain_inherent = + cumulus_primitives_parachain_inherent::ParachainInherentData::create_at( + relay_parent, + &relay_chain_interface, + &validation_data, + id, + ).await; + let time = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *time, + slot_duration, + ); + + let parachain_inherent = parachain_inherent.ok_or_else(|| { + Box::::from( + "Failed to create parachain inherent", + ) + })?; + Ok((slot, time, parachain_inherent)) + } + }, + block_import: ParachainBlockImport::new(client.clone()), + para_client: client, + backoff_authoring_blocks: Option::<()>::None, + sync_oracle, + keystore, + force_authoring, + slot_duration, + // We got around 500ms for proposing + block_proposal_slot_portion: SlotProportion::new(1f32 / 24f32), + telemetry, + max_block_proposal_slot_portion: None, + }, + )) + }, + ) + .await +} diff --git a/bin/rialto-parachain/runtime/Cargo.toml b/bin/rialto-parachain/runtime/Cargo.toml new file mode 100644 index 00000000000..467fbd3699a --- /dev/null +++ b/bin/rialto-parachain/runtime/Cargo.toml @@ -0,0 +1,136 @@ +[package] +name = "rialto-parachain-runtime" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +repository = "https://github.com/paritytech/parity-bridges-common/" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[build-dependencies] +substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[dependencies] +codec = { package = 'parity-scale-codec', version = '3.1.5', default-features = false, features = ['derive']} +hex-literal = "0.3" +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Bridge depedencies + +bp-messages = { path = "../../../primitives/messages", default-features = false } +bp-millau = { path = "../../../primitives/chain-millau", default-features = false } +bp-relayers = { path = "../../../primitives/relayers", default-features = false } +bp-runtime = { path = "../../../primitives/runtime", default-features = false } +bp-rialto-parachain = { path = "../../../primitives/chain-rialto-parachain", default-features = false } +bridge-runtime-common = { path = "../../runtime-common", default-features = false } +pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false } +pallet-bridge-messages = { path = "../../../modules/messages", default-features = false } +pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false } + +# Substrate Dependencies +## Substrate Primitive Dependencies +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +## Substrate FRAME Dependencies +frame-executive = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +## Substrate Pallet Dependencies +pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Cumulus Dependencies +cumulus-pallet-aura-ext = { git = "https://github.com/paritytech/cumulus", branch = "master", default-features = false } +cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "master", default-features = false } +cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "master", default-features = false } +cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "master", default-features = false } +cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "master", default-features = false } +cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "master", default-features = false } +cumulus-primitives-timestamp = { git = "https://github.com/paritytech/cumulus", branch = "master", default-features = false } +parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "master", default-features = false } + +# Polkadot Dependencies +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } + +[features] +default = ['std'] +runtime-benchmarks = [ + 'sp-runtime/runtime-benchmarks', + 'frame-benchmarking', + 'frame-support/runtime-benchmarks', + 'frame-system-benchmarking', + 'frame-system/runtime-benchmarks', + 'pallet-balances/runtime-benchmarks', + 'pallet-timestamp/runtime-benchmarks', +] +std = [ + "bp-messages/std", + "bp-millau/std", + "bp-relayers/std", + "bp-runtime/std", + "bp-rialto-parachain/std", + "bridge-runtime-common/std", + "codec/std", + "log/std", + "scale-info/std", + "sp-api/std", + "sp-std/std", + "sp-io/std", + "sp-core/std", + "sp-runtime/std", + "sp-version/std", + "sp-offchain/std", + "sp-session/std", + "sp-block-builder/std", + "sp-transaction-pool/std", + "sp-inherents/std", + "frame-support/std", + "frame-executive/std", + "frame-system/std", + "pallet-balances/std", + "pallet-bridge-grandpa/std", + "pallet-bridge-messages/std", + "pallet-bridge-relayers/std", + "pallet-randomness-collective-flip/std", + "pallet-timestamp/std", + "pallet-sudo/std", + "pallet-transaction-payment/std", + "pallet-xcm/std", + "parachain-info/std", + "polkadot-parachain/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-pallet-xcm/std", + "cumulus-primitives-core/std", + "cumulus-primitives-timestamp/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", + "pallet-aura/std", + "sp-consensus-aura/std", +] diff --git a/bin/rialto-parachain/runtime/build.rs b/bin/rialto-parachain/runtime/build.rs new file mode 100644 index 00000000000..65095bd1b7e --- /dev/null +++ b/bin/rialto-parachain/runtime/build.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use substrate_wasm_builder::WasmBuilder; + +fn main() { + WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build() +} diff --git a/bin/rialto-parachain/runtime/src/lib.rs b/bin/rialto-parachain/runtime/src/lib.rs new file mode 100644 index 00000000000..e80ecdf188d --- /dev/null +++ b/bin/rialto-parachain/runtime/src/lib.rs @@ -0,0 +1,904 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! The Rialto parachain runtime. This can be compiled with `#[no_std]`, ready for Wasm. +//! +//! Originally a copy of runtime from https://github.com/substrate-developer-hub/substrate-parachain-template. + +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +use crate::millau_messages::{WithMillauMessageBridge, XCM_LANE}; + +use bridge_runtime_common::messages::source::{XcmBridge, XcmBridgeAdapter}; +use cumulus_pallet_parachain_system::AnyRelayNumber; +use sp_api::impl_runtime_apis; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::{AccountIdLookup, Block as BlockT}, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, +}; + +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +// A few exports that help ease life for downstream crates. +use bp_runtime::{HeaderId, HeaderIdProvider}; +pub use frame_support::{ + construct_runtime, + dispatch::DispatchClass, + match_types, parameter_types, + traits::{Everything, IsInVec, Nothing, Randomness}, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + IdentityFee, Weight, + }, + StorageValue, +}; +pub use frame_system::{Call as SystemCall, EnsureRoot}; +pub use pallet_balances::Call as BalancesCall; +pub use pallet_sudo::Call as SudoCall; +pub use pallet_timestamp::Call as TimestampCall; +pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +pub use sp_runtime::{MultiAddress, Perbill, Permill}; + +pub use bp_rialto_parachain::{ + AccountId, Balance, BlockLength, BlockNumber, BlockWeights, Hash, Hasher as Hashing, Header, + Index, Signature, MAXIMUM_BLOCK_WEIGHT, +}; + +pub use pallet_bridge_grandpa::Call as BridgeGrandpaCall; +pub use pallet_bridge_messages::Call as MessagesCall; +pub use pallet_xcm::Call as XcmCall; + +// Polkadot & XCM imports +use bridge_runtime_common::CustomNetworkId; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::Sibling; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, + EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + UsingComponents, +}; +use xcm_executor::{Config, XcmExecutor}; + +pub mod millau_messages; + +/// The address format for describing accounts. +pub type Address = MultiAddress; +/// Block type as expected by this runtime. +pub type Block = generic::Block; +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = generic::CheckedExtrinsic; +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, +>; + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + } +} + +/// This runtime version. +#[sp_version::runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("template-parachain"), + impl_name: create_runtime_str!("template-parachain"), + authoring_version: 1, + spec_version: 1, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 0, +}; + +/// This determines the average expected block time that we are targeting. +/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. +/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked +/// up by `pallet_aura` to implement `fn slot_duration()`. +/// +/// Change this to adjust the block time. +pub const MILLISECS_PER_BLOCK: u64 = 12000; + +pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + +pub const EPOCH_DURATION_IN_BLOCKS: u32 = 10 * MINUTES; + +// Time is measured by number of blocks. +pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; + +// Unit = the base number of indivisible units for balances +pub const UNIT: Balance = 1_000_000_000_000; +pub const MILLIUNIT: Balance = 1_000_000_000; +pub const MICROUNIT: Balance = 1_000_000; + +// 1 in 4 blocks (on average, not counting collisions) will be primary babe blocks. +pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const BlockHashCount: BlockNumber = 250; + pub const Version: RuntimeVersion = VERSION; + pub const SS58Prefix: u8 = 48; +} + +// Configure FRAME pallets to include in runtime. + +impl frame_system::Config for Runtime { + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type RuntimeCall = RuntimeCall; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = Hashing; + /// The header type. + type Header = generic::Header; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + /// The ubiquitous origin type. + type RuntimeOrigin = RuntimeOrigin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// Runtime version. + type Version = Version; + /// Converts a module to an index of this module in the runtime. + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The weight of database operations that the runtime can invoke. + type DbWeight = (); + /// The basic call filter to use in dispatchable. + type BaseCallFilter = Everything; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// Block & extrinsics weights: base values and limits. + type BlockWeights = BlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = BlockLength; + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + /// The action to take on a Runtime Upgrade + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const MinimumPeriod: u64 = SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the Unix epoch. + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: u128 = MILLIUNIT; + pub const TransferFee: u128 = MILLIUNIT; + pub const CreationFee: u128 = MILLIUNIT; + pub const TransactionByteFee: u128 = MICROUNIT; + pub const OperationalFeeMultiplier: u8 = 5; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = pallet_balances::weights::SubstrateWeight; + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +impl pallet_transaction_payment::Config for Runtime { + type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; + type OperationalFeeMultiplier = OperationalFeeMultiplier; + type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; + type FeeMultiplierUpdate = (); + type RuntimeEvent = RuntimeEvent; +} + +impl pallet_sudo::Config for Runtime { + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type OutboundXcmpMessageSource = XcmpQueue; + type DmpMessageHandler = DmpQueue; + type ReservedDmpWeight = ReservedDmpWeight; + type XcmpMessageHandler = XcmpQueue; + type ReservedXcmpWeight = ReservedXcmpWeight; + type CheckAssociatedRelayNumber = AnyRelayNumber; +} + +impl parachain_info::Config for Runtime {} + +impl cumulus_pallet_aura_ext::Config for Runtime {} + +impl pallet_randomness_collective_flip::Config for Runtime {} + +parameter_types! { + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: NetworkId = CustomNetworkId::Rialto.as_network_id(); + pub RelayOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + /// The Millau network ID. + pub const MillauNetwork: NetworkId = CustomNetworkId::Millau.as_network_id(); + /// The RialtoParachain network ID. + pub const ThisNetwork: NetworkId = CustomNetworkId::RialtoParachain.as_network_id(); +} + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the default `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, +); + +/// Means for transacting assets on this chain. +pub type LocalAssetTransactor = CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports. + (), +>; + +/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with XCM `Transact`. There is an `OriginKind` which can +/// biases the kind of local `Origin` it will become. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when + // recognised. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognised. + SiblingParachainAsNative, + // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a + // transaction from the Root origin. + ParentAsSuperuser, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `Origin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. + XcmPassthrough, +); + +// TODO: until https://github.com/paritytech/parity-bridges-common/issues/1417 is fixed (in either way), +// the following constant must match the similar constant in the Millau runtime. + +/// One XCM operation is `1_000_000_000` weight - almost certainly a conservative estimate. +pub const BASE_XCM_WEIGHT: u64 = 1_000_000_000; + +parameter_types! { + pub UnitWeightCost: u64 = BASE_XCM_WEIGHT; + // One UNIT buys 1 second of weight. + pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), UNIT); + pub const MaxInstructions: u32 = 100; + pub const MaxAuthorities: u32 = 100_000; + pub MaxAssetsIntoHolding: u32 = 64; +} + +match_types! { + pub type ParentOrParentsUnitPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) } + }; +} + +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + AllowUnpaidExecutionFrom, + // ^^^ Parent & its unit plurality gets free execution +); + +/// XCM weigher type. +pub type XcmWeigher = FixedWeightBounds; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + type IsReserve = NativeAsset; + type IsTeleporter = NativeAsset; // <- should be enough to allow teleportation of UNIT + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = XcmWeigher; + type Trader = UsingComponents, RelayLocation, AccountId, Balances, ()>; + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; +} + +/// No local origins on this chain are allowed to dispatch XCM sends/executions. +pub type LocalOriginToLocation = SignedToAccountId32; + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = ( + // Bridge is used to communicate with other relay chain (Millau). + XcmBridgeAdapter, +); + +/// With-Millau bridge. +pub struct ToMillauBridge; + +impl XcmBridge for ToMillauBridge { + type MessageBridge = WithMillauMessageBridge; + type MessageSender = pallet_bridge_messages::Pallet; + + fn universal_location() -> InteriorMultiLocation { + UniversalLocation::get() + } + + fn verify_destination(dest: &MultiLocation) -> bool { + matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == MillauNetwork::get()) + } + + fn build_destination() -> MultiLocation { + let dest: InteriorMultiLocation = MillauNetwork::get().into(); + let here = UniversalLocation::get(); + dest.relative_to(&here) + } + + fn xcm_lane() -> bp_messages::LaneId { + XCM_LANE + } +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = XcmWeigher; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = (); + type MaxLockers = frame_support::traits::ConstU32<8>; + type UniversalLocation = UniversalLocation; +} + +impl cumulus_pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; + type ChannelInfo = ParachainSystem; + type VersionWrapper = (); + type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; + type WeightInfo = (); + type PriceForSiblingDelivery = (); +} + +impl cumulus_pallet_dmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; + type ExecuteOverweightOrigin = frame_system::EnsureRoot; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = MaxAuthorities; +} + +impl pallet_bridge_relayers::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reward = Balance; + type PaymentProcedure = bp_relayers::MintReward, AccountId>; + type WeightInfo = (); +} + +parameter_types! { + /// This is a pretty unscientific cap. + /// + /// Note that once this is hit the pallet will essentially throttle incoming requests down to one + /// call per block. + pub const MaxRequests: u32 = 50; + + /// Number of headers to keep. + /// + /// Assuming the worst case of every header being finalized, we will keep headers at least for a + /// week. + pub const HeadersToKeep: u32 = 7 * bp_millau::DAYS as u32; + + /// Maximal number of authorities at Millau. + pub const MaxAuthoritiesAtMillau: u32 = bp_millau::MAX_AUTHORITIES_COUNT; + /// Maximal size of SCALE-encoded Millau header. + pub const MaxMillauHeaderSize: u32 = bp_millau::MAX_HEADER_SIZE; +} + +pub type MillauGrandpaInstance = (); +impl pallet_bridge_grandpa::Config for Runtime { + type BridgedChain = bp_millau::Millau; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type MaxBridgedAuthorities = MaxAuthoritiesAtMillau; + type MaxBridgedHeaderSize = MaxMillauHeaderSize; + type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; +} + +parameter_types! { + pub const MaxMessagesToPruneAtOnce: bp_messages::MessageNonce = 8; + pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_millau::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_millau::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + pub const RootAccountForPayments: Option = None; + pub const BridgedChainId: bp_runtime::ChainId = bp_runtime::MILLAU_CHAIN_ID; + pub ActiveOutboundLanes: &'static [bp_messages::LaneId] = &[millau_messages::XCM_LANE]; +} + +/// Instance of the messages pallet used to relay messages to/from Millau chain. +pub type WithMillauMessagesInstance = (); + +impl pallet_bridge_messages::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; + type ActiveOutboundLanes = ActiveOutboundLanes; + type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = crate::millau_messages::ToMillauMaximalOutboundPayloadSize; + type OutboundPayload = crate::millau_messages::ToMillauMessagePayload; + + type InboundPayload = crate::millau_messages::FromMillauMessagePayload; + type InboundRelayer = bp_millau::AccountId; + + type TargetHeaderChain = crate::millau_messages::Millau; + type LaneMessageVerifier = crate::millau_messages::ToMillauMessageVerifier; + type MessageDeliveryAndDispatchPayment = + pallet_bridge_relayers::MessageDeliveryAndDispatchPaymentAdapter< + Runtime, + WithMillauMessagesInstance, + >; + + type SourceHeaderChain = crate::millau_messages::Millau; + type MessageDispatch = crate::millau_messages::FromMillauMessageDispatch; + type BridgedChainId = BridgedChainId; +} + +// Create the runtime by composing the FRAME pallets that were previously configured. +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = generic::Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event}, + RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + + ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Storage, Inherent, Event} = 20, + ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21, + + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 30, + + Aura: pallet_aura::{Pallet, Config}, + AuraExt: cumulus_pallet_aura_ext::{Pallet, Config}, + + // XCM helpers. + XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 50, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin} = 51, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event, Origin} = 52, + DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 53, + + // Millau bridge modules. + BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event}, + BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage}, + BridgeMillauMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event, Config}, + } +); + +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic( + extrinsic: ::Extrinsic, + ) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_session::SessionKeys for Runtime { + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + } + + fn authorities() -> Vec { + Aura::authorities().to_vec() + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Index { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + } + + impl bp_millau::MillauFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeMillauGrandpa::best_finalized().map(|header| header.id()) + } + } + + impl bp_millau::ToMillauOutboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + begin: bp_messages::MessageNonce, + end: bp_messages::MessageNonce, + ) -> Vec { + bridge_runtime_common::messages_api::outbound_message_details::< + Runtime, + WithMillauMessagesInstance, + >(lane, begin, end) + } + } + + impl bp_millau::FromMillauInboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, + ) -> Vec { + bridge_runtime_common::messages_api::inbound_message_details::< + Runtime, + WithMillauMessagesInstance, + >(lane, messages) + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(_extra: bool) -> ( + Vec, + Vec, + ) { + todo!("TODO: fix or remove") + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey}; + + use frame_system_benchmarking::Pallet as SystemBench; + impl frame_system_benchmarking::Config for Runtime {} + + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + + add_benchmark!(params, batches, frame_system, SystemBench::); + add_benchmark!(params, batches, pallet_balances, Balances); + add_benchmark!(params, batches, pallet_timestamp, Timestamp); + + Ok(batches) + } + } +} + +struct CheckInherents; + +impl cumulus_pallet_parachain_system::CheckInherents for CheckInherents { + fn check_inherents( + block: &Block, + relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof, + ) -> sp_inherents::CheckInherentsResult { + let relay_chain_slot = relay_state_proof + .read_slot() + .expect("Could not read the relay chain slot from the proof"); + + let inherent_data = + cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration( + relay_chain_slot, + sp_std::time::Duration::from_secs(6), + ) + .create_inherent_data() + .expect("Could not create the timestamp inherent data"); + + inherent_data.check_extrinsics(block) + } +} + +cumulus_pallet_parachain_system::register_validate_block!( + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, + CheckInherents = CheckInherents, +); + +#[cfg(test)] +mod tests { + use super::*; + use crate::millau_messages::WeightCredit; + use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, + MessageKey, + }; + use bp_runtime::messages::MessageDispatchResult; + use bridge_runtime_common::messages::target::FromBridgedChainMessageDispatch; + use codec::Encode; + + fn new_test_ext() -> sp_io::TestExternalities { + sp_io::TestExternalities::new( + frame_system::GenesisConfig::default().build_storage::().unwrap(), + ) + } + + #[test] + fn xcm_messages_to_millau_are_sent() { + new_test_ext().execute_with(|| { + // the encoded message (origin ++ xcm) is 0x010109020419A8 + let dest = (Parent, X1(GlobalConsensus(MillauNetwork::get()))); + let xcm: Xcm<()> = vec![Instruction::Trap(42)].into(); + + let send_result = send_xcm::(dest.into(), xcm); + let expected_fee = MultiAssets::from((Here, Fungibility::Fungible(1_000_000_u128))); + let expected_hash = + ([0u8, 0u8, 0u8, 0u8], 1u64).using_encoded(sp_io::hashing::blake2_256); + assert_eq!(send_result, Ok((expected_hash, expected_fee)),); + }) + } + + #[test] + fn xcm_messages_from_millau_are_dispatched() { + type XcmExecutor = xcm_executor::XcmExecutor; + type MessageDispatcher = FromBridgedChainMessageDispatch< + WithMillauMessageBridge, + XcmExecutor, + XcmWeigher, + WeightCredit, + >; + + new_test_ext().execute_with(|| { + let location: MultiLocation = + (Parent, X1(GlobalConsensus(MillauNetwork::get()))).into(); + let xcm: Xcm = vec![Instruction::Trap(42)].into(); + + let mut incoming_message = DispatchMessage { + key: MessageKey { lane_id: [0, 0, 0, 0], nonce: 1 }, + data: DispatchMessageData { payload: Ok((location, xcm).into()) }, + }; + + let dispatch_weight = MessageDispatcher::dispatch_weight(&mut incoming_message); + assert_eq!( + dispatch_weight, + frame_support::weights::Weight::from_ref_time(1_000_000_000) + ); + + let dispatch_result = + MessageDispatcher::dispatch(&AccountId::from([0u8; 32]), incoming_message); + assert_eq!( + dispatch_result, + MessageDispatchResult { + unspent_weight: frame_support::weights::Weight::from_ref_time(0), + dispatch_fee_paid_during_dispatch: false, + } + ); + }) + } +} diff --git a/bin/rialto-parachain/runtime/src/millau_messages.rs b/bin/rialto-parachain/runtime/src/millau_messages.rs new file mode 100644 index 00000000000..25d37f15988 --- /dev/null +++ b/bin/rialto-parachain/runtime/src/millau_messages.rs @@ -0,0 +1,163 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Everything required to serve Millau <-> RialtoParachain messages. + +// TODO: this is almost exact copy of `millau_messages.rs` from Rialto runtime. +// Should be extracted to a separate crate and reused here. + +use crate::{MillauGrandpaInstance, Runtime, RuntimeCall, RuntimeOrigin}; + +use bp_messages::{ + source_chain::TargetHeaderChain, + target_chain::{ProvedMessages, SourceHeaderChain}, + InboundLaneData, LaneId, Message, MessageNonce, +}; +use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_PARACHAIN_CHAIN_ID}; +use bridge_runtime_common::messages::{self, MessageBridge}; +use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; + +/// Default lane that is used to send messages to Millau. +pub const XCM_LANE: LaneId = [0, 0, 0, 0]; +/// Weight of 2 XCM instructions is for simple `Trap(42)` program, coming through bridge +/// (it is prepended with `UniversalOrigin` instruction). It is used just for simplest manual +/// tests, confirming that we don't break encoding somewhere between. +pub const BASE_XCM_WEIGHT_TWICE: u64 = 2 * crate::BASE_XCM_WEIGHT; + +parameter_types! { + /// Weight credit for our test messages. + /// + /// 2 XCM instructions is for simple `Trap(42)` program, coming through bridge + /// (it is prepended with `UniversalOrigin` instruction). + pub const WeightCredit: Weight = Weight::from_ref_time(BASE_XCM_WEIGHT_TWICE); +} + +/// Message payload for RialtoParachain -> Millau messages. +pub type ToMillauMessagePayload = messages::source::FromThisChainMessagePayload; + +/// Message verifier for RialtoParachain -> Millau messages. +pub type ToMillauMessageVerifier = + messages::source::FromThisChainMessageVerifier; + +/// Message payload for Millau -> RialtoParachain messages. +pub type FromMillauMessagePayload = messages::target::FromBridgedChainMessagePayload; + +/// Call-dispatch based message dispatch for Millau -> RialtoParachain messages. +pub type FromMillauMessageDispatch = messages::target::FromBridgedChainMessageDispatch< + WithMillauMessageBridge, + xcm_executor::XcmExecutor, + crate::XcmWeigher, + WeightCredit, +>; + +/// Messages proof for Millau -> RialtoParachain messages. +pub type FromMillauMessagesProof = messages::target::FromBridgedChainMessagesProof; + +/// Messages delivery proof for RialtoParachain -> Millau messages. +pub type ToMillauMessagesDeliveryProof = + messages::source::FromBridgedChainMessagesDeliveryProof; + +/// Maximal outbound payload size of Rialto -> Millau messages. +pub type ToMillauMaximalOutboundPayloadSize = + messages::source::FromThisChainMaximalOutboundPayloadSize; + +/// Millau <-> RialtoParachain message bridge. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct WithMillauMessageBridge; + +impl MessageBridge for WithMillauMessageBridge { + const THIS_CHAIN_ID: ChainId = RIALTO_PARACHAIN_CHAIN_ID; + const BRIDGED_CHAIN_ID: ChainId = MILLAU_CHAIN_ID; + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = + bp_rialto_parachain::WITH_RIALTO_PARACHAIN_MESSAGES_PALLET_NAME; + + type ThisChain = RialtoParachain; + type BridgedChain = Millau; + type BridgedHeaderChain = + pallet_bridge_grandpa::GrandpaChainHeaders; +} + +/// RialtoParachain chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct RialtoParachain; + +impl messages::UnderlyingChainProvider for RialtoParachain { + type Chain = bp_rialto_parachain::RialtoParachain; +} + +impl messages::ThisChainWithMessages for RialtoParachain { + type RuntimeCall = RuntimeCall; + type RuntimeOrigin = RuntimeOrigin; + + fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool { + true + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + MessageNonce::MAX + } +} + +/// Millau chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct Millau; + +impl messages::UnderlyingChainProvider for Millau { + type Chain = bp_millau::Millau; +} + +impl messages::BridgedChainWithMessages for Millau { + fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { + true + } +} + +impl TargetHeaderChain for Millau { + type Error = &'static str; + // The proof is: + // - hash of the header this proof has been created with; + // - the storage proof of one or several keys; + // - id of the lane we prove state of. + type MessagesDeliveryProof = ToMillauMessagesDeliveryProof; + + fn verify_message(payload: &ToMillauMessagePayload) -> Result<(), Self::Error> { + messages::source::verify_chain_message::(payload) + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error> { + messages::source::verify_messages_delivery_proof::(proof) + } +} + +impl SourceHeaderChain for Millau { + type Error = &'static str; + // The proof is: + // - hash of the header this proof has been created with; + // - the storage proof of one or several keys; + // - id of the lane we prove messages for; + // - inclusive range of messages nonces that are proved. + type MessagesProof = FromMillauMessagesProof; + + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result, Self::Error> { + messages::target::verify_messages_proof::(proof, messages_count) + .map_err(Into::into) + } +} diff --git a/bin/rialto/node/Cargo.toml b/bin/rialto/node/Cargo.toml new file mode 100644 index 00000000000..82b073896d9 --- /dev/null +++ b/bin/rialto/node/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "rialto-bridge-node" +description = "Substrate node compatible with Rialto runtime" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +build = "build.rs" +repository = "https://github.com/paritytech/parity-bridges-common/" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +clap = { version = "4.0.9", features = ["derive"] } +serde_json = "1.0.79" + +# Bridge dependencies + +rialto-runtime = { path = "../runtime" } + +# Substrate Dependencies + +beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +node-inspect = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master", features = ["wasmtime"] } +sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-consensus-babe = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } + +# Polkadot Dependencies +polkadot-node-core-pvf = { git = "https://github.com/paritytech/polkadot", branch = "master" } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" } +polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "master" } +polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false, features = [ "full-node", "polkadot-native" ] } + +[build-dependencies] +substrate-build-script-utils = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = [] +runtime-benchmarks = [ + "rialto-runtime/runtime-benchmarks", +] diff --git a/bin/rialto/node/build.rs b/bin/rialto/node/build.rs new file mode 100644 index 00000000000..d9b50049e26 --- /dev/null +++ b/bin/rialto/node/build.rs @@ -0,0 +1,23 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; + +fn main() { + generate_cargo_keys(); + + rerun_if_git_head_changed(); +} diff --git a/bin/rialto/node/src/chain_spec.rs b/bin/rialto/node/src/chain_spec.rs new file mode 100644 index 00000000000..0e9edb38ac0 --- /dev/null +++ b/bin/rialto/node/src/chain_spec.rs @@ -0,0 +1,286 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use beefy_primitives::crypto::AuthorityId as BeefyId; +use frame_support::weights::Weight; +use polkadot_primitives::v2::{AssignmentId, ValidatorId}; +use rialto_runtime::{ + AccountId, BabeConfig, BalancesConfig, BeefyConfig, BridgeMillauMessagesConfig, + ConfigurationConfig, GenesisConfig, GrandpaConfig, SessionConfig, SessionKeys, Signature, + SudoConfig, SystemConfig, WASM_BINARY, +}; +use serde_json::json; +use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_consensus_babe::AuthorityId as BabeId; +use sp_core::{sr25519, Pair, Public}; +use sp_finality_grandpa::AuthorityId as GrandpaId; +use sp_runtime::traits::{IdentifyAccount, Verify}; + +/// "Names" of the authorities accounts at local testnet. +const LOCAL_AUTHORITIES_ACCOUNTS: [&str; 5] = ["Alice", "Bob", "Charlie", "Dave", "Eve"]; +/// "Names" of the authorities accounts at development testnet. +const DEV_AUTHORITIES_ACCOUNTS: [&str; 1] = [LOCAL_AUTHORITIES_ACCOUNTS[0]]; +/// "Names" of all possible authorities accounts. +const ALL_AUTHORITIES_ACCOUNTS: [&str; 5] = LOCAL_AUTHORITIES_ACCOUNTS; +/// "Name" of the `sudo` account. +const SUDO_ACCOUNT: &str = "Sudo"; +/// "Name" of the account, which owns the with-Millau messages pallet. +const MILLAU_MESSAGES_PALLET_OWNER: &str = "Millau.MessagesOwner"; + +/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. +pub type ChainSpec = + sc_service::GenericChainSpec; + +/// The chain specification option. This is expected to come in from the CLI and +/// is little more than one of a number of alternatives which can easily be converted +/// from a string (`--chain=...`) into a `ChainSpec`. +#[derive(Clone, Debug)] +pub enum Alternative { + /// Whatever the current runtime is, with just Alice as an auth. + Development, + /// Whatever the current runtime is, with simple Alice/Bob/Charlie/Dave/Eve auths. + LocalTestnet, +} + +/// Helper function to generate a crypto pair from seed +pub fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{seed}"), None) + .expect("static values are valid; qed") + .public() +} + +type AccountPublic = ::Signer; + +/// Helper function to generate an account ID from seed +pub fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() +} + +/// Helper function to generate authority keys. +pub fn get_authority_keys_from_seed( + s: &str, +) -> (AccountId, BabeId, BeefyId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) { + ( + get_account_id_from_seed::(s), + get_from_seed::(s), + get_from_seed::(s), + get_from_seed::(s), + get_from_seed::(s), + get_from_seed::(s), + get_from_seed::(s), + ) +} + +impl Alternative { + /// Get an actual chain config from one of the alternatives. + pub(crate) fn load(self) -> ChainSpec { + let properties = Some( + json!({ + "tokenDecimals": 9, + "tokenSymbol": "RLT" + }) + .as_object() + .expect("Map given; qed") + .clone(), + ); + match self { + Alternative::Development => ChainSpec::from_genesis( + "Rialto Development", + "rialto_dev", + sc_service::ChainType::Development, + || { + testnet_genesis( + DEV_AUTHORITIES_ACCOUNTS + .into_iter() + .map(get_authority_keys_from_seed) + .collect(), + get_account_id_from_seed::(SUDO_ACCOUNT), + endowed_accounts(), + true, + ) + }, + vec![], + None, + None, + None, + properties, + Default::default(), + ), + Alternative::LocalTestnet => ChainSpec::from_genesis( + "Rialto Local", + "rialto_local", + sc_service::ChainType::Local, + || { + testnet_genesis( + LOCAL_AUTHORITIES_ACCOUNTS + .into_iter() + .map(get_authority_keys_from_seed) + .collect(), + get_account_id_from_seed::(SUDO_ACCOUNT), + endowed_accounts(), + true, + ) + }, + vec![], + None, + None, + None, + properties, + Default::default(), + ), + } + } +} + +/// We're using the same set of endowed accounts on all Millau chains (dev/local) to make +/// sure that all accounts, required for bridge to be functional (e.g. relayers fund account, +/// accounts used by relayers in our test deployments, accounts used for demonstration +/// purposes), are all available on these chains. +fn endowed_accounts() -> Vec { + let all_authorities = ALL_AUTHORITIES_ACCOUNTS.iter().flat_map(|x| { + [ + get_account_id_from_seed::(x), + get_account_id_from_seed::(&format!("{x}//stash")), + ] + }); + vec![ + // Sudo account + get_account_id_from_seed::(SUDO_ACCOUNT), + // Regular (unused) accounts + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Ferdie//stash"), + // Accounts, used by Rialto<>Millau bridge + get_account_id_from_seed::(MILLAU_MESSAGES_PALLET_OWNER), + get_account_id_from_seed::("Millau.HeadersAndMessagesRelay"), + get_account_id_from_seed::("Millau.OutboundMessagesRelay.Lane00000001"), + get_account_id_from_seed::("Millau.InboundMessagesRelay.Lane00000001"), + get_account_id_from_seed::("Millau.MessagesSender"), + ] + .into_iter() + .chain(all_authorities) + .collect() +} + +fn session_keys( + babe: BabeId, + beefy: BeefyId, + grandpa: GrandpaId, + para_validator: ValidatorId, + para_assignment: AssignmentId, + authority_discovery: AuthorityDiscoveryId, +) -> SessionKeys { + SessionKeys { babe, beefy, grandpa, para_validator, para_assignment, authority_discovery } +} + +fn testnet_genesis( + initial_authorities: Vec<( + AccountId, + BabeId, + BeefyId, + GrandpaId, + ValidatorId, + AssignmentId, + AuthorityDiscoveryId, + )>, + root_key: AccountId, + endowed_accounts: Vec, + _enable_println: bool, +) -> GenesisConfig { + GenesisConfig { + system: SystemConfig { + code: WASM_BINARY.expect("Rialto development WASM not available").to_vec(), + }, + balances: BalancesConfig { + balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 50)).collect(), + }, + babe: BabeConfig { + authorities: Vec::new(), + epoch_config: Some(rialto_runtime::BABE_GENESIS_EPOCH_CONFIG), + }, + beefy: BeefyConfig { authorities: Vec::new() }, + grandpa: GrandpaConfig { authorities: Vec::new() }, + sudo: SudoConfig { key: Some(root_key) }, + session: SessionConfig { + keys: initial_authorities + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + session_keys( + x.1.clone(), + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + x.6.clone(), + ), + ) + }) + .collect::>(), + }, + authority_discovery: Default::default(), + hrmp: Default::default(), + // this configuration is exact copy of configuration from Polkadot repo + // (see /node/service/src/chain_spec.rs:default_parachains_host_configuration) + configuration: ConfigurationConfig { + config: polkadot_runtime_parachains::configuration::HostConfiguration { + validation_upgrade_cooldown: 2u32, + validation_upgrade_delay: 2, + code_retention_period: 1200, + max_code_size: polkadot_primitives::v2::MAX_CODE_SIZE, + max_pov_size: polkadot_primitives::v2::MAX_POV_SIZE, + max_head_data_size: 32 * 1024, + group_rotation_frequency: 20, + chain_availability_period: 4, + thread_availability_period: 4, + max_upward_queue_count: 8, + max_upward_queue_size: 1024 * 1024, + max_downward_message_size: 1024 * 1024, + ump_service_total_weight: Weight::from_ref_time(100_000_000_000), + max_upward_message_size: 50 * 1024, + max_upward_message_num_per_candidate: 5, + hrmp_sender_deposit: 0, + hrmp_recipient_deposit: 0, + hrmp_channel_max_capacity: 8, + hrmp_channel_max_total_size: 8 * 1024, + hrmp_max_parachain_inbound_channels: 4, + hrmp_max_parathread_inbound_channels: 4, + hrmp_channel_max_message_size: 1024 * 1024, + hrmp_max_parachain_outbound_channels: 4, + hrmp_max_parathread_outbound_channels: 4, + hrmp_max_message_num_per_candidate: 5, + dispute_period: 6, + no_show_slots: 2, + n_delay_tranches: 25, + needed_approvals: 2, + relay_vrf_modulo_samples: 2, + zeroth_delay_tranche_width: 0, + minimum_validation_upgrade_delay: 5, + ..Default::default() + }, + }, + paras: Default::default(), + bridge_millau_messages: BridgeMillauMessagesConfig { + owner: Some(get_account_id_from_seed::(MILLAU_MESSAGES_PALLET_OWNER)), + ..Default::default() + }, + xcm_pallet: Default::default(), + } +} diff --git a/bin/rialto/node/src/cli.rs b/bin/rialto/node/src/cli.rs new file mode 100644 index 00000000000..0cdd25417e7 --- /dev/null +++ b/bin/rialto/node/src/cli.rs @@ -0,0 +1,87 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use clap::Parser; +use sc_cli::RunCmd; + +#[derive(Debug, Parser)] +pub struct Cli { + #[structopt(subcommand)] + pub subcommand: Option, + + #[structopt(flatten)] + pub run: RunCmd, +} + +/// Possible subcommands of the main binary. +#[derive(Debug, Parser)] +pub enum Subcommand { + /// Key management CLI utilities + #[clap(subcommand)] + Key(sc_cli::KeySubcommand), + + /// Verify a signature for a message, provided on `STDIN`, with a given (public or secret) key. + Verify(sc_cli::VerifyCmd), + + /// Generate a seed that provides a vanity address. + Vanity(sc_cli::VanityCmd), + + /// Sign a message, with a given (secret) key. + Sign(sc_cli::SignCmd), + + /// Build a chain specification. + BuildSpec(sc_cli::BuildSpecCmd), + + /// Validate blocks. + CheckBlock(sc_cli::CheckBlockCmd), + + /// Export blocks. + ExportBlocks(sc_cli::ExportBlocksCmd), + + /// Export the state of a given block into a chain spec. + ExportState(sc_cli::ExportStateCmd), + + /// Import blocks. + ImportBlocks(sc_cli::ImportBlocksCmd), + + /// Remove the whole chain. + PurgeChain(sc_cli::PurgeChainCmd), + + /// Revert the chain to a previous state. + Revert(sc_cli::RevertCmd), + + /// Inspect blocks or extrinsics. + Inspect(node_inspect::cli::InspectCmd), + + /// Benchmark runtime pallets. + #[clap(subcommand)] + Benchmark(frame_benchmarking_cli::BenchmarkCmd), + + /// FOR INTERNAL USE: analog of the "prepare-worker" command of the polkadot binary. + #[clap(name = "prepare-worker", hide = true)] + PvfPrepareWorker(ValidationWorkerCommand), + + /// FOR INTERNAL USE: analog of the "execute-worker" command of the polkadot binary. + #[clap(name = "execute-worker", hide = true)] + PvfExecuteWorker(ValidationWorkerCommand), +} + +/// Validation worker command. +#[derive(Debug, Parser)] +pub struct ValidationWorkerCommand { + /// The path to the validation host's socket. + pub socket_path: String, +} diff --git a/bin/rialto/node/src/command.rs b/bin/rialto/node/src/command.rs new file mode 100644 index 00000000000..8286444d3ea --- /dev/null +++ b/bin/rialto/node/src/command.rs @@ -0,0 +1,222 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::cli::{Cli, Subcommand}; +use frame_benchmarking_cli::BenchmarkCmd; +use rialto_runtime::{Block, RuntimeApi}; +use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; + +impl SubstrateCli for Cli { + fn impl_name() -> String { + "Rialto Bridge Node".into() + } + + fn impl_version() -> String { + env!("CARGO_PKG_VERSION").into() + } + + fn description() -> String { + "Rialto Bridge Node".into() + } + + fn author() -> String { + "Parity Technologies".into() + } + + fn support_url() -> String { + "https://github.com/paritytech/parity-bridges-common/".into() + } + + fn copyright_start_year() -> i32 { + 2019 + } + + fn executable_name() -> String { + "rialto-bridge-node".into() + } + + fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { + &rialto_runtime::VERSION + } + + fn load_spec(&self, id: &str) -> Result, String> { + Ok(Box::new( + match id { + "" | "dev" => crate::chain_spec::Alternative::Development, + "local" => crate::chain_spec::Alternative::LocalTestnet, + _ => return Err(format!("Unsupported chain specification: {id}")), + } + .load(), + )) + } +} + +// Rialto native executor instance. +pub struct ExecutorDispatch; + +impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + + fn dispatch(method: &str, data: &[u8]) -> Option> { + rialto_runtime::api::dispatch(method, data) + } + + fn native_version() -> sc_executor::NativeVersion { + rialto_runtime::native_version() + } +} + +/// Parse and run command line arguments +pub fn run() -> sc_cli::Result<()> { + let cli = Cli::from_args(); + sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom( + rialto_runtime::SS58Prefix::get() as u16, + )); + + match &cli.subcommand { + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + match cmd { + BenchmarkCmd::Pallet(cmd) => + if cfg!(feature = "runtime-benchmarks") { + runner.sync_run(|config| cmd.run::(config)) + } else { + println!( + "Benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + ); + Ok(()) + }, + _ => Err("Unsupported benchmarking subcommand".into()), + } + }, + Some(Subcommand::Key(cmd)) => cmd.run(&cli), + Some(Subcommand::Sign(cmd)) => cmd.run(), + Some(Subcommand::Verify(cmd)) => cmd.run(), + Some(Subcommand::Vanity(cmd)) => cmd.run(), + Some(Subcommand::BuildSpec(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) + }, + Some(Subcommand::CheckBlock(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|mut config| { + let (client, _, import_queue, task_manager) = + polkadot_service::new_chain_ops(&mut config, None).map_err(service_error)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::ExportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|mut config| { + let (client, _, _, task_manager) = + polkadot_service::new_chain_ops(&mut config, None).map_err(service_error)?; + Ok((cmd.run(client, config.database), task_manager)) + }) + }, + Some(Subcommand::ExportState(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|mut config| { + let (client, _, _, task_manager) = + polkadot_service::new_chain_ops(&mut config, None).map_err(service_error)?; + Ok((cmd.run(client, config.chain_spec), task_manager)) + }) + }, + Some(Subcommand::ImportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|mut config| { + let (client, _, import_queue, task_manager) = + polkadot_service::new_chain_ops(&mut config, None).map_err(service_error)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::PurgeChain(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.database)) + }, + Some(Subcommand::Revert(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|mut config| { + let (client, backend, _, task_manager) = + polkadot_service::new_chain_ops(&mut config, None).map_err(service_error)?; + Ok((cmd.run(client, backend, None), task_manager)) + }) + }, + Some(Subcommand::Inspect(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run::(config)) + }, + Some(Subcommand::PvfPrepareWorker(cmd)) => { + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + polkadot_node_core_pvf::prepare_worker_entrypoint(&cmd.socket_path); + Ok(()) + }, + Some(crate::cli::Subcommand::PvfExecuteWorker(cmd)) => { + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + polkadot_node_core_pvf::execute_worker_entrypoint(&cmd.socket_path); + Ok(()) + }, + None => { + let runner = cli.create_runner(&cli.run)?; + + // some parameters that are used by polkadot nodes, but that are not used by our binary + // let jaeger_agent = None; + // let grandpa_pause = None; + // let no_beefy = true; + // let telemetry_worker_handler = None; + // let is_collator = crate::service::IsCollator::No; + let overseer_gen = polkadot_service::overseer::RealOverseerGen; + runner.run_node_until_exit(|config| async move { + let is_collator = polkadot_service::IsCollator::No; + let grandpa_pause = None; + let enable_beefy = true; + let jaeger_agent = None; + let telemetry_worker_handle = None; + let program_path = None; + let overseer_enable_anyways = false; + + polkadot_service::new_full::( + config, + is_collator, + grandpa_pause, + enable_beefy, + jaeger_agent, + telemetry_worker_handle, + program_path, + overseer_enable_anyways, + overseer_gen, + None, + None, + None, + ) + .map(|full| full.task_manager) + .map_err(service_error) + }) + }, + } +} + +// We don't want to change 'service.rs' too much to ease future updates => it'll keep using +// its own error enum like original polkadot service does. +fn service_error(err: polkadot_service::Error) -> sc_cli::Error { + sc_cli::Error::Application(Box::new(err)) +} diff --git a/bin/rialto/node/src/main.rs b/bin/rialto/node/src/main.rs new file mode 100644 index 00000000000..6dea84a309b --- /dev/null +++ b/bin/rialto/node/src/main.rs @@ -0,0 +1,28 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rialto bridge node. + +#![warn(missing_docs)] + +mod chain_spec; +mod cli; +mod command; + +/// Run the Rialto Node +fn main() -> sc_cli::Result<()> { + command::run() +} diff --git a/bin/rialto/runtime/Cargo.toml b/bin/rialto/runtime/Cargo.toml new file mode 100644 index 00000000000..7220e5790a4 --- /dev/null +++ b/bin/rialto/runtime/Cargo.toml @@ -0,0 +1,145 @@ +[package] +name = "rialto-runtime" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +repository = "https://github.com/paritytech/parity-bridges-common/" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Bridge dependencies + +bp-messages = { path = "../../../primitives/messages", default-features = false } +bp-millau = { path = "../../../primitives/chain-millau", default-features = false } +bp-relayers = { path = "../../../primitives/relayers", default-features = false } +bp-rialto = { path = "../../../primitives/chain-rialto", default-features = false } +bp-runtime = { path = "../../../primitives/runtime", default-features = false } +bridge-runtime-common = { path = "../../runtime-common", default-features = false } +pallet-bridge-beefy = { path = "../../../modules/beefy", default-features = false } +pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false } +pallet-bridge-messages = { path = "../../../modules/messages", default-features = false } +pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false } +pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false } + +# Substrate Dependencies + +beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +frame-executive = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-babe = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-beefy = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-consensus-babe = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-mmr-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-offchain = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Polkadot (parachain) Dependencies +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } + +[dev-dependencies] +bridge-runtime-common = { path = "../../runtime-common", features = ["integrity-test"] } +env_logger = "0.8" +static_assertions = "1.1" + +[build-dependencies] +substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = ["std"] +std = [ + "beefy-primitives/std", + "bp-messages/std", + "bp-millau/std", + "bp-relayers/std", + "bp-rialto/std", + "bp-runtime/std", + "bridge-runtime-common/std", + "codec/std", + "frame-benchmarking/std", + "frame-executive/std", + "frame-support/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "log/std", + "pallet-authority-discovery/std", + "pallet-babe/std", + "pallet-balances/std", + "pallet-beefy/std", + "pallet-beefy-mmr/std", + "pallet-bridge-beefy/std", + "pallet-bridge-grandpa/std", + "pallet-bridge-messages/std", + "pallet-bridge-relayers/std", + "pallet-grandpa/std", + "pallet-mmr/std", + "pallet-xcm/std", + "sp-mmr-primitives/std", + "pallet-shift-session-manager/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "polkadot-primitives/std", + "polkadot-runtime-common/std", + "polkadot-runtime-parachains/std", + "scale-info/std", + "sp-api/std", + "sp-authority-discovery/std", + "sp-block-builder/std", + "sp-consensus-babe/std", + "sp-core/std", + "sp-inherents/std", + "sp-io/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-transaction-pool/std", + "sp-version/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", +] +runtime-benchmarks = [ + "bridge-runtime-common/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-bridge-messages/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] diff --git a/bin/rialto/runtime/build.rs b/bin/rialto/runtime/build.rs new file mode 100644 index 00000000000..cc865704327 --- /dev/null +++ b/bin/rialto/runtime/build.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use substrate_wasm_builder::WasmBuilder; + +fn main() { + WasmBuilder::new() + .with_current_project() + .import_memory() + .export_heap_base() + .build() +} diff --git a/bin/rialto/runtime/src/lib.rs b/bin/rialto/runtime/src/lib.rs new file mode 100644 index 00000000000..25778705e5e --- /dev/null +++ b/bin/rialto/runtime/src/lib.rs @@ -0,0 +1,1006 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! The Rialto runtime. This can be compiled with `#[no_std]`, ready for Wasm. + +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] +// Runtime-generated enums +#![allow(clippy::large_enum_variant)] +// From construct_runtime macro +#![allow(clippy::from_over_into)] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +pub mod millau_messages; +pub mod parachains; +pub mod xcm_config; + +use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet}; +use bp_runtime::{HeaderId, HeaderIdProvider}; +use pallet_grandpa::{ + fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, +}; +use pallet_mmr::primitives as mmr; +use pallet_transaction_payment::{FeeDetails, Multiplier, RuntimeDispatchInfo}; +use sp_api::impl_runtime_apis; +use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_mmr_primitives::{ + DataOrHash, EncodableOpaqueLeaf, Error as MmrError, LeafDataProvider, Proof as MmrProof, +}; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::{AccountIdLookup, Block as BlockT, Keccak256, NumberFor, OpaqueKeys}, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, FixedPointNumber, Perquintill, +}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +// A few exports that help ease life for downstream crates. +pub use frame_support::{ + construct_runtime, + dispatch::DispatchClass, + parameter_types, + traits::{Currency, ExistenceRequirement, Imbalance, KeyOwnerProofSystem}, + weights::{constants::WEIGHT_PER_SECOND, IdentityFee, RuntimeDbWeight, Weight}, + StorageValue, +}; + +pub use frame_system::Call as SystemCall; +pub use pallet_balances::Call as BalancesCall; +pub use pallet_bridge_beefy::Call as BridgeBeefyCall; +pub use pallet_bridge_grandpa::Call as BridgeGrandpaCall; +pub use pallet_bridge_messages::Call as MessagesCall; +pub use pallet_sudo::Call as SudoCall; +pub use pallet_timestamp::Call as TimestampCall; +pub use pallet_xcm::Call as XcmCall; + +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +pub use sp_runtime::{Perbill, Permill}; + +/// An index to a block. +pub type BlockNumber = bp_rialto::BlockNumber; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = bp_rialto::Signature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = bp_rialto::AccountId; + +/// The type for looking up accounts. We don't expect more than 4 billion of them, but you +/// never know... +pub type AccountIndex = u32; + +/// Balance of an account. +pub type Balance = bp_rialto::Balance; + +/// Index of a transaction in the chain. +pub type Index = bp_rialto::Index; + +/// A hash of some data used by the chain. +pub type Hash = bp_rialto::Hash; + +/// Hashing algorithm used by the chain. +pub type Hashing = bp_rialto::Hasher; + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + use super::*; + + pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + + /// Opaque block header type. + pub type Header = generic::Header; + /// Opaque block type. + pub type Block = generic::Block; + /// Opaque block identifier type. + pub type BlockId = generic::BlockId; +} + +impl_opaque_keys! { + pub struct SessionKeys { + pub babe: Babe, + pub grandpa: Grandpa, + pub beefy: Beefy, + pub para_validator: Initializer, + pub para_assignment: SessionInfo, + pub authority_discovery: AuthorityDiscovery, + } +} + +/// This runtime version. +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("rialto-runtime"), + impl_name: create_runtime_str!("rialto-runtime"), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 1, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const BlockHashCount: BlockNumber = 250; + pub const Version: RuntimeVersion = VERSION; + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 60_000_000, // ~0.06 ms = ~60 µs + write: 200_000_000, // ~0.2 ms = 200 µs + }; + pub const SS58Prefix: u8 = 48; +} + +impl frame_system::Config for Runtime { + /// The basic call filter to use in dispatchable. + type BaseCallFilter = frame_support::traits::Everything; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type RuntimeCall = RuntimeCall; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = Hashing; + /// The header type. + type Header = generic::Header; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + /// The ubiquitous origin type. + type RuntimeOrigin = RuntimeOrigin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// Version of the runtime. + type Version = Version; + /// Provides information about the pallet setup in the runtime. + type PalletInfo = PalletInfo; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// Block and extrinsics weights: base values and limits. + type BlockWeights = bp_rialto::BlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = bp_rialto::BlockLength; + /// The weight of database operations that the runtime can invoke. + type DbWeight = DbWeight; + /// The designated SS58 prefix of this chain. + type SS58Prefix = SS58Prefix; + /// The set code logic, just the default since we're not a parachain. + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +/// The BABE epoch configuration at genesis. +pub const BABE_GENESIS_EPOCH_CONFIG: sp_consensus_babe::BabeEpochConfiguration = + sp_consensus_babe::BabeEpochConfiguration { + c: bp_rialto::time_units::PRIMARY_PROBABILITY, + allowed_slots: sp_consensus_babe::AllowedSlots::PrimaryAndSecondaryVRFSlots, + }; + +parameter_types! { + pub const EpochDuration: u64 = bp_rialto::EPOCH_DURATION_IN_SLOTS as u64; + pub const ExpectedBlockTime: bp_rialto::Moment = bp_rialto::time_units::MILLISECS_PER_BLOCK; + pub const MaxAuthorities: u32 = 10; +} + +impl pallet_babe::Config for Runtime { + type EpochDuration = EpochDuration; + type ExpectedBlockTime = ExpectedBlockTime; + type MaxAuthorities = MaxAuthorities; + + // session module is the trigger + type EpochChangeTrigger = pallet_babe::ExternalTrigger; + + // equivocation related configuration - we don't expect any equivocations in our testnets + type KeyOwnerProofSystem = (); + type KeyOwnerProof = >::Proof; + type KeyOwnerIdentification = >::IdentificationTuple; + type HandleEquivocation = (); + + type DisabledValidators = (); + type WeightInfo = (); +} + +impl pallet_beefy::Config for Runtime { + type BeefyId = BeefyId; + type MaxAuthorities = MaxAuthorities; + type OnNewValidatorSet = MmrLeaf; +} + +impl pallet_grandpa::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxAuthorities = MaxAuthorities; + type KeyOwnerProofSystem = (); + type KeyOwnerProof = + >::Proof; + type KeyOwnerIdentification = >::IdentificationTuple; + type HandleEquivocation = (); + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + type WeightInfo = (); +} + +type MmrHash = ::Output; +type MmrHashing = ::Hashing; + +impl pallet_mmr::Config for Runtime { + const INDEXING_PREFIX: &'static [u8] = b"mmr"; + type Hashing = Keccak256; + type Hash = ::Output; + type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; + type WeightInfo = (); + type LeafData = pallet_beefy_mmr::Pallet; +} + +parameter_types! { + /// Version of the produced MMR leaf. + /// + /// The version consists of two parts; + /// - `major` (3 bits) + /// - `minor` (5 bits) + /// + /// `major` should be updated only if decoding the previous MMR Leaf format from the payload + /// is not possible (i.e. backward incompatible change). + /// `minor` should be updated if fields are added to the previous MMR Leaf, which given SCALE + /// encoding does not prevent old leafs from being decoded. + /// + /// Hence we expect `major` to be changed really rarely (think never). + /// See [`MmrLeafVersion`] type documentation for more details. + pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(0, 0); +} + +pub struct BeefyDummyDataProvider; + +impl beefy_primitives::mmr::BeefyDataProvider<()> for BeefyDummyDataProvider { + fn extra_data() {} +} + +impl pallet_beefy_mmr::Config for Runtime { + type LeafVersion = LeafVersion; + type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; + type LeafExtra = (); + type BeefyDataProvider = BeefyDummyDataProvider; +} + +parameter_types! { + pub const MinimumPeriod: u64 = bp_rialto::SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the UNIX epoch. + type Moment = bp_rialto::Moment; + type OnTimestampSet = Babe; + type MinimumPeriod = MinimumPeriod; + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + type WeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: bp_rialto::Balance = 500; + // For weight estimation, we assume that the most locks on an individual account will be 50. + // This number may need to be adjusted in the future if this assumption no longer holds true. + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const TransactionBaseFee: Balance = 0; + pub const TransactionByteFee: Balance = 1; + pub const OperationalFeeMultiplier: u8 = 5; + // values for following parameters are copied from polkadot repo, but it is fine + // not to sync them - we're not going to make Rialto a full copy of one of Polkadot-like chains + pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); + pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000); + pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); + pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value(); +} + +impl pallet_transaction_payment::Config for Runtime { + type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; + type OperationalFeeMultiplier = OperationalFeeMultiplier; + type WeightToFee = bp_rialto::WeightToFee; + type LengthToFee = bp_rialto::WeightToFee; + type FeeMultiplierUpdate = pallet_transaction_payment::TargetedFeeAdjustment< + Runtime, + TargetBlockFullness, + AdjustmentVariable, + MinimumMultiplier, + MaximumMultiplier, + >; + type RuntimeEvent = RuntimeEvent; +} + +impl pallet_sudo::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + type ValidatorIdOf = (); + type ShouldEndSession = Babe; + type NextSessionRotation = Babe; + type SessionManager = pallet_shift_session_manager::Pallet; + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78) + type WeightInfo = (); +} + +impl pallet_authority_discovery::Config for Runtime { + type MaxAuthorities = MaxAuthorities; +} + +impl pallet_bridge_relayers::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reward = Balance; + type PaymentProcedure = bp_relayers::MintReward, AccountId>; + type WeightInfo = (); +} + +parameter_types! { + /// This is a pretty unscientific cap. + /// + /// Note that once this is hit the pallet will essentially throttle incoming requests down to one + /// call per block. + pub const MaxRequests: u32 = 50; + + /// Number of headers to keep. + /// + /// Assuming the worst case of every header being finalized, we will keep headers at least for a + /// week. + pub const HeadersToKeep: u32 = 7 * bp_rialto::DAYS; + + /// Maximal number of authorities at Millau. + pub const MaxAuthoritiesAtMillau: u32 = bp_millau::MAX_AUTHORITIES_COUNT; + /// Maximal size of SCALE-encoded Millau header. + pub const MaxMillauHeaderSize: u32 = bp_millau::MAX_HEADER_SIZE; +} + +pub type MillauGrandpaInstance = (); +impl pallet_bridge_grandpa::Config for Runtime { + type BridgedChain = bp_millau::Millau; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type MaxBridgedAuthorities = MaxAuthoritiesAtMillau; + type MaxBridgedHeaderSize = MaxMillauHeaderSize; + type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; +} + +impl pallet_shift_session_manager::Config for Runtime {} + +parameter_types! { + pub const MaxMessagesToPruneAtOnce: bp_messages::MessageNonce = 8; + pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_millau::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_millau::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + pub const RootAccountForPayments: Option = None; + pub const BridgedChainId: bp_runtime::ChainId = bp_runtime::MILLAU_CHAIN_ID; + pub ActiveOutboundLanes: &'static [bp_messages::LaneId] = &[millau_messages::XCM_LANE]; +} + +/// Instance of the messages pallet used to relay messages to/from Millau chain. +pub type WithMillauMessagesInstance = (); + +impl pallet_bridge_messages::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; + type ActiveOutboundLanes = ActiveOutboundLanes; + type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = crate::millau_messages::ToMillauMaximalOutboundPayloadSize; + type OutboundPayload = crate::millau_messages::ToMillauMessagePayload; + + type InboundPayload = crate::millau_messages::FromMillauMessagePayload; + type InboundRelayer = bp_millau::AccountId; + + type TargetHeaderChain = crate::millau_messages::Millau; + type LaneMessageVerifier = crate::millau_messages::ToMillauMessageVerifier; + type MessageDeliveryAndDispatchPayment = + pallet_bridge_relayers::MessageDeliveryAndDispatchPaymentAdapter< + Runtime, + WithMillauMessagesInstance, + >; + + type SourceHeaderChain = crate::millau_messages::Millau; + type MessageDispatch = crate::millau_messages::FromMillauMessageDispatch; + type BridgedChainId = BridgedChainId; +} + +pub type MillauBeefyInstance = (); +impl pallet_bridge_beefy::Config for Runtime { + type MaxRequests = frame_support::traits::ConstU32<16>; + type CommitmentsToKeep = frame_support::traits::ConstU32<8>; + type BridgedChain = bp_millau::Millau; +} + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = opaque::Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, + + // Must be before session. + Babe: pallet_babe::{Pallet, Call, Storage, Config, ValidateUnsigned}, + + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + + // Consensus support. + AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config}, + Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event}, + ShiftSessionManager: pallet_shift_session_manager::{Pallet}, + + // BEEFY Bridges support. + Beefy: pallet_beefy::{Pallet, Storage, Config}, + Mmr: pallet_mmr::{Pallet, Storage}, + MmrLeaf: pallet_beefy_mmr::{Pallet, Storage}, + + // Millau bridge modules. + BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event}, + BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage}, + BridgeMillauMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event, Config}, + + // Millau bridge modules (BEEFY based). + BridgeMillauBeefy: pallet_bridge_beefy::{Pallet, Call, Storage}, + + // Parachain modules. + ParachainsOrigin: polkadot_runtime_parachains::origin::{Pallet, Origin}, + Configuration: polkadot_runtime_parachains::configuration::{Pallet, Call, Storage, Config}, + Shared: polkadot_runtime_parachains::shared::{Pallet, Call, Storage}, + Inclusion: polkadot_runtime_parachains::inclusion::{Pallet, Call, Storage, Event}, + ParasInherent: polkadot_runtime_parachains::paras_inherent::{Pallet, Call, Storage, Inherent}, + Scheduler: polkadot_runtime_parachains::scheduler::{Pallet, Storage}, + Paras: polkadot_runtime_parachains::paras::{Pallet, Call, Storage, Event, Config}, + Initializer: polkadot_runtime_parachains::initializer::{Pallet, Call, Storage}, + Dmp: polkadot_runtime_parachains::dmp::{Pallet, Call, Storage}, + Ump: polkadot_runtime_parachains::ump::{Pallet, Call, Storage, Event}, + Hrmp: polkadot_runtime_parachains::hrmp::{Pallet, Call, Storage, Event, Config}, + SessionInfo: polkadot_runtime_parachains::session_info::{Pallet, Storage}, + ParaSessionInfo: polkadot_runtime_parachains::session_info::{Pallet, Storage}, + ParasDisputes: polkadot_runtime_parachains::disputes::{Pallet, Call, Storage, Event}, + ParasSlashing: polkadot_runtime_parachains::disputes::slashing::{Pallet, Call, Storage, ValidateUnsigned}, + + // Parachain Onboarding Pallets + Registrar: polkadot_runtime_common::paras_registrar::{Pallet, Call, Storage, Event}, + Slots: polkadot_runtime_common::slots::{Pallet, Call, Storage, Event}, + ParasSudoWrapper: polkadot_runtime_common::paras_sudo_wrapper::{Pallet, Call}, + + // Pallet for sending XCM. + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 99, + } +); + +/// The address format for describing accounts. +pub type Address = sp_runtime::MultiAddress; +/// Block header type as expected by this runtime. +pub type Header = generic::Header; +/// Block type as expected by this runtime. +pub type Block = generic::Block; +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); +/// The payload being signed in transactions. +pub type SignedPayload = generic::SignedPayload; +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = generic::CheckedExtrinsic; +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, +>; + +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block); + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Index { + System::account_nonce(account) + } + } + + impl beefy_primitives::BeefyApi for Runtime { + fn validator_set() -> Option> { + Beefy::validator_set() + } + } + + impl sp_mmr_primitives::MmrApi for Runtime { + fn generate_proof(block_number: BlockNumber) + -> Result<(EncodableOpaqueLeaf, MmrProof), MmrError> + { + Mmr::generate_batch_proof(vec![block_number]) + .and_then(|(leaves, proof)| Ok(( + mmr::EncodableOpaqueLeaf::from_leaf(&leaves[0]), + mmr::BatchProof::into_single_leaf_proof(proof)? + ))) + } + + fn verify_proof(leaf: EncodableOpaqueLeaf, proof: MmrProof) + -> Result<(), MmrError> + { + pub type Leaf = < + ::LeafData as LeafDataProvider + >::LeafData; + + let leaf: Leaf = leaf + .into_opaque_leaf() + .try_decode() + .ok_or(MmrError::Verify)?; + Mmr::verify_leaves(vec![leaf], mmr::Proof::into_batch_proof(proof)) + } + + fn verify_proof_stateless( + root: Hash, + leaf: EncodableOpaqueLeaf, + proof: MmrProof + ) -> Result<(), MmrError> { + let node = DataOrHash::Data(leaf.into_opaque_leaf()); + pallet_mmr::verify_leaves_proof::( + root, + vec![node], + pallet_mmr::primitives::Proof::into_batch_proof(proof), + ) + } + + fn mmr_root() -> Result { + Ok(Mmr::mmr_root()) + } + + fn generate_batch_proof(block_numbers: Vec) + -> Result<(Vec, mmr::BatchProof), mmr::Error> + { + Mmr::generate_batch_proof(block_numbers) + .map(|(leaves, proof)| (leaves.into_iter().map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf)).collect(), proof)) + } + + fn generate_historical_batch_proof( + block_numbers: Vec, + best_known_block_number: BlockNumber + ) -> Result<(Vec, mmr::BatchProof), mmr::Error> { + Mmr::generate_historical_batch_proof(block_numbers, best_known_block_number).map( + |(leaves, proof)| { + ( + leaves + .into_iter() + .map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf)) + .collect(), + proof, + ) + }, + ) + } + + fn verify_batch_proof(leaves: Vec, proof: mmr::BatchProof) + -> Result<(), mmr::Error> + { + type Leaf = < + ::LeafData as LeafDataProvider + >::LeafData; + let leaves = leaves.into_iter().map(|leaf| + leaf.into_opaque_leaf() + .try_decode() + .ok_or(mmr::Error::Verify)).collect::, mmr::Error>>()?; + Mmr::verify_leaves(leaves, proof) + } + + fn verify_batch_proof_stateless( + root: MmrHash, + leaves: Vec, + proof: mmr::BatchProof + ) -> Result<(), mmr::Error> { + let nodes = leaves.into_iter().map(|leaf|mmr::DataOrHash::Data(leaf.into_opaque_leaf())).collect(); + pallet_mmr::verify_leaves_proof::(root, nodes, proof) + } + } + + impl bp_millau::MillauFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeMillauGrandpa::best_finalized().map(|header| header.id()) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_consensus_babe::BabeApi for Runtime { + fn configuration() -> sp_consensus_babe::BabeConfiguration { + // The choice of `c` parameter (where `1 - c` represents the + // probability of a slot being empty), is done in accordance to the + // slot duration and expected target block time, for safely + // resisting network delays of maximum two seconds. + // + sp_consensus_babe::BabeConfiguration { + slot_duration: Babe::slot_duration(), + epoch_length: EpochDuration::get(), + c: BABE_GENESIS_EPOCH_CONFIG.c, + authorities: Babe::authorities().to_vec(), + randomness: Babe::randomness(), + allowed_slots: BABE_GENESIS_EPOCH_CONFIG.allowed_slots, + } + } + + fn current_epoch_start() -> sp_consensus_babe::Slot { + Babe::current_epoch_start() + } + + fn current_epoch() -> sp_consensus_babe::Epoch { + Babe::current_epoch() + } + + fn next_epoch() -> sp_consensus_babe::Epoch { + Babe::next_epoch() + } + + fn generate_key_ownership_proof( + _slot: sp_consensus_babe::Slot, + _authority_id: sp_consensus_babe::AuthorityId, + ) -> Option { + None + } + + fn submit_report_equivocation_unsigned_extrinsic( + equivocation_proof: sp_consensus_babe::EquivocationProof<::Header>, + key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof, + ) -> Option<()> { + let key_owner_proof = key_owner_proof.decode()?; + + Babe::submit_unsigned_equivocation_report( + equivocation_proof, + key_owner_proof, + ) + } + } + + impl polkadot_primitives::runtime_api::ParachainHost for Runtime { + fn validators() -> Vec { + polkadot_runtime_parachains::runtime_api_impl::v2::validators::() + } + + fn validator_groups() -> (Vec>, polkadot_primitives::v2::GroupRotationInfo) { + polkadot_runtime_parachains::runtime_api_impl::v2::validator_groups::() + } + + fn availability_cores() -> Vec> { + polkadot_runtime_parachains::runtime_api_impl::v2::availability_cores::() + } + + fn persisted_validation_data(para_id: polkadot_primitives::v2::Id, assumption: polkadot_primitives::v2::OccupiedCoreAssumption) + -> Option> { + polkadot_runtime_parachains::runtime_api_impl::v2::persisted_validation_data::(para_id, assumption) + } + + fn assumed_validation_data( + para_id: polkadot_primitives::v2::Id, + expected_persisted_validation_data_hash: Hash, + ) -> Option<(polkadot_primitives::v2::PersistedValidationData, polkadot_primitives::v2::ValidationCodeHash)> { + polkadot_runtime_parachains::runtime_api_impl::v2::assumed_validation_data::( + para_id, + expected_persisted_validation_data_hash, + ) + } + + fn check_validation_outputs( + para_id: polkadot_primitives::v2::Id, + outputs: polkadot_primitives::v2::CandidateCommitments, + ) -> bool { + polkadot_runtime_parachains::runtime_api_impl::v2::check_validation_outputs::(para_id, outputs) + } + + fn session_index_for_child() -> polkadot_primitives::v2::SessionIndex { + polkadot_runtime_parachains::runtime_api_impl::v2::session_index_for_child::() + } + + fn validation_code(para_id: polkadot_primitives::v2::Id, assumption: polkadot_primitives::v2::OccupiedCoreAssumption) + -> Option { + polkadot_runtime_parachains::runtime_api_impl::v2::validation_code::(para_id, assumption) + } + + fn candidate_pending_availability(para_id: polkadot_primitives::v2::Id) -> Option> { + polkadot_runtime_parachains::runtime_api_impl::v2::candidate_pending_availability::(para_id) + } + + fn candidate_events() -> Vec> { + polkadot_runtime_parachains::runtime_api_impl::v2::candidate_events::(|ev| { + match ev { + RuntimeEvent::Inclusion(ev) => { + Some(ev) + } + _ => None, + } + }) + } + + fn session_info(index: polkadot_primitives::v2::SessionIndex) -> Option { + polkadot_runtime_parachains::runtime_api_impl::v2::session_info::(index) + } + + fn dmq_contents(recipient: polkadot_primitives::v2::Id) -> Vec> { + polkadot_runtime_parachains::runtime_api_impl::v2::dmq_contents::(recipient) + } + + fn inbound_hrmp_channels_contents( + recipient: polkadot_primitives::v2::Id + ) -> BTreeMap>> { + polkadot_runtime_parachains::runtime_api_impl::v2::inbound_hrmp_channels_contents::(recipient) + } + + fn validation_code_by_hash(hash: polkadot_primitives::v2::ValidationCodeHash) -> Option { + polkadot_runtime_parachains::runtime_api_impl::v2::validation_code_by_hash::(hash) + } + + fn on_chain_votes() -> Option> { + polkadot_runtime_parachains::runtime_api_impl::v2::on_chain_votes::() + } + + fn submit_pvf_check_statement(stmt: polkadot_primitives::v2::PvfCheckStatement, signature: polkadot_primitives::v2::ValidatorSignature) { + polkadot_runtime_parachains::runtime_api_impl::v2::submit_pvf_check_statement::(stmt, signature) + } + + fn pvfs_require_precheck() -> Vec { + polkadot_runtime_parachains::runtime_api_impl::v2::pvfs_require_precheck::() + } + + fn validation_code_hash(para_id: polkadot_primitives::v2::Id, assumption: polkadot_primitives::v2::OccupiedCoreAssumption) + -> Option + { + polkadot_runtime_parachains::runtime_api_impl::v2::validation_code_hash::(para_id, assumption) + } + } + + impl sp_authority_discovery::AuthorityDiscoveryApi for Runtime { + fn authorities() -> Vec { + polkadot_runtime_parachains::runtime_api_impl::v2::relevant_authority_ids::() + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< + Block, + Balance, + > for Runtime { + fn query_info(uxt: ::Extrinsic, len: u32) -> RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details(uxt: ::Extrinsic, len: u32) -> FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, sp_core::crypto::KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl fg_primitives::GrandpaApi for Runtime { + fn current_set_id() -> fg_primitives::SetId { + Grandpa::current_set_id() + } + + fn grandpa_authorities() -> GrandpaAuthorityList { + Grandpa::grandpa_authorities() + } + + fn submit_report_equivocation_unsigned_extrinsic( + equivocation_proof: fg_primitives::EquivocationProof< + ::Hash, + NumberFor, + >, + key_owner_proof: fg_primitives::OpaqueKeyOwnershipProof, + ) -> Option<()> { + let key_owner_proof = key_owner_proof.decode()?; + + Grandpa::submit_unsigned_equivocation_report( + equivocation_proof, + key_owner_proof, + ) + } + + fn generate_key_ownership_proof( + _set_id: fg_primitives::SetId, + _authority_id: GrandpaId, + ) -> Option { + // NOTE: this is the only implementation possible since we've + // defined our key owner proof type as a bottom type (i.e. a type + // with no values). + None + } + } + + impl bp_millau::ToMillauOutboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + begin: bp_messages::MessageNonce, + end: bp_messages::MessageNonce, + ) -> Vec { + bridge_runtime_common::messages_api::outbound_message_details::< + Runtime, + WithMillauMessagesInstance, + >(lane, begin, end) + } + } + + impl bp_millau::FromMillauInboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, + ) -> Vec { + bridge_runtime_common::messages_api::inbound_message_details::< + Runtime, + WithMillauMessagesInstance, + >(lane, messages) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn call_size() { + const BRIDGES_PALLETS_MAX_CALL_SIZE: usize = 200; + assert!( + core::mem::size_of::>() <= + BRIDGES_PALLETS_MAX_CALL_SIZE + ); + assert!( + core::mem::size_of::>() <= + BRIDGES_PALLETS_MAX_CALL_SIZE + ); + // Largest inner Call is `pallet_session::Call` with a size of 224 bytes. This size is a + // result of large `SessionKeys` struct. + // Total size of Rialto runtime Call is 232. + const MAX_CALL_SIZE: usize = 232; + assert!(core::mem::size_of::() <= MAX_CALL_SIZE); + } +} diff --git a/bin/rialto/runtime/src/millau_messages.rs b/bin/rialto/runtime/src/millau_messages.rs new file mode 100644 index 00000000000..66480c3168d --- /dev/null +++ b/bin/rialto/runtime/src/millau_messages.rs @@ -0,0 +1,242 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Everything required to serve Millau <-> Rialto messages. + +use crate::{MillauGrandpaInstance, Runtime, RuntimeCall, RuntimeOrigin}; + +use bp_messages::{ + source_chain::TargetHeaderChain, + target_chain::{ProvedMessages, SourceHeaderChain}, + InboundLaneData, LaneId, Message, MessageNonce, +}; +use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_CHAIN_ID}; +use bridge_runtime_common::messages::{self, MessageBridge}; +use frame_support::{parameter_types, weights::Weight, RuntimeDebug}; + +/// Lane that is used for XCM messages exchange. +pub const XCM_LANE: LaneId = [0, 0, 0, 0]; +/// Weight of 2 XCM instructions is for simple `Trap(42)` program, coming through bridge +/// (it is prepended with `UniversalOrigin` instruction). It is used just for simplest manual +/// tests, confirming that we don't break encoding somewhere between. +pub const BASE_XCM_WEIGHT_TWICE: u64 = 2 * crate::xcm_config::BASE_XCM_WEIGHT; + +parameter_types! { + /// Weight credit for our test messages. + /// + /// 2 XCM instructions is for simple `Trap(42)` program, coming through bridge + /// (it is prepended with `UniversalOrigin` instruction). + pub const WeightCredit: Weight = Weight::from_ref_time(BASE_XCM_WEIGHT_TWICE); +} + +/// Message payload for Rialto -> Millau messages. +pub type ToMillauMessagePayload = messages::source::FromThisChainMessagePayload; + +/// Message verifier for Rialto -> Millau messages. +pub type ToMillauMessageVerifier = + messages::source::FromThisChainMessageVerifier; + +/// Message payload for Millau -> Rialto messages. +pub type FromMillauMessagePayload = messages::target::FromBridgedChainMessagePayload; + +/// Call-dispatch based message dispatch for Millau -> Rialto messages. +pub type FromMillauMessageDispatch = messages::target::FromBridgedChainMessageDispatch< + WithMillauMessageBridge, + xcm_executor::XcmExecutor, + crate::xcm_config::XcmWeigher, + WeightCredit, +>; + +/// Messages proof for Millau -> Rialto messages. +pub type FromMillauMessagesProof = messages::target::FromBridgedChainMessagesProof; + +/// Messages delivery proof for Rialto -> Millau messages. +pub type ToMillauMessagesDeliveryProof = + messages::source::FromBridgedChainMessagesDeliveryProof; + +/// Maximal outbound payload size of Rialto -> Millau messages. +pub type ToMillauMaximalOutboundPayloadSize = + messages::source::FromThisChainMaximalOutboundPayloadSize; + +/// Millau <-> Rialto message bridge. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct WithMillauMessageBridge; + +impl MessageBridge for WithMillauMessageBridge { + const THIS_CHAIN_ID: ChainId = RIALTO_CHAIN_ID; + const BRIDGED_CHAIN_ID: ChainId = MILLAU_CHAIN_ID; + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = bp_rialto::WITH_RIALTO_MESSAGES_PALLET_NAME; + + type ThisChain = Rialto; + type BridgedChain = Millau; + type BridgedHeaderChain = + pallet_bridge_grandpa::GrandpaChainHeaders; +} + +/// Rialto chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct Rialto; + +impl messages::UnderlyingChainProvider for Rialto { + type Chain = bp_rialto::Rialto; +} + +impl messages::ThisChainWithMessages for Rialto { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + + fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool { + true + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + MessageNonce::MAX + } +} + +/// Millau chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct Millau; + +impl messages::UnderlyingChainProvider for Millau { + type Chain = bp_millau::Millau; +} + +impl messages::BridgedChainWithMessages for Millau { + fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { + true + } +} + +impl TargetHeaderChain for Millau { + type Error = &'static str; + // The proof is: + // - hash of the header this proof has been created with; + // - the storage proof of one or several keys; + // - id of the lane we prove state of. + type MessagesDeliveryProof = ToMillauMessagesDeliveryProof; + + fn verify_message(payload: &ToMillauMessagePayload) -> Result<(), Self::Error> { + messages::source::verify_chain_message::(payload) + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error> { + messages::source::verify_messages_delivery_proof::(proof) + } +} + +impl SourceHeaderChain for Millau { + type Error = &'static str; + // The proof is: + // - hash of the header this proof has been created with; + // - the storage proof of one or several keys; + // - id of the lane we prove messages for; + // - inclusive range of messages nonces that are proved. + type MessagesProof = FromMillauMessagesProof; + + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result, Self::Error> { + messages::target::verify_messages_proof::(proof, messages_count) + .map_err(Into::into) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{MillauGrandpaInstance, Runtime, WithMillauMessagesInstance}; + use bp_runtime::Chain; + use bridge_runtime_common::{ + assert_complete_bridge_types, + integrity::{ + assert_complete_bridge_constants, AssertBridgeMessagesPalletConstants, + AssertBridgePalletNames, AssertChainConstants, AssertCompleteBridgeConstants, + }, + }; + + #[test] + fn ensure_rialto_message_lane_weights_are_correct() { + type Weights = pallet_bridge_messages::weights::BridgeWeight; + + pallet_bridge_messages::ensure_weights_are_correct::(); + + let max_incoming_message_proof_size = bp_millau::EXTRA_STORAGE_PROOF_SIZE.saturating_add( + messages::target::maximal_incoming_message_size(bp_rialto::Rialto::max_extrinsic_size()), + ); + pallet_bridge_messages::ensure_able_to_receive_message::( + bp_rialto::Rialto::max_extrinsic_size(), + bp_rialto::Rialto::max_extrinsic_weight(), + max_incoming_message_proof_size, + messages::target::maximal_incoming_message_dispatch_weight( + bp_rialto::Rialto::max_extrinsic_weight(), + ), + ); + + let max_incoming_inbound_lane_data_proof_size = + bp_messages::InboundLaneData::<()>::encoded_size_hint_u32( + bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as _, + ); + pallet_bridge_messages::ensure_able_to_receive_confirmation::( + bp_rialto::Rialto::max_extrinsic_size(), + bp_rialto::Rialto::max_extrinsic_weight(), + max_incoming_inbound_lane_data_proof_size, + bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + bp_rialto::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + ); + } + + #[test] + fn ensure_bridge_integrity() { + assert_complete_bridge_types!( + runtime: Runtime, + with_bridged_chain_grandpa_instance: MillauGrandpaInstance, + with_bridged_chain_messages_instance: WithMillauMessagesInstance, + bridge: WithMillauMessageBridge, + this_chain: bp_rialto::Rialto, + bridged_chain: bp_millau::Millau, + ); + + assert_complete_bridge_constants::< + Runtime, + MillauGrandpaInstance, + WithMillauMessagesInstance, + WithMillauMessageBridge, + bp_rialto::Rialto, + >(AssertCompleteBridgeConstants { + this_chain_constants: AssertChainConstants { + block_length: bp_rialto::BlockLength::get(), + block_weights: bp_rialto::BlockWeights::get(), + }, + messages_pallet_constants: AssertBridgeMessagesPalletConstants { + max_unrewarded_relayers_in_bridged_confirmation_tx: + bp_millau::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + max_unconfirmed_messages_in_bridged_confirmation_tx: + bp_millau::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + bridged_chain_id: bp_runtime::MILLAU_CHAIN_ID, + }, + pallet_names: AssertBridgePalletNames { + with_this_chain_messages_pallet_name: bp_rialto::WITH_RIALTO_MESSAGES_PALLET_NAME, + with_bridged_chain_grandpa_pallet_name: bp_millau::WITH_MILLAU_GRANDPA_PALLET_NAME, + with_bridged_chain_messages_pallet_name: + bp_millau::WITH_MILLAU_MESSAGES_PALLET_NAME, + }, + }); + } +} diff --git a/bin/rialto/runtime/src/parachains.rs b/bin/rialto/runtime/src/parachains.rs new file mode 100644 index 00000000000..960931a0b3c --- /dev/null +++ b/bin/rialto/runtime/src/parachains.rs @@ -0,0 +1,210 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Parachains support in Rialto runtime. + +use crate::{ + AccountId, Babe, Balance, Balances, BlockNumber, Registrar, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, ShiftSessionManager, Slots, UncheckedExtrinsic, +}; + +use frame_support::{parameter_types, traits::KeyOwnerProofSystem, weights::Weight}; +use frame_system::EnsureRoot; +use polkadot_primitives::v2::{ValidatorId, ValidatorIndex}; +use polkadot_runtime_common::{paras_registrar, paras_sudo_wrapper, slots}; +use polkadot_runtime_parachains::{ + configuration as parachains_configuration, disputes as parachains_disputes, + disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, + inclusion as parachains_inclusion, initializer as parachains_initializer, + origin as parachains_origin, paras as parachains_paras, + paras_inherent as parachains_paras_inherent, scheduler as parachains_scheduler, + session_info as parachains_session_info, shared as parachains_shared, ump as parachains_ump, +}; +use sp_core::crypto::KeyTypeId; +use sp_runtime::transaction_validity::TransactionPriority; + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = RuntimeCall; +} + +/// Special `RewardValidators` that does nothing ;) +pub struct RewardValidators; +impl polkadot_runtime_parachains::inclusion::RewardValidators for RewardValidators { + fn reward_backing(_: impl IntoIterator) {} + fn reward_bitfields(_: impl IntoIterator) {} +} + +// all required parachain modules from `polkadot-runtime-parachains` crate + +impl parachains_configuration::Config for Runtime { + type WeightInfo = parachains_configuration::TestWeightInfo; +} + +impl parachains_dmp::Config for Runtime {} + +impl parachains_hrmp::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type Currency = Balances; + type WeightInfo = parachains_hrmp::TestWeightInfo; +} + +impl parachains_inclusion::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RewardValidators = RewardValidators; + type DisputesHandler = (); +} + +impl parachains_initializer::Config for Runtime { + type Randomness = pallet_babe::RandomnessFromOneEpochAgo; + type ForceOrigin = EnsureRoot; + type WeightInfo = (); +} + +impl parachains_disputes::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RewardValidators = (); + type SlashingHandler = (); + type WeightInfo = parachains_disputes::TestWeightInfo; +} + +impl parachains_slashing::Config for Runtime { + type KeyOwnerProofSystem = (); + type KeyOwnerProof = + >::Proof; + type KeyOwnerIdentification = >::IdentificationTuple; + type HandleReports = (); + type WeightInfo = parachains_slashing::TestWeightInfo; + type BenchmarkingConfig = parachains_slashing::BenchConfig<200>; +} + +impl parachains_origin::Config for Runtime {} + +parameter_types! { + pub const ParasUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); +} + +impl parachains_paras::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = parachains_paras::TestWeightInfo; + type UnsignedPriority = ParasUnsignedPriority; + type NextSessionRotation = Babe; +} + +impl parachains_paras_inherent::Config for Runtime { + type WeightInfo = parachains_paras_inherent::TestWeightInfo; +} + +impl parachains_scheduler::Config for Runtime {} + +impl parachains_session_info::Config for Runtime { + type ValidatorSet = ShiftSessionManager; +} + +impl parachains_shared::Config for Runtime {} + +parameter_types! { + pub const FirstMessageFactorPercent: u64 = 100; +} + +impl parachains_ump::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type UmpSink = (); + type FirstMessageFactorPercent = FirstMessageFactorPercent; + type ExecuteOverweightOrigin = EnsureRoot; + type WeightInfo = parachains_ump::TestWeightInfo; +} + +// required onboarding pallets. We're not going to use auctions or crowdloans, so they're missing + +parameter_types! { + pub const ParaDeposit: Balance = 0; + pub const DataDepositPerByte: Balance = 0; +} + +impl paras_registrar::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type Currency = Balances; + type OnSwap = Slots; + type ParaDeposit = ParaDeposit; + type DataDepositPerByte = DataDepositPerByte; + type WeightInfo = paras_registrar::TestWeightInfo; +} + +parameter_types! { + pub const LeasePeriod: BlockNumber = 10 * bp_rialto::MINUTES; +} + +impl slots::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type Registrar = Registrar; + type LeasePeriod = LeasePeriod; + type WeightInfo = slots::TestWeightInfo; + type LeaseOffset = (); + type ForceOrigin = EnsureRoot; +} + +impl paras_sudo_wrapper::Config for Runtime {} + +pub struct ZeroWeights; + +impl polkadot_runtime_common::paras_registrar::WeightInfo for ZeroWeights { + fn reserve() -> Weight { + Weight::from_ref_time(0) + } + fn register() -> Weight { + Weight::from_ref_time(0) + } + fn force_register() -> Weight { + Weight::from_ref_time(0) + } + fn deregister() -> Weight { + Weight::from_ref_time(0) + } + fn swap() -> Weight { + Weight::from_ref_time(0) + } + fn schedule_code_upgrade(_: u32) -> Weight { + Weight::from_ref_time(0) + } + fn set_current_head(_: u32) -> Weight { + Weight::from_ref_time(0) + } +} + +impl polkadot_runtime_common::slots::WeightInfo for ZeroWeights { + fn force_lease() -> Weight { + Weight::from_ref_time(0) + } + fn manage_lease_period_start(_c: u32, _t: u32) -> Weight { + Weight::from_ref_time(0) + } + fn clear_all_leases() -> Weight { + Weight::from_ref_time(0) + } + fn trigger_onboard() -> Weight { + Weight::from_ref_time(0) + } +} diff --git a/bin/rialto/runtime/src/xcm_config.rs b/bin/rialto/runtime/src/xcm_config.rs new file mode 100644 index 00000000000..895522655d2 --- /dev/null +++ b/bin/rialto/runtime/src/xcm_config.rs @@ -0,0 +1,285 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! XCM configurations for the Rialto runtime. + +use super::{ + millau_messages::WithMillauMessageBridge, AccountId, AllPalletsWithSystem, Balances, Runtime, + RuntimeCall, RuntimeEvent, RuntimeOrigin, WithMillauMessagesInstance, XcmPallet, +}; +use bp_rialto::WeightToFee; +use bridge_runtime_common::{ + messages::source::{XcmBridge, XcmBridgeAdapter}, + CustomNetworkId, +}; +use frame_support::{ + parameter_types, + traits::{Everything, Nothing}, +}; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, + CurrencyAdapter as XcmCurrencyAdapter, IsConcrete, MintLocation, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, +}; + +parameter_types! { + /// The location of the `MLAU` token, from the context of this chain. Since this token is native to this + /// chain, we make it synonymous with it and thus it is the `Here` location, which means "equivalent to + /// the context". + pub const TokenLocation: MultiLocation = Here.into_location(); + /// The Rialto network ID. + pub const ThisNetwork: NetworkId = CustomNetworkId::Rialto.as_network_id(); + /// The Millau network ID. + pub const MillauNetwork: NetworkId = CustomNetworkId::Millau.as_network_id(); + + /// Our XCM location ancestry - i.e. our location within the Consensus Universe. + /// + /// Since Polkadot is a top-level relay-chain with its own consensus, it's just our network ID. + pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); + /// The check account, which holds any native assets that have been teleported out and not back in (yet). + pub CheckAccount: (AccountId, MintLocation) = (XcmPallet::check_account(), MintLocation::Local); +} + +/// The canonical means of converting a `MultiLocation` into an `AccountId`, used when we want to +/// determine the sovereign account controlled by a location. +pub type SovereignAccountOf = ( + // We can directly alias an `AccountId32` into a local account. + AccountId32Aliases, +); + +/// Our asset transactor. This is what allows us to interest with the runtime facilities from the +/// point of view of XCM-only concepts like `MultiLocation` and `MultiAsset`. +/// +/// Ours is only aware of the Balances pallet, which is mapped to `TokenLocation`. +pub type LocalAssetTransactor = XcmCurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // We can convert the MultiLocations with our converter above: + SovereignAccountOf, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We track our teleports in/out to keep total issuance correct. + CheckAccount, +>; + +/// The means that we convert the XCM message origin location into a local dispatch origin. +type LocalOriginConverter = ( + // A `Signed` origin of the sovereign account that the original location controls. + SovereignSignedViaLocation, + // The AccountId32 location type can be expressed natively as a `Signed` origin. + SignedAccountId32AsNative, +); + +/// The amount of weight an XCM operation takes. This is a safe overestimate. +pub const BASE_XCM_WEIGHT: u64 = 1_000_000_000; + +parameter_types! { + /// The amount of weight an XCM operation takes. This is a safe overestimate. + pub const BaseXcmWeight: u64 = BASE_XCM_WEIGHT; + /// Maximum number of instructions in a single XCM fragment. A sanity check against weight + /// calculations getting too crazy. + pub const MaxInstructions: u32 = 100; +} + +/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our +/// individual routers. +pub type XcmRouter = ( + // Router to send messages to Millau. + XcmBridgeAdapter, +); + +parameter_types! { + pub const MaxAssetsIntoHolding: u32 = 64; +} + +/// The barriers one of which must be passed for an XCM message to be executed. +pub type Barrier = ( + // Weight that is paid for may be consumed. + TakeWeightCredit, + // If the message is one that immediately attemps to pay for execution, then allow it. + AllowTopLevelPaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, +); + +/// Incoming XCM weigher type. +pub type XcmWeigher = xcm_builder::FixedWeightBounds; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = XcmWeigher; + // The weight trader piggybacks on the existing transaction-fee conversion logic. + type Trader = UsingComponents; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; +} + +/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior +/// location of this chain. +pub type LocalOriginToLocation = ( + // Usual Signed origin to be used in XCM as a corresponding AccountId32 + SignedToAccountId32, +); + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + // We don't allow any messages to be sent via the transaction yet. This is basically safe to + // enable, (safe the possibility of someone spamming the parachain if they're willing to pay + // the DOT to send from the Relay-chain). But it's useless until we bring in XCM v3 which will + // make `DescendOrigin` a bit more useful. + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // Anyone can execute XCM messages locally. + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = xcm_executor::XcmExecutor; + // Anyone is able to use teleportation regardless of who they are and what they want to + // teleport. + type XcmTeleportFilter = Everything; + // Anyone is able to use reserve transfers regardless of who they are and what they want to + // transfer. + type XcmReserveTransferFilter = Everything; + type Weigher = XcmWeigher; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = frame_support::traits::ConstU32<8>; +} + +/// With-Millau bridge. +pub struct ToMillauBridge; + +impl XcmBridge for ToMillauBridge { + type MessageBridge = WithMillauMessageBridge; + type MessageSender = pallet_bridge_messages::Pallet; + + fn universal_location() -> InteriorMultiLocation { + UniversalLocation::get() + } + + fn verify_destination(dest: &MultiLocation) -> bool { + matches!(*dest, MultiLocation { parents: 1, interior: X1(GlobalConsensus(r)) } if r == MillauNetwork::get()) + } + + fn build_destination() -> MultiLocation { + let dest: InteriorMultiLocation = MillauNetwork::get().into(); + let here = UniversalLocation::get(); + dest.relative_to(&here) + } + + fn xcm_lane() -> bp_messages::LaneId { + [0, 0, 0, 0] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::millau_messages::WeightCredit; + use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, + MessageKey, + }; + use bp_runtime::messages::MessageDispatchResult; + use bridge_runtime_common::messages::target::FromBridgedChainMessageDispatch; + use codec::Encode; + + fn new_test_ext() -> sp_io::TestExternalities { + sp_io::TestExternalities::new( + frame_system::GenesisConfig::default().build_storage::().unwrap(), + ) + } + + #[test] + fn xcm_messages_to_millau_are_sent() { + new_test_ext().execute_with(|| { + // the encoded message (origin ++ xcm) is 0x010109030419A8 + let dest = (Parent, X1(GlobalConsensus(MillauNetwork::get()))); + let xcm: Xcm<()> = vec![Instruction::Trap(42)].into(); + + let send_result = send_xcm::(dest.into(), xcm); + let expected_fee = MultiAssets::from((Here, 1_000_000_u128)); + let expected_hash = + ([0u8, 0u8, 0u8, 0u8], 1u64).using_encoded(sp_io::hashing::blake2_256); + assert_eq!(send_result, Ok((expected_hash, expected_fee)),); + }) + } + + #[test] + fn xcm_messages_from_millau_are_dispatched() { + type XcmExecutor = xcm_executor::XcmExecutor; + type MessageDispatcher = FromBridgedChainMessageDispatch< + WithMillauMessageBridge, + XcmExecutor, + XcmWeigher, + WeightCredit, + >; + + new_test_ext().execute_with(|| { + let location: MultiLocation = + (Parent, X1(GlobalConsensus(MillauNetwork::get()))).into(); + let xcm: Xcm = vec![Instruction::Trap(42)].into(); + + let mut incoming_message = DispatchMessage { + key: MessageKey { lane_id: [0, 0, 0, 0], nonce: 1 }, + data: DispatchMessageData { payload: Ok((location, xcm).into()) }, + }; + + let dispatch_weight = MessageDispatcher::dispatch_weight(&mut incoming_message); + assert_eq!( + dispatch_weight, + frame_support::weights::Weight::from_ref_time(1_000_000_000) + ); + + let dispatch_result = + MessageDispatcher::dispatch(&AccountId::from([0u8; 32]), incoming_message); + assert_eq!( + dispatch_result, + MessageDispatchResult { + unspent_weight: frame_support::weights::Weight::from_ref_time(0), + dispatch_fee_paid_during_dispatch: false, + } + ); + }) + } +} diff --git a/bin/runtime-common/Cargo.toml b/bin/runtime-common/Cargo.toml new file mode 100644 index 00000000000..4f8f7e34c40 --- /dev/null +++ b/bin/runtime-common/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = "bridge-runtime-common" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +repository = "https://github.com/paritytech/parity-bridges-common/" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } +hash-db = { version = "0.15.2", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +static_assertions = { version = "1.1", optional = true } + +# Bridge dependencies + +bp-header-chain = { path = "../../primitives/header-chain", default-features = false } +bp-messages = { path = "../../primitives/messages", default-features = false } +bp-parachains = { path = "../../primitives/parachains", default-features = false } +bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } +pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false } +pallet-bridge-messages = { path = "../../modules/messages", default-features = false } +pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false } + +# Substrate dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Polkadot dependencies +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } + +[dev-dependencies] +millau-runtime = { path = "../millau/runtime" } + +[features] +default = ["std"] +std = [ + "bp-header-chain/std", + "bp-messages/std", + "bp-parachains/std", + "bp-polkadot-core/std", + "bp-runtime/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "hash-db/std", + "log/std", + "pallet-bridge-grandpa/std", + "pallet-bridge-messages/std", + "pallet-bridge-parachains/std", + "pallet-xcm/std", + "scale-info/std", + "sp-api/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", +] +runtime-benchmarks = [ + "pallet-balances", + "pallet-bridge-grandpa/runtime-benchmarks", + "pallet-bridge-messages/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] +integrity-test = [ + "static_assertions", +] diff --git a/bin/runtime-common/src/integrity.rs b/bin/runtime-common/src/integrity.rs new file mode 100644 index 00000000000..4c69a29b821 --- /dev/null +++ b/bin/runtime-common/src/integrity.rs @@ -0,0 +1,291 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Integrity tests for chain constants and pallets configuration. +//! +//! Most of the tests in this module assume that the bridge is using standard (see `crate::messages` +//! module for details) configuration. + +use crate::messages::MessageBridge; + +use bp_messages::MessageNonce; +use bp_runtime::{Chain, ChainId}; +use codec::Encode; +use frame_support::{storage::generator::StorageValue, traits::Get}; +use frame_system::limits; + +/// Macro that ensures that the runtime configuration and chain primitives crate are sharing +/// the same types (index, block number, hash, hasher, account id and header). +#[macro_export] +macro_rules! assert_chain_types( + ( runtime: $r:path, this_chain: $this:path ) => { + { + // if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard + // configuration is used), or something has broke existing configuration (meaning that all bridged chains + // and relays will stop functioning) + use frame_system::Config as SystemConfig; + use static_assertions::assert_type_eq_all; + + assert_type_eq_all!(<$r as SystemConfig>::Index, bp_runtime::IndexOf<$this>); + assert_type_eq_all!(<$r as SystemConfig>::BlockNumber, bp_runtime::BlockNumberOf<$this>); + assert_type_eq_all!(<$r as SystemConfig>::Hash, bp_runtime::HashOf<$this>); + assert_type_eq_all!(<$r as SystemConfig>::Hashing, bp_runtime::HasherOf<$this>); + assert_type_eq_all!(<$r as SystemConfig>::AccountId, bp_runtime::AccountIdOf<$this>); + assert_type_eq_all!(<$r as SystemConfig>::Header, bp_runtime::HeaderOf<$this>); + } + } +); + +/// Macro that ensures that the bridge GRANDPA pallet is configured properly to bridge with given +/// chain. +#[macro_export] +macro_rules! assert_bridge_grandpa_pallet_types( + ( runtime: $r:path, with_bridged_chain_grandpa_instance: $i:path, bridged_chain: $bridged:path ) => { + { + // if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard + // configuration is used), or something has broke existing configuration (meaning that all bridged chains + // and relays will stop functioning) + use pallet_bridge_grandpa::Config as GrandpaConfig; + use static_assertions::assert_type_eq_all; + + assert_type_eq_all!(<$r as GrandpaConfig<$i>>::BridgedChain, $bridged); + } + } +); + +/// Macro that ensures that the bridge messages pallet is configured properly to bridge using given +/// configuration. +#[macro_export] +macro_rules! assert_bridge_messages_pallet_types( + ( + runtime: $r:path, + with_bridged_chain_messages_instance: $i:path, + bridge: $bridge:path + ) => { + { + // if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard + // configuration is used), or something has broke existing configuration (meaning that all bridged chains + // and relays will stop functioning) + use $crate::messages::{ + source::FromThisChainMessagePayload, + target::FromBridgedChainMessagePayload, + AccountIdOf, BalanceOf, BridgedChain, CallOf, ThisChain, + }; + use pallet_bridge_messages::Config as MessagesConfig; + use static_assertions::assert_type_eq_all; + + assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, FromThisChainMessagePayload); + + assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundPayload, FromBridgedChainMessagePayload>>); + assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundRelayer, AccountIdOf>); + + assert_type_eq_all!(<$r as MessagesConfig<$i>>::TargetHeaderChain, BridgedChain<$bridge>); + assert_type_eq_all!(<$r as MessagesConfig<$i>>::SourceHeaderChain, BridgedChain<$bridge>); + } + } +); + +/// Macro that combines four other macro calls - `assert_chain_types`, `assert_bridge_types`, +/// `assert_bridge_grandpa_pallet_types` and `assert_bridge_messages_pallet_types`. It may be used +/// at the chain that is implemeting complete standard messages bridge (i.e. with bridge GRANDPA and +/// messages pallets deployed). +#[macro_export] +macro_rules! assert_complete_bridge_types( + ( + runtime: $r:path, + with_bridged_chain_grandpa_instance: $gi:path, + with_bridged_chain_messages_instance: $mi:path, + bridge: $bridge:path, + this_chain: $this:path, + bridged_chain: $bridged:path, + ) => { + $crate::assert_chain_types!(runtime: $r, this_chain: $this); + $crate::assert_bridge_grandpa_pallet_types!( + runtime: $r, + with_bridged_chain_grandpa_instance: $gi, + bridged_chain: $bridged + ); + $crate::assert_bridge_messages_pallet_types!( + runtime: $r, + with_bridged_chain_messages_instance: $mi, + bridge: $bridge + ); + } +); + +/// Parameters for asserting chain-related constants. +#[derive(Debug)] +pub struct AssertChainConstants { + /// Block length limits of the chain. + pub block_length: limits::BlockLength, + /// Block weight limits of the chain. + pub block_weights: limits::BlockWeights, +} + +/// Test that our hardcoded, chain-related constants, are matching chain runtime configuration. +/// +/// In particular, this test ensures that: +/// +/// 1) block weight limits are matching; +/// 2) block size limits are matching. +pub fn assert_chain_constants(params: AssertChainConstants) +where + R: frame_system::Config, + C: Chain, +{ + // we don't check runtime version here, because in our case we'll be building relay from one + // repo and runtime will live in another repo, along with outdated relay version. To avoid + // unneeded commits, let's not raise an error in case of version mismatch. + + // if one of following assert fails, it means that we may need to upgrade bridged chain and + // relay to use updated constants. If constants are now smaller than before, it may lead to + // undeliverable messages. + + // `BlockLength` struct is not implementing `PartialEq`, so we compare encoded values here. + assert_eq!( + R::BlockLength::get().encode(), + params.block_length.encode(), + "BlockLength from runtime ({:?}) differ from hardcoded: {:?}", + R::BlockLength::get(), + params.block_length, + ); + // `BlockWeights` struct is not implementing `PartialEq`, so we compare encoded values here + assert_eq!( + R::BlockWeights::get().encode(), + params.block_weights.encode(), + "BlockWeights from runtime ({:?}) differ from hardcoded: {:?}", + R::BlockWeights::get(), + params.block_weights, + ); +} + +/// Test that the constants, used in GRANDPA pallet configuration are valid. +pub fn assert_bridge_grandpa_pallet_constants() +where + R: pallet_bridge_grandpa::Config, + GI: 'static, +{ + assert!( + R::MaxRequests::get() > 0, + "MaxRequests ({}) must be larger than zero", + R::MaxRequests::get(), + ); +} + +/// Parameters for asserting messages pallet constants. +#[derive(Debug)] +pub struct AssertBridgeMessagesPalletConstants { + /// Maximal number of unrewarded relayer entries in a confirmation transaction at the bridged + /// chain. + pub max_unrewarded_relayers_in_bridged_confirmation_tx: MessageNonce, + /// Maximal number of unconfirmed messages in a confirmation transaction at the bridged chain. + pub max_unconfirmed_messages_in_bridged_confirmation_tx: MessageNonce, + /// Identifier of the bridged chain. + pub bridged_chain_id: ChainId, +} + +/// Test that the constants, used in messages pallet configuration are valid. +pub fn assert_bridge_messages_pallet_constants(params: AssertBridgeMessagesPalletConstants) +where + R: pallet_bridge_messages::Config, + MI: 'static, +{ + assert!( + !R::ActiveOutboundLanes::get().is_empty(), + "ActiveOutboundLanes ({:?}) must not be empty", + R::ActiveOutboundLanes::get(), + ); + assert!( + R::MaxUnrewardedRelayerEntriesAtInboundLane::get() <= params.max_unrewarded_relayers_in_bridged_confirmation_tx, + "MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}", + R::MaxUnrewardedRelayerEntriesAtInboundLane::get(), + params.max_unrewarded_relayers_in_bridged_confirmation_tx, + ); + assert!( + R::MaxUnconfirmedMessagesAtInboundLane::get() <= params.max_unconfirmed_messages_in_bridged_confirmation_tx, + "MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}", + R::MaxUnconfirmedMessagesAtInboundLane::get(), + params.max_unconfirmed_messages_in_bridged_confirmation_tx, + ); + assert_eq!(R::BridgedChainId::get(), params.bridged_chain_id); +} + +/// Parameters for asserting bridge pallet names. +#[derive(Debug)] +pub struct AssertBridgePalletNames<'a> { + /// Name of the messages pallet, deployed at the bridged chain and used to bridge with this + /// chain. + pub with_this_chain_messages_pallet_name: &'a str, + /// Name of the GRANDPA pallet, deployed at this chain and used to bridge with the bridged + /// chain. + pub with_bridged_chain_grandpa_pallet_name: &'a str, + /// Name of the messages pallet, deployed at this chain and used to bridge with the bridged + /// chain. + pub with_bridged_chain_messages_pallet_name: &'a str, +} + +/// Tests that bridge pallet names used in `construct_runtime!()` macro call are matching constants +/// from chain primitives crates. +pub fn assert_bridge_pallet_names(params: AssertBridgePalletNames) +where + B: MessageBridge, + R: pallet_bridge_grandpa::Config + pallet_bridge_messages::Config, + GI: 'static, + MI: 'static, +{ + assert_eq!(B::BRIDGED_MESSAGES_PALLET_NAME, params.with_this_chain_messages_pallet_name); + assert_eq!( + pallet_bridge_grandpa::PalletOwner::::storage_value_final_key().to_vec(), + bp_runtime::storage_value_key(params.with_bridged_chain_grandpa_pallet_name, "PalletOwner",).0, + ); + assert_eq!( + pallet_bridge_messages::PalletOwner::::storage_value_final_key().to_vec(), + bp_runtime::storage_value_key( + params.with_bridged_chain_messages_pallet_name, + "PalletOwner", + ) + .0, + ); +} + +/// Parameters for asserting complete standard messages bridge. +#[derive(Debug)] +pub struct AssertCompleteBridgeConstants<'a> { + /// Parameters to assert this chain constants. + pub this_chain_constants: AssertChainConstants, + /// Parameters to assert messages pallet constants. + pub messages_pallet_constants: AssertBridgeMessagesPalletConstants, + /// Parameters to assert pallet names constants. + pub pallet_names: AssertBridgePalletNames<'a>, +} + +/// All bridge-related constants tests for the complete standard messages bridge (i.e. with bridge +/// GRANDPA and messages pallets deployed). +pub fn assert_complete_bridge_constants(params: AssertCompleteBridgeConstants) +where + R: frame_system::Config + + pallet_bridge_grandpa::Config + + pallet_bridge_messages::Config, + GI: 'static, + MI: 'static, + B: MessageBridge, + This: Chain, +{ + assert_chain_constants::(params.this_chain_constants); + assert_bridge_grandpa_pallet_constants::(); + assert_bridge_messages_pallet_constants::(params.messages_pallet_constants); + assert_bridge_pallet_names::(params.pallet_names); +} diff --git a/bin/runtime-common/src/lib.rs b/bin/runtime-common/src/lib.rs new file mode 100644 index 00000000000..ca8f2268404 --- /dev/null +++ b/bin/runtime-common/src/lib.rs @@ -0,0 +1,220 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Common types/functions that may be used by runtimes of all bridged chains. + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_runtime::FilterCall; +use sp_runtime::transaction_validity::TransactionValidity; +use xcm::v3::NetworkId; + +pub mod messages; +pub mod messages_api; +pub mod messages_benchmarking; +pub mod messages_extension; +pub mod parachains_benchmarking; + +mod messages_generation; + +#[cfg(feature = "integrity-test")] +pub mod integrity; + +/// A duplication of the `FilterCall` trait. +/// +/// We need this trait in order to be able to implement it for the messages pallet, +/// since the implementation is done outside of the pallet crate. +pub trait BridgeRuntimeFilterCall { + /// Checks if a runtime call is valid. + fn validate(call: &Call) -> TransactionValidity; +} + +impl BridgeRuntimeFilterCall for pallet_bridge_grandpa::Pallet +where + pallet_bridge_grandpa::Pallet: FilterCall, +{ + fn validate(call: &Call) -> TransactionValidity { + as FilterCall>::validate(call) + } +} + +impl BridgeRuntimeFilterCall for pallet_bridge_parachains::Pallet +where + pallet_bridge_parachains::Pallet: FilterCall, +{ + fn validate(call: &Call) -> TransactionValidity { + as FilterCall>::validate(call) + } +} + +/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension. +/// +/// ## Example +/// +/// ```nocompile +/// generate_bridge_reject_obsolete_headers_and_messages!{ +/// Call, AccountId +/// BridgeRialtoGrandpa, BridgeWestendGrandpa, +/// BridgeRialtoParachains +/// } +/// ``` +/// +/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged +/// headers and messages. Without that extension, even honest relayers may lose their funds if +/// there are multiple relays running and submitting the same information. +#[macro_export] +macro_rules! generate_bridge_reject_obsolete_headers_and_messages { + ($call:ty, $account_id:ty, $($filter_call:ty),*) => { + #[derive(Clone, codec::Decode, codec::Encode, Eq, PartialEq, frame_support::RuntimeDebug, scale_info::TypeInfo)] + pub struct BridgeRejectObsoleteHeadersAndMessages; + impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages { + const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages"; + type AccountId = $account_id; + type Call = $call; + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed(&self) -> sp_std::result::Result< + (), + sp_runtime::transaction_validity::TransactionValidityError, + > { + Ok(()) + } + + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> sp_runtime::transaction_validity::TransactionValidity { + let valid = sp_runtime::transaction_validity::ValidTransaction::default(); + $( + let valid = valid + .combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?); + )* + Ok(valid) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &sp_runtime::traits::DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(drop) + } + } + }; +} + +/// A mapping over `NetworkId`. +/// Since `NetworkId` doesn't include `Millau`, `Rialto` and `RialtoParachain`, we create some +/// synthetic associations between these chains and `NetworkId` chains. +pub enum CustomNetworkId { + /// The Millau network ID, associated with Kusama. + Millau, + /// The Rialto network ID, associated with Polkadot. + Rialto, + /// The RialtoParachain network ID, associated with Westend. + RialtoParachain, +} + +impl CustomNetworkId { + pub const fn as_network_id(&self) -> NetworkId { + match *self { + CustomNetworkId::Millau => NetworkId::Kusama, + CustomNetworkId::Rialto => NetworkId::Polkadot, + CustomNetworkId::RialtoParachain => NetworkId::Westend, + } + } +} + +#[cfg(test)] +mod tests { + use crate::BridgeRuntimeFilterCall; + use frame_support::{assert_err, assert_ok}; + use sp_runtime::{ + traits::SignedExtension, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + }; + + pub struct MockCall { + data: u32, + } + + impl sp_runtime::traits::Dispatchable for MockCall { + type RuntimeOrigin = (); + type Config = (); + type Info = (); + type PostInfo = (); + + fn dispatch( + self, + _origin: Self::RuntimeOrigin, + ) -> sp_runtime::DispatchResultWithInfo { + unimplemented!() + } + } + + struct FirstFilterCall; + impl BridgeRuntimeFilterCall for FirstFilterCall { + fn validate(call: &MockCall) -> TransactionValidity { + if call.data <= 1 { + return InvalidTransaction::Custom(1).into() + } + + Ok(ValidTransaction { priority: 1, ..Default::default() }) + } + } + + struct SecondFilterCall; + impl BridgeRuntimeFilterCall for SecondFilterCall { + fn validate(call: &MockCall) -> TransactionValidity { + if call.data <= 2 { + return InvalidTransaction::Custom(2).into() + } + + Ok(ValidTransaction { priority: 2, ..Default::default() }) + } + } + + #[test] + fn test() { + generate_bridge_reject_obsolete_headers_and_messages!( + MockCall, + (), + FirstFilterCall, + SecondFilterCall + ); + + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0), + InvalidTransaction::Custom(1) + ); + + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0), + InvalidTransaction::Custom(2) + ); + + assert_ok!( + BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0), + ValidTransaction { priority: 3, ..Default::default() } + ) + } +} diff --git a/bin/runtime-common/src/messages.rs b/bin/runtime-common/src/messages.rs new file mode 100644 index 00000000000..696d059cf63 --- /dev/null +++ b/bin/runtime-common/src/messages.rs @@ -0,0 +1,1202 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types that allow runtime to act as a source/target endpoint of message lanes. +//! +//! Messages are assumed to be encoded `Call`s of the target chain. Call-dispatch +//! pallet is used to dispatch incoming messages. Message identified by a tuple +//! of to elements - message lane id and message nonce. + +use bp_header_chain::{HeaderChain, HeaderChainError}; +use bp_messages::{ + source_chain::LaneMessageVerifier, + target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages}, + InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, +}; +use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Size, StorageProofChecker}; +use codec::{Decode, DecodeLimit, Encode}; +use frame_support::{traits::Get, weights::Weight, RuntimeDebug}; +use hash_db::Hasher; +use scale_info::TypeInfo; +use sp_std::{convert::TryFrom, fmt::Debug, marker::PhantomData, vec::Vec}; +use sp_trie::StorageProof; +use xcm::latest::prelude::*; + +/// Bidirectional message bridge. +pub trait MessageBridge { + /// Identifier of this chain. + const THIS_CHAIN_ID: ChainId; + /// Identifier of the Bridged chain. + const BRIDGED_CHAIN_ID: ChainId; + /// Name of the paired messages pallet instance at the Bridged chain. + /// + /// Should be the name that is used in the `construct_runtime!()` macro. + const BRIDGED_MESSAGES_PALLET_NAME: &'static str; + + /// This chain in context of message bridge. + type ThisChain: ThisChainWithMessages; + /// Bridged chain in context of message bridge. + type BridgedChain: BridgedChainWithMessages; + /// Bridged header chain. + type BridgedHeaderChain: HeaderChain>; +} + +/// A trait that provides the type of the underlying chain. +pub trait UnderlyingChainProvider { + /// Underlying chain type. + type Chain: Chain; +} + +/// This chain that has `pallet-bridge-messages` module. +pub trait ThisChainWithMessages: UnderlyingChainProvider { + /// Call origin on the chain. + type RuntimeOrigin; + /// Call type on the chain. + type RuntimeCall: Encode + Decode; + + /// Do we accept message sent by given origin to given lane? + fn is_message_accepted(origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool; + + /// Maximal number of pending (not yet delivered) messages at This chain. + /// + /// Any messages over this limit, will be rejected. + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce; +} + +/// Bridged chain that has `pallet-bridge-messages` module. +pub trait BridgedChainWithMessages: UnderlyingChainProvider { + /// Returns `true` if message dispatch weight is withing expected limits. `false` means + /// that the message is too heavy to be sent over the bridge and shall be rejected. + fn verify_dispatch_weight(message_payload: &[u8]) -> bool; +} + +/// This chain in context of message bridge. +pub type ThisChain = ::ThisChain; +/// Bridged chain in context of message bridge. +pub type BridgedChain = ::BridgedChain; +/// Underlying chain type. +pub type UnderlyingChainOf = ::Chain; +/// Hash used on the chain. +pub type HashOf = bp_runtime::HashOf<::Chain>; +/// Hasher used on the chain. +pub type HasherOf = bp_runtime::HasherOf>; +/// Account id used on the chain. +pub type AccountIdOf = bp_runtime::AccountIdOf>; +/// Type of balances that is used on the chain. +pub type BalanceOf = bp_runtime::BalanceOf>; +/// Type of origin that is used on the chain. +pub type OriginOf = ::RuntimeOrigin; +/// Type of call that is used on this chain. +pub type CallOf = ::RuntimeCall; + +/// Raw storage proof type (just raw trie nodes). +pub type RawStorageProof = Vec>; + +/// Sub-module that is declaring types required for processing This -> Bridged chain messages. +pub mod source { + use super::*; + + /// Message payload for This -> Bridged chain messages. + pub type FromThisChainMessagePayload = Vec; + + /// Maximal size of outbound message payload. + pub struct FromThisChainMaximalOutboundPayloadSize(PhantomData); + + impl Get for FromThisChainMaximalOutboundPayloadSize { + fn get() -> u32 { + maximal_message_size::() + } + } + + /// Messages delivery proof from bridged chain: + /// + /// - hash of finalized header; + /// - storage proof of inbound lane state; + /// - lane id. + #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] + pub struct FromBridgedChainMessagesDeliveryProof { + /// Hash of the bridge header the proof is for. + pub bridged_header_hash: BridgedHeaderHash, + /// Storage trie proof generated for [`Self::bridged_header_hash`]. + pub storage_proof: RawStorageProof, + /// Lane id of which messages were delivered and the proof is for. + pub lane: LaneId, + } + + impl Size for FromBridgedChainMessagesDeliveryProof { + fn size(&self) -> u32 { + u32::try_from( + self.storage_proof + .iter() + .fold(0usize, |sum, node| sum.saturating_add(node.len())), + ) + .unwrap_or(u32::MAX) + } + } + + /// 'Parsed' message delivery proof - inbound lane id and its state. + pub type ParsedMessagesDeliveryProofFromBridgedChain = + (LaneId, InboundLaneData>>); + + /// Message verifier that is doing all basic checks. + /// + /// This verifier assumes following: + /// + /// - all message lanes are equivalent, so all checks are the same; + /// + /// Following checks are made: + /// + /// - message is rejected if its lane is currently blocked; + /// - message is rejected if there are too many pending (undelivered) messages at the outbound + /// lane; + /// - check that the sender has rights to dispatch the call on target chain using provided + /// dispatch origin; + /// - check that the sender has paid enough funds for both message delivery and dispatch. + #[derive(RuntimeDebug)] + pub struct FromThisChainMessageVerifier(PhantomData); + + /// The error message returned from LaneMessageVerifier when outbound lane is disabled. + pub const MESSAGE_REJECTED_BY_OUTBOUND_LANE: &str = + "The outbound message lane has rejected the message."; + /// The error message returned from LaneMessageVerifier when too many pending messages at the + /// lane. + pub const TOO_MANY_PENDING_MESSAGES: &str = "Too many pending messages at the lane."; + /// The error message returned from LaneMessageVerifier when call origin is mismatch. + pub const BAD_ORIGIN: &str = "Unable to match the source origin to expected target origin."; + + impl LaneMessageVerifier>, FromThisChainMessagePayload> + for FromThisChainMessageVerifier + where + B: MessageBridge, + // matches requirements from the `frame_system::Config::Origin` + OriginOf>: Clone + + Into>>, OriginOf>>>, + AccountIdOf>: PartialEq + Clone, + { + type Error = &'static str; + + fn verify_message( + submitter: &OriginOf>, + lane: &LaneId, + lane_outbound_data: &OutboundLaneData, + _payload: &FromThisChainMessagePayload, + ) -> Result<(), Self::Error> { + // reject message if lane is blocked + if !ThisChain::::is_message_accepted(submitter, lane) { + return Err(MESSAGE_REJECTED_BY_OUTBOUND_LANE) + } + + // reject message if there are too many pending messages at this lane + let max_pending_messages = ThisChain::::maximal_pending_messages_at_outbound_lane(); + let pending_messages = lane_outbound_data + .latest_generated_nonce + .saturating_sub(lane_outbound_data.latest_received_nonce); + if pending_messages > max_pending_messages { + return Err(TOO_MANY_PENDING_MESSAGES) + } + + Ok(()) + } + } + + /// Return maximal message size of This -> Bridged chain message. + pub fn maximal_message_size() -> u32 { + super::target::maximal_incoming_message_size( + UnderlyingChainOf::>::max_extrinsic_size(), + ) + } + + /// Do basic Bridged-chain specific verification of This -> Bridged chain message. + /// + /// Ok result from this function means that the delivery transaction with this message + /// may be 'mined' by the target chain. But the lane may have its own checks (e.g. fee + /// check) that would reject message (see `FromThisChainMessageVerifier`). + pub fn verify_chain_message( + payload: &FromThisChainMessagePayload, + ) -> Result<(), &'static str> { + if !BridgedChain::::verify_dispatch_weight(payload) { + return Err("Incorrect message weight declared") + } + + // The maximal size of extrinsic at Substrate-based chain depends on the + // `frame_system::Config::MaximumBlockLength` and + // `frame_system::Config::AvailableBlockRatio` constants. This check is here to be sure that + // the lane won't stuck because message is too large to fit into delivery transaction. + // + // **IMPORTANT NOTE**: the delivery transaction contains storage proof of the message, not + // the message itself. The proof is always larger than the message. But unless chain state + // is enormously large, it should be several dozens/hundreds of bytes. The delivery + // transaction also contains signatures and signed extensions. Because of this, we reserve + // 1/3 of the the maximal extrinsic weight for this data. + if payload.len() > maximal_message_size::() as usize { + return Err("The message is too large to be sent over the lane") + } + + Ok(()) + } + + /// Verify proof of This -> Bridged chain messages delivery. + /// + /// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged + /// parachains, please use the `verify_messages_delivery_proof_from_parachain`. + pub fn verify_messages_delivery_proof( + proof: FromBridgedChainMessagesDeliveryProof>>, + ) -> Result, &'static str> { + let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = + proof; + B::BridgedHeaderChain::parse_finalized_storage_proof( + bridged_header_hash, + StorageProof::new(storage_proof), + |storage| { + // Messages delivery proof is just proof of single storage key read => any error + // is fatal. + let storage_inbound_lane_data_key = + bp_messages::storage_keys::inbound_lane_data_key( + B::BRIDGED_MESSAGES_PALLET_NAME, + &lane, + ); + let raw_inbound_lane_data = storage + .read_value(storage_inbound_lane_data_key.0.as_ref()) + .map_err(|_| "Failed to read inbound lane state from storage proof")? + .ok_or("Inbound lane state is missing from the messages proof")?; + let inbound_lane_data = InboundLaneData::decode(&mut &raw_inbound_lane_data[..]) + .map_err(|_| "Failed to decode inbound lane state from the proof")?; + + Ok((lane, inbound_lane_data)) + }, + ) + .map_err(<&'static str>::from)? + } + + /// XCM bridge. + pub trait XcmBridge { + /// Runtime message bridge configuration. + type MessageBridge: MessageBridge; + /// Runtime message sender adapter. + type MessageSender: bp_messages::source_chain::MessagesBridge< + OriginOf>, + FromThisChainMessagePayload, + >; + + /// Our location within the Consensus Universe. + fn universal_location() -> InteriorMultiLocation; + /// Verify that the adapter is responsible for handling given XCM destination. + fn verify_destination(dest: &MultiLocation) -> bool; + /// Build route from this chain to the XCM destination. + fn build_destination() -> MultiLocation; + /// Return message lane used to deliver XCM messages. + fn xcm_lane() -> LaneId; + } + + /// XCM bridge adapter for `bridge-messages` pallet. + pub struct XcmBridgeAdapter(PhantomData); + + impl SendXcm for XcmBridgeAdapter + where + BalanceOf>: Into, + OriginOf>: From, + { + type Ticket = FromThisChainMessagePayload; + + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult { + let d = dest.take().ok_or(SendError::MissingArgument)?; + if !T::verify_destination(&d) { + *dest = Some(d); + return Err(SendError::NotApplicable) + } + + let route = T::build_destination(); + let msg = (route, msg.take().ok_or(SendError::MissingArgument)?).encode(); + + // let's just take fixed (out of thin air) fee per message in our test bridges + // (this code won't be used in production anyway) + let fee_assets = MultiAssets::from((Here, 1_000_000_u128)); + + Ok((msg, fee_assets)) + } + + fn deliver(ticket: Self::Ticket) -> Result { + use bp_messages::source_chain::MessagesBridge; + + let lane = T::xcm_lane(); + let msg = ticket; + let result = T::MessageSender::send_message( + pallet_xcm::Origin::from(MultiLocation::from(T::universal_location())).into(), + lane, + msg, + ); + result + .map(|artifacts| { + let hash = (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256); + log::debug!( + target: "runtime::bridge", + "Sent XCM message {:?}/{} to {:?}: {:?}", + lane, + artifacts.nonce, + T::MessageBridge::BRIDGED_CHAIN_ID, + hash, + ); + hash + }) + .map_err(|e| { + log::debug!( + target: "runtime::bridge", + "Failed to send XCM message over lane {:?} to {:?}: {:?}", + lane, + T::MessageBridge::BRIDGED_CHAIN_ID, + e, + ); + SendError::Transport("Bridge has rejected the message") + }) + } + } +} + +/// Sub-module that is declaring types required for processing Bridged -> This chain messages. +pub mod target { + use super::*; + + /// Decoded Bridged -> This message payload. + #[derive(RuntimeDebug, PartialEq, Eq)] + pub struct FromBridgedChainMessagePayload { + /// Data that is actually sent over the wire. + pub xcm: (xcm::v3::MultiLocation, xcm::v3::Xcm), + /// Weight of the message, computed by the weigher. Unknown initially. + pub weight: Option, + } + + impl Decode for FromBridgedChainMessagePayload { + fn decode(input: &mut I) -> Result { + let _: codec::Compact = Decode::decode(input)?; + type XcmPairType = (xcm::v3::MultiLocation, xcm::v3::Xcm); + Ok(FromBridgedChainMessagePayload { + xcm: XcmPairType::::decode_with_depth_limit( + sp_api::MAX_EXTRINSIC_DEPTH, + input, + )?, + weight: None, + }) + } + } + + impl From<(xcm::v3::MultiLocation, xcm::v3::Xcm)> + for FromBridgedChainMessagePayload + { + fn from(xcm: (xcm::v3::MultiLocation, xcm::v3::Xcm)) -> Self { + FromBridgedChainMessagePayload { xcm, weight: None } + } + } + + /// Messages proof from bridged chain: + /// + /// - hash of finalized header; + /// - storage proof of messages and (optionally) outbound lane state; + /// - lane id; + /// - nonces (inclusive range) of messages which are included in this proof. + #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] + pub struct FromBridgedChainMessagesProof { + /// Hash of the finalized bridged header the proof is for. + pub bridged_header_hash: BridgedHeaderHash, + /// A storage trie proof of messages being delivered. + pub storage_proof: RawStorageProof, + /// Messages in this proof are sent over this lane. + pub lane: LaneId, + /// Nonce of the first message being delivered. + pub nonces_start: MessageNonce, + /// Nonce of the last message being delivered. + pub nonces_end: MessageNonce, + } + + impl Size for FromBridgedChainMessagesProof { + fn size(&self) -> u32 { + u32::try_from( + self.storage_proof + .iter() + .fold(0usize, |sum, node| sum.saturating_add(node.len())), + ) + .unwrap_or(u32::MAX) + } + } + + /// Dispatching Bridged -> This chain messages. + #[derive(RuntimeDebug, Clone, Copy)] + pub struct FromBridgedChainMessageDispatch { + _marker: PhantomData<(B, XcmExecutor, XcmWeigher, WeightCredit)>, + } + + impl + MessageDispatch>> + for FromBridgedChainMessageDispatch + where + XcmExecutor: xcm::v3::ExecuteXcm>>, + XcmWeigher: xcm_executor::traits::WeightBounds>>, + WeightCredit: Get, + { + type DispatchPayload = FromBridgedChainMessagePayload>>; + + fn dispatch_weight( + message: &mut DispatchMessage, + ) -> frame_support::weights::Weight { + match message.data.payload { + Ok(ref mut payload) => { + // I have no idea why this method takes `&mut` reference and there's nothing + // about that in documentation. Hope it'll only mutate iff error is returned. + let weight = XcmWeigher::weight(&mut payload.xcm.1); + let weight = Weight::from_ref_time(weight.unwrap_or_else(|e| { + log::debug!( + target: "runtime::bridge-dispatch", + "Failed to compute dispatch weight of incoming XCM message {:?}/{}: {:?}", + message.key.lane_id, + message.key.nonce, + e, + ); + + // we shall return 0 and then the XCM executor will fail to execute XCM + // if we'll return something else (e.g. maximal value), the lane may stuck + 0 + })); + + payload.weight = Some(weight); + weight + }, + _ => Weight::zero(), + } + } + + fn dispatch( + _relayer_account: &AccountIdOf>, + message: DispatchMessage, + ) -> MessageDispatchResult { + let message_id = (message.key.lane_id, message.key.nonce); + let do_dispatch = move || -> sp_std::result::Result { + let FromBridgedChainMessagePayload { xcm: (location, xcm), weight: weight_limit } = + message.data.payload?; + log::trace!( + target: "runtime::bridge-dispatch", + "Going to execute message {:?} (weight limit: {:?}): {:?} {:?}", + message_id, + weight_limit, + location, + xcm, + ); + let hash = message_id.using_encoded(sp_io::hashing::blake2_256); + + // if this cod will end up in production, this most likely needs to be set to zero + let weight_credit = WeightCredit::get(); + + let xcm_outcome = XcmExecutor::execute_xcm_in_credit( + location, + xcm, + hash, + weight_limit.unwrap_or_else(Weight::zero).ref_time(), + weight_credit.ref_time(), + ); + Ok(xcm_outcome) + }; + + let xcm_outcome = do_dispatch(); + match xcm_outcome { + Ok(outcome) => { + log::trace!( + target: "runtime::bridge-dispatch", + "Incoming message {:?} dispatched with result: {:?}", + message_id, + outcome, + ); + match outcome.ensure_execution() { + Ok(_weight) => (), + Err(e) => { + log::error!( + target: "runtime::bridge-dispatch", + "Incoming message {:?} was not dispatched, error: {:?}", + message_id, + e, + ); + }, + } + }, + Err(e) => { + log::error!( + target: "runtime::bridge-dispatch", + "Incoming message {:?} was not dispatched, codec error: {:?}", + message_id, + e, + ); + }, + } + + MessageDispatchResult { + unspent_weight: Weight::zero(), + dispatch_fee_paid_during_dispatch: false, + } + } + } + + /// Return maximal dispatch weight of the message we're able to receive. + pub fn maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight { + maximal_extrinsic_weight / 2 + } + + /// Return maximal message size given maximal extrinsic size. + pub fn maximal_incoming_message_size(maximal_extrinsic_size: u32) -> u32 { + maximal_extrinsic_size / 3 * 2 + } + + /// Verify proof of Bridged -> This chain messages. + /// + /// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged + /// parachains, please use the `verify_messages_proof_from_parachain`. + /// + /// The `messages_count` argument verification (sane limits) is supposed to be made + /// outside of this function. This function only verifies that the proof declares exactly + /// `messages_count` messages. + pub fn verify_messages_proof( + proof: FromBridgedChainMessagesProof>>, + messages_count: u32, + ) -> Result, MessageProofError> { + let FromBridgedChainMessagesProof { + bridged_header_hash, + storage_proof, + lane, + nonces_start, + nonces_end, + } = proof; + + B::BridgedHeaderChain::parse_finalized_storage_proof( + bridged_header_hash, + StorageProof::new(storage_proof), + |storage| { + let parser = + StorageProofCheckerAdapter::<_, B> { storage, _dummy: Default::default() }; + + // receiving proofs where end < begin is ok (if proof includes outbound lane state) + let messages_in_the_proof = + if let Some(nonces_difference) = nonces_end.checked_sub(nonces_start) { + // let's check that the user (relayer) has passed correct `messages_count` + // (this bounds maximal capacity of messages vec below) + let messages_in_the_proof = nonces_difference.saturating_add(1); + if messages_in_the_proof != MessageNonce::from(messages_count) { + return Err(MessageProofError::MessagesCountMismatch) + } + + messages_in_the_proof + } else { + 0 + }; + + // Read messages first. All messages that are claimed to be in the proof must + // be in the proof. So any error in `read_value`, or even missing value is fatal. + // + // Mind that we allow proofs with no messages if outbound lane state is proved. + let mut messages = Vec::with_capacity(messages_in_the_proof as _); + for nonce in nonces_start..=nonces_end { + let message_key = MessageKey { lane_id: lane, nonce }; + let raw_message_data = parser + .read_raw_message(&message_key) + .ok_or(MessageProofError::MissingRequiredMessage)?; + let payload = MessagePayload::decode(&mut &raw_message_data[..]) + .map_err(|_| MessageProofError::FailedToDecodeMessage)?; + messages.push(Message { key: message_key, payload }); + } + + // Now let's check if proof contains outbound lane state proof. It is optional, so + // we simply ignore `read_value` errors and missing value. + let mut proved_lane_messages = ProvedLaneMessages { lane_state: None, messages }; + let raw_outbound_lane_data = parser.read_raw_outbound_lane_data(&lane); + if let Some(raw_outbound_lane_data) = raw_outbound_lane_data { + proved_lane_messages.lane_state = Some( + OutboundLaneData::decode(&mut &raw_outbound_lane_data[..]) + .map_err(|_| MessageProofError::FailedToDecodeOutboundLaneState)?, + ); + } + + // Now we may actually check if the proof is empty or not. + if proved_lane_messages.lane_state.is_none() && + proved_lane_messages.messages.is_empty() + { + return Err(MessageProofError::Empty) + } + + // We only support single lane messages in this generated_schema + let mut proved_messages = ProvedMessages::new(); + proved_messages.insert(lane, proved_lane_messages); + + Ok(proved_messages) + }, + ) + .map_err(MessageProofError::HeaderChain)? + } + + /// Error that happens during message proof verification. + #[derive(Debug, PartialEq, Eq)] + pub enum MessageProofError { + /// Error returned by the bridged header chain. + HeaderChain(HeaderChainError), + /// The message proof is empty. + Empty, + /// Declared messages count doesn't match actual value. + MessagesCountMismatch, + /// Message is missing from the proof. + MissingRequiredMessage, + /// Failed to decode message from the proof. + FailedToDecodeMessage, + /// Failed to decode outbound lane data from the proof. + FailedToDecodeOutboundLaneState, + } + + impl From for &'static str { + fn from(err: MessageProofError) -> &'static str { + match err { + MessageProofError::HeaderChain(err) => err.into(), + MessageProofError::Empty => "Messages proof is empty", + MessageProofError::MessagesCountMismatch => + "Declared messages count doesn't match actual value", + MessageProofError::MissingRequiredMessage => "Message is missing from the proof", + MessageProofError::FailedToDecodeMessage => + "Failed to decode message from the proof", + MessageProofError::FailedToDecodeOutboundLaneState => + "Failed to decode outbound lane data from the proof", + } + } + } + + struct StorageProofCheckerAdapter { + storage: StorageProofChecker, + _dummy: sp_std::marker::PhantomData, + } + + impl StorageProofCheckerAdapter { + fn read_raw_outbound_lane_data(&self, lane_id: &LaneId) -> Option> { + let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key( + B::BRIDGED_MESSAGES_PALLET_NAME, + lane_id, + ); + self.storage.read_value(storage_outbound_lane_data_key.0.as_ref()).ok()? + } + + fn read_raw_message(&self, message_key: &MessageKey) -> Option> { + let storage_message_key = bp_messages::storage_keys::message_key( + B::BRIDGED_MESSAGES_PALLET_NAME, + &message_key.lane_id, + message_key.nonce, + ); + self.storage.read_value(storage_message_key.0.as_ref()).ok()? + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::messages_generation::{ + encode_all_messages, encode_lane_data, prepare_messages_storage_proof, + }; + use bp_runtime::HeaderOf; + use codec::{Decode, Encode}; + use frame_support::weights::Weight; + use sp_core::H256; + use sp_runtime::traits::{BlakeTwo256, Header as _}; + use std::cell::RefCell; + + const BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT: usize = 5; + const BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT: usize = 2048; + const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024; + + /// Bridge that is deployed on ThisChain and allows sending/receiving messages to/from + /// BridgedChain. + #[derive(Debug, PartialEq, Eq)] + struct OnThisChainBridge; + + impl MessageBridge for OnThisChainBridge { + const THIS_CHAIN_ID: ChainId = *b"this"; + const BRIDGED_CHAIN_ID: ChainId = *b"brdg"; + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = ""; + + type ThisChain = ThisChain; + type BridgedChain = BridgedChain; + type BridgedHeaderChain = BridgedHeaderChain; + } + + /// Bridge that is deployed on BridgedChain and allows sending/receiving messages to/from + /// ThisChain; + #[derive(Debug, PartialEq, Eq)] + struct OnBridgedChainBridge; + + impl MessageBridge for OnBridgedChainBridge { + const THIS_CHAIN_ID: ChainId = *b"brdg"; + const BRIDGED_CHAIN_ID: ChainId = *b"this"; + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = ""; + + type ThisChain = BridgedChain; + type BridgedChain = ThisChain; + type BridgedHeaderChain = ThisHeaderChain; + } + + #[derive(Clone, Debug)] + struct ThisChainOrigin(Result, ()>); + + impl From + for Result, ThisChainOrigin> + { + fn from( + origin: ThisChainOrigin, + ) -> Result, ThisChainOrigin> { + origin.clone().0.map_err(|_| origin) + } + } + + #[derive(Clone, Debug)] + struct BridgedChainOrigin; + + impl From + for Result, BridgedChainOrigin> + { + fn from( + _origin: BridgedChainOrigin, + ) -> Result, BridgedChainOrigin> { + unreachable!() + } + } + + struct ThisUnderlyingChain; + type ThisChainHeader = sp_runtime::generic::Header; + type ThisChainAccountId = u32; + type ThisChainBalance = u32; + #[derive(Decode, Encode)] + struct ThisChainCall; + + impl Chain for ThisUnderlyingChain { + type BlockNumber = u64; + type Hash = H256; + type Hasher = BlakeTwo256; + type Header = ThisChainHeader; + type AccountId = ThisChainAccountId; + type Balance = ThisChainBalance; + type Index = u32; + type Signature = sp_runtime::MultiSignature; + + fn max_extrinsic_size() -> u32 { + BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE + } + + fn max_extrinsic_weight() -> Weight { + Weight::zero() + } + } + + struct ThisChain; + + impl UnderlyingChainProvider for ThisChain { + type Chain = ThisUnderlyingChain; + } + + impl ThisChainWithMessages for ThisChain { + type RuntimeOrigin = ThisChainOrigin; + type RuntimeCall = ThisChainCall; + + fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, lane: &LaneId) -> bool { + lane == TEST_LANE_ID + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE + } + } + + impl BridgedChainWithMessages for ThisChain { + fn verify_dispatch_weight(_message_payload: &[u8]) -> bool { + unreachable!() + } + } + + struct BridgedUnderlyingChain; + type BridgedChainHeader = sp_runtime::generic::Header; + type BridgedChainAccountId = u128; + type BridgedChainBalance = u128; + #[derive(Decode, Encode)] + struct BridgedChainCall; + + impl Chain for BridgedUnderlyingChain { + type BlockNumber = u64; + type Hash = H256; + type Hasher = BlakeTwo256; + type Header = BridgedChainHeader; + type AccountId = BridgedChainAccountId; + type Balance = BridgedChainBalance; + type Index = u32; + type Signature = sp_runtime::MultiSignature; + + fn max_extrinsic_size() -> u32 { + BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE + } + fn max_extrinsic_weight() -> Weight { + Weight::zero() + } + } + + struct BridgedChain; + + impl UnderlyingChainProvider for BridgedChain { + type Chain = BridgedUnderlyingChain; + } + + impl ThisChainWithMessages for BridgedChain { + type RuntimeOrigin = BridgedChainOrigin; + type RuntimeCall = BridgedChainCall; + + fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool { + unreachable!() + } + + fn maximal_pending_messages_at_outbound_lane() -> MessageNonce { + unreachable!() + } + } + + impl BridgedChainWithMessages for BridgedChain { + fn verify_dispatch_weight(message_payload: &[u8]) -> bool { + message_payload.len() >= BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT && + message_payload.len() <= BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT + } + } + + thread_local! { + static TEST_BRIDGED_HEADER: RefCell> = RefCell::new(None); + } + + struct BridgedHeaderChain; + + impl HeaderChain for BridgedHeaderChain { + fn finalized_header( + _hash: HashOf, + ) -> Option> { + TEST_BRIDGED_HEADER.with(|h| h.borrow().clone()) + } + } + + struct ThisHeaderChain; + + impl HeaderChain for ThisHeaderChain { + fn finalized_header(_hash: HashOf) -> Option> { + unreachable!() + } + } + + fn test_lane_outbound_data() -> OutboundLaneData { + OutboundLaneData::default() + } + + const TEST_LANE_ID: &LaneId = b"test"; + const MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE: MessageNonce = 32; + + fn regular_outbound_message_payload() -> source::FromThisChainMessagePayload { + vec![42] + } + + #[test] + fn message_is_rejected_when_sent_using_disabled_lane() { + assert_eq!( + source::FromThisChainMessageVerifier::::verify_message( + &ThisChainOrigin(Ok(frame_system::RawOrigin::Root)), + b"dsbl", + &test_lane_outbound_data(), + ®ular_outbound_message_payload(), + ), + Err(source::MESSAGE_REJECTED_BY_OUTBOUND_LANE) + ); + } + + #[test] + fn message_is_rejected_when_there_are_too_many_pending_messages_at_outbound_lane() { + assert_eq!( + source::FromThisChainMessageVerifier::::verify_message( + &ThisChainOrigin(Ok(frame_system::RawOrigin::Root)), + TEST_LANE_ID, + &OutboundLaneData { + latest_received_nonce: 100, + latest_generated_nonce: 100 + MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE + 1, + ..Default::default() + }, + ®ular_outbound_message_payload(), + ), + Err(source::TOO_MANY_PENDING_MESSAGES) + ); + } + + #[test] + fn verify_chain_message_rejects_message_with_too_small_declared_weight() { + assert!(source::verify_chain_message::(&vec![ + 42; + BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT - + 1 + ]) + .is_err()); + } + + #[test] + fn verify_chain_message_rejects_message_with_too_large_declared_weight() { + assert!(source::verify_chain_message::(&vec![ + 42; + BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT - + 1 + ]) + .is_err()); + } + + #[test] + fn verify_chain_message_rejects_message_too_large_message() { + assert!(source::verify_chain_message::(&vec![ + 0; + source::maximal_message_size::() + as usize + 1 + ],) + .is_err()); + } + + #[test] + fn verify_chain_message_accepts_maximal_message() { + assert_eq!( + source::verify_chain_message::(&vec![ + 0; + source::maximal_message_size::() + as _ + ],), + Ok(()), + ); + } + + fn using_messages_proof( + nonces_end: MessageNonce, + outbound_lane_data: Option, + encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option>, + encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec, + test: impl Fn(target::FromBridgedChainMessagesProof) -> R, + ) -> R { + let (state_root, storage_proof) = prepare_messages_storage_proof::( + *TEST_LANE_ID, + 1..=nonces_end, + outbound_lane_data, + bp_runtime::StorageProofSize::Minimal(0), + vec![42], + encode_message, + encode_outbound_lane_data, + ); + + TEST_BRIDGED_HEADER.with(|h| { + *h.borrow_mut() = Some(BridgedChainHeader::new( + 0, + Default::default(), + state_root, + Default::default(), + Default::default(), + )) + }); + + test(target::FromBridgedChainMessagesProof { + bridged_header_hash: Default::default(), + storage_proof, + lane: *TEST_LANE_ID, + nonces_start: 1, + nonces_end, + }) + } + + #[test] + fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() { + assert_eq!( + using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| { + target::verify_messages_proof::(proof, 5) + }), + Err(target::MessageProofError::MessagesCountMismatch), + ); + } + + #[test] + fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() { + assert_eq!( + using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| { + target::verify_messages_proof::(proof, 15) + }), + Err(target::MessageProofError::MessagesCountMismatch), + ); + } + + #[test] + fn message_proof_is_rejected_if_header_is_missing_from_the_chain() { + assert_eq!( + using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| { + TEST_BRIDGED_HEADER.with(|h| *h.borrow_mut() = None); + target::verify_messages_proof::(proof, 10) + }), + Err(target::MessageProofError::HeaderChain(HeaderChainError::UnknownHeader)), + ); + } + + #[test] + fn message_proof_is_rejected_if_header_state_root_mismatches() { + assert_eq!( + using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| { + TEST_BRIDGED_HEADER + .with(|h| h.borrow_mut().as_mut().unwrap().state_root = Default::default()); + target::verify_messages_proof::(proof, 10) + }), + Err(target::MessageProofError::HeaderChain(HeaderChainError::StorageRootMismatch)), + ); + } + + #[test] + fn message_proof_is_rejected_if_required_message_is_missing() { + assert_eq!( + using_messages_proof( + 10, + None, + |n, m| if n != 5 { Some(m.encode()) } else { None }, + encode_lane_data, + |proof| target::verify_messages_proof::(proof, 10) + ), + Err(target::MessageProofError::MissingRequiredMessage), + ); + } + + #[test] + fn message_proof_is_rejected_if_message_decode_fails() { + assert_eq!( + using_messages_proof( + 10, + None, + |n, m| { + let mut m = m.encode(); + if n == 5 { + m = vec![42] + } + Some(m) + }, + encode_lane_data, + |proof| target::verify_messages_proof::(proof, 10), + ), + Err(target::MessageProofError::FailedToDecodeMessage), + ); + } + + #[test] + fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() { + assert_eq!( + using_messages_proof( + 10, + Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + encode_all_messages, + |d| { + let mut d = d.encode(); + d.truncate(1); + d + }, + |proof| target::verify_messages_proof::(proof, 10), + ), + Err(target::MessageProofError::FailedToDecodeOutboundLaneState), + ); + } + + #[test] + fn message_proof_is_rejected_if_it_is_empty() { + assert_eq!( + using_messages_proof(0, None, encode_all_messages, encode_lane_data, |proof| { + target::verify_messages_proof::(proof, 0) + },), + Err(target::MessageProofError::Empty), + ); + } + + #[test] + fn non_empty_message_proof_without_messages_is_accepted() { + assert_eq!( + using_messages_proof( + 0, + Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + encode_all_messages, + encode_lane_data, + |proof| target::verify_messages_proof::(proof, 0), + ), + Ok(vec![( + *TEST_LANE_ID, + ProvedLaneMessages { + lane_state: Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + messages: Vec::new(), + }, + )] + .into_iter() + .collect()), + ); + } + + #[test] + fn non_empty_message_proof_is_accepted() { + assert_eq!( + using_messages_proof( + 1, + Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + encode_all_messages, + encode_lane_data, + |proof| target::verify_messages_proof::(proof, 1), + ), + Ok(vec![( + *TEST_LANE_ID, + ProvedLaneMessages { + lane_state: Some(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 1, + latest_generated_nonce: 1, + }), + messages: vec![Message { + key: MessageKey { lane_id: *TEST_LANE_ID, nonce: 1 }, + payload: vec![42], + }], + }, + )] + .into_iter() + .collect()), + ); + } + + #[test] + fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() { + assert_eq!( + using_messages_proof(1, None, encode_all_messages, encode_lane_data, |mut proof| { + proof.nonces_end = u64::MAX; + target::verify_messages_proof::(proof, u32::MAX) + },), + Err(target::MessageProofError::MessagesCountMismatch), + ); + } +} diff --git a/bin/runtime-common/src/messages_api.rs b/bin/runtime-common/src/messages_api.rs new file mode 100644 index 00000000000..199e062fe98 --- /dev/null +++ b/bin/runtime-common/src/messages_api.rs @@ -0,0 +1,66 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Helpers for implementing various message-related runtime API mthods. + +use bp_messages::{ + InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, +}; +use sp_std::vec::Vec; + +/// Implementation of the `To*OutboundLaneApi::message_details`. +pub fn outbound_message_details( + lane: LaneId, + begin: MessageNonce, + end: MessageNonce, +) -> Vec +where + Runtime: pallet_bridge_messages::Config, + MessagesPalletInstance: 'static, +{ + (begin..=end) + .filter_map(|nonce| { + let message_data = + pallet_bridge_messages::Pallet::::outbound_message_data(lane, nonce)?; + Some(OutboundMessageDetails { + nonce, + // dispatch message weight is always zero at the source chain, since we're paying for + // dispatch at the target chain + dispatch_weight: frame_support::weights::Weight::zero(), + size: message_data.len() as _, + }) + }) + .collect() +} + +/// Implementation of the `To*InboundLaneApi::message_details`. +pub fn inbound_message_details( + lane: LaneId, + messages: Vec<(MessagePayload, OutboundMessageDetails)>, +) -> Vec +where + Runtime: pallet_bridge_messages::Config, + MessagesPalletInstance: 'static, +{ + messages + .into_iter() + .map(|(payload, details)| { + pallet_bridge_messages::Pallet::::inbound_message_data( + lane, payload, details, + ) + }) + .collect() +} diff --git a/bin/runtime-common/src/messages_benchmarking.rs b/bin/runtime-common/src/messages_benchmarking.rs new file mode 100644 index 00000000000..919c03583b8 --- /dev/null +++ b/bin/runtime-common/src/messages_benchmarking.rs @@ -0,0 +1,157 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Everything required to run benchmarks of messages module, based on +//! `bridge_runtime_common::messages` implementation. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{ + messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, + AccountIdOf, BalanceOf, BridgedChain, CallOf, HashOf, MessageBridge, ThisChain, + }, + messages_generation::{ + encode_all_messages, encode_lane_data, grow_trie, prepare_messages_storage_proof, + }, +}; + +use bp_messages::storage_keys; +use bp_runtime::{record_all_trie_keys, StorageProofSize}; +use codec::Encode; +use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; +use pallet_bridge_messages::benchmarking::{MessageDeliveryProofParams, MessageProofParams}; +use sp_core::Hasher; +use sp_runtime::traits::{Header, MaybeSerializeDeserialize, Zero}; +use sp_std::{fmt::Debug, prelude::*}; +use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, Recorder, TrieMut}; + +/// Prepare proof of messages for the `receive_messages_proof` call. +/// +/// In addition to returning valid messages proof, environment is prepared to verify this message +/// proof. +pub fn prepare_message_proof( + params: MessageProofParams, +) -> (FromBridgedChainMessagesProof>>, Weight) +where + R: frame_system::Config>> + + pallet_balances::Config>> + + pallet_bridge_grandpa::Config, + R::BridgedChain: bp_runtime::Chain>, Header = BH>, + B: MessageBridge, + BI: 'static, + FI: 'static, + BH: Header>>, + BHH: Hasher>>, + AccountIdOf>: PartialEq + sp_std::fmt::Debug, + AccountIdOf>: From<[u8; 32]>, + BalanceOf>: Debug + MaybeSerializeDeserialize, + CallOf>: From> + GetDispatchInfo, + HashOf>: Copy + Default, +{ + let message_payload = match params.size { + StorageProofSize::Minimal(ref size) => vec![0u8; *size as _], + _ => vec![], + }; + + // finally - prepare storage proof and update environment + let (state_root, storage_proof) = prepare_messages_storage_proof::( + params.lane, + params.message_nonces.clone(), + params.outbound_lane_data, + params.size, + message_payload, + encode_all_messages, + encode_lane_data, + ); + let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::(state_root); + + ( + FromBridgedChainMessagesProof { + bridged_header_hash, + storage_proof, + lane: params.lane, + nonces_start: *params.message_nonces.start(), + nonces_end: *params.message_nonces.end(), + }, + Weight::zero(), + ) +} + +/// Prepare proof of messages delivery for the `receive_messages_delivery_proof` call. +pub fn prepare_message_delivery_proof( + params: MessageDeliveryProofParams>>, +) -> FromBridgedChainMessagesDeliveryProof>> +where + R: pallet_bridge_grandpa::Config, + R::BridgedChain: bp_runtime::Chain>, Header = BH>, + FI: 'static, + B: MessageBridge, + BH: Header>>, + BHH: Hasher>>, + HashOf>: Copy + Default, +{ + // prepare Bridged chain storage with inbound lane state + let storage_key = + storage_keys::inbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, ¶ms.lane).0; + let mut root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = TrieDBMutBuilderV1::::new(&mut mdb, &mut root).build(); + trie.insert(&storage_key, ¶ms.inbound_lane_data.encode()) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + } + root = grow_trie(root, &mut mdb, params.size); + + // generate storage proof to be delivered to This chain + let mut proof_recorder = Recorder::>::new(); + record_all_trie_keys::, _>(&mdb, &root, &mut proof_recorder) + .map_err(|_| "record_all_trie_keys has failed") + .expect("record_all_trie_keys should not fail in benchmarks"); + let storage_proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect(); + + // finally insert header with given state root to our storage + let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::(root); + + FromBridgedChainMessagesDeliveryProof { + bridged_header_hash: bridged_header_hash.into(), + storage_proof, + lane: params.lane, + } +} + +/// Insert header to the bridge GRANDPA pallet. +pub(crate) fn insert_header_to_grandpa_pallet( + state_root: bp_runtime::HashOf, +) -> (bp_runtime::BlockNumberOf, bp_runtime::HashOf) +where + R: pallet_bridge_grandpa::Config, + GI: 'static, + R::BridgedChain: bp_runtime::Chain, +{ + let bridged_block_number = Zero::zero(); + let bridged_header = bp_runtime::HeaderOf::::new( + bridged_block_number, + Default::default(), + state_root, + Default::default(), + Default::default(), + ); + let bridged_header_hash = bridged_header.hash(); + pallet_bridge_grandpa::initialize_for_benchmarks::(bridged_header); + (bridged_block_number, bridged_header_hash) +} diff --git a/bin/runtime-common/src/messages_extension.rs b/bin/runtime-common/src/messages_extension.rs new file mode 100644 index 00000000000..28606d58dad --- /dev/null +++ b/bin/runtime-common/src/messages_extension.rs @@ -0,0 +1,231 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{ + messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, + }, + BridgeRuntimeFilterCall, +}; +use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; +use pallet_bridge_messages::{Config, Pallet}; +use sp_runtime::transaction_validity::TransactionValidity; + +/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation +/// transactions, that are delivering outdated messages/confirmations. Without this validation, +/// even honest relayers may lose their funds if there are multiple relays running and submitting +/// the same messages/confirmations. +impl< + BridgedHeaderHash, + SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof, + >, + TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain< + >::OutboundPayload, + ::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof, + >, + Call: IsSubType, T>>, + T: frame_system::Config + + Config, + I: 'static, + > BridgeRuntimeFilterCall for Pallet +{ + fn validate(call: &Call) -> TransactionValidity { + match call.is_sub_type() { + Some(pallet_bridge_messages::Call::::receive_messages_proof { + ref proof, + .. + }) => { + let inbound_lane_data = + pallet_bridge_messages::InboundLanes::::get(proof.lane); + if proof.nonces_end <= inbound_lane_data.last_delivered_nonce() { + log::trace!( + target: pallet_bridge_messages::LOG_TARGET, + "Rejecting obsolete messages delivery transaction: \ + lane {:?}, bundled {:?}, best {:?}", + proof.lane, + proof.nonces_end, + inbound_lane_data.last_delivered_nonce(), + ); + + return sp_runtime::transaction_validity::InvalidTransaction::Stale.into() + } + }, + Some(pallet_bridge_messages::Call::::receive_messages_delivery_proof { + ref proof, + ref relayers_state, + .. + }) => { + let latest_delivered_nonce = relayers_state.last_delivered_nonce; + + let outbound_lane_data = + pallet_bridge_messages::OutboundLanes::::get(proof.lane); + if latest_delivered_nonce <= outbound_lane_data.latest_received_nonce { + log::trace!( + target: pallet_bridge_messages::LOG_TARGET, + "Rejecting obsolete messages confirmation transaction: \ + lane {:?}, bundled {:?}, best {:?}", + proof.lane, + latest_delivered_nonce, + outbound_lane_data.latest_received_nonce, + ); + + return sp_runtime::transaction_validity::InvalidTransaction::Stale.into() + } + }, + _ => {}, + } + + Ok(sp_runtime::transaction_validity::ValidTransaction::default()) + } +} + +#[cfg(test)] +mod tests { + use bp_messages::UnrewardedRelayersState; + use millau_runtime::{ + bridge_runtime_common::{ + messages::{ + source::FromBridgedChainMessagesDeliveryProof, + target::FromBridgedChainMessagesProof, + }, + BridgeRuntimeFilterCall, + }, + Runtime, RuntimeCall, WithRialtoMessagesInstance, + }; + + fn deliver_message_10() { + pallet_bridge_messages::InboundLanes::::insert( + [0, 0, 0, 0], + bp_messages::InboundLaneData { relayers: Default::default(), last_confirmed_nonce: 10 }, + ); + } + + fn validate_message_delivery( + nonces_start: bp_messages::MessageNonce, + nonces_end: bp_messages::MessageNonce, + ) -> bool { + pallet_bridge_messages::Pallet::::validate( + &RuntimeCall::BridgeRialtoMessages( + pallet_bridge_messages::Call::::receive_messages_proof { + relayer_id_at_bridged_chain: [0u8; 32].into(), + messages_count: (nonces_end - nonces_start + 1) as u32, + dispatch_weight: frame_support::weights::Weight::zero(), + proof: FromBridgedChainMessagesProof { + bridged_header_hash: Default::default(), + storage_proof: vec![], + lane: [0, 0, 0, 0], + nonces_start, + nonces_end, + }, + }, + ), + ) + .is_ok() + } + + #[test] + fn extension_rejects_obsolete_messages() { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + // when current best delivered is message#10 and we're trying to deliver message#5 => tx + // is rejected + deliver_message_10(); + assert!(!validate_message_delivery(8, 9)); + }); + } + + #[test] + fn extension_rejects_same_message() { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + // when current best delivered is message#10 and we're trying to import message#10 => tx + // is rejected + deliver_message_10(); + assert!(!validate_message_delivery(8, 10)); + }); + } + + #[test] + fn extension_accepts_new_message() { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + // when current best delivered is message#10 and we're trying to deliver message#15 => + // tx is accepted + deliver_message_10(); + assert!(validate_message_delivery(10, 15)); + }); + } + + fn confirm_message_10() { + pallet_bridge_messages::OutboundLanes::::insert( + [0, 0, 0, 0], + bp_messages::OutboundLaneData { + oldest_unpruned_nonce: 0, + latest_received_nonce: 10, + latest_generated_nonce: 10, + }, + ); + } + + fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool { + pallet_bridge_messages::Pallet::::validate( + &RuntimeCall::BridgeRialtoMessages(pallet_bridge_messages::Call::< + Runtime, + WithRialtoMessagesInstance, + >::receive_messages_delivery_proof { + proof: FromBridgedChainMessagesDeliveryProof { + bridged_header_hash: Default::default(), + storage_proof: Vec::new(), + lane: [0, 0, 0, 0], + }, + relayers_state: UnrewardedRelayersState { + last_delivered_nonce, + ..Default::default() + }, + }), + ) + .is_ok() + } + + #[test] + fn extension_rejects_obsolete_confirmations() { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + // when current best confirmed is message#10 and we're trying to confirm message#5 => tx + // is rejected + confirm_message_10(); + assert!(!validate_message_confirmation(5)); + }); + } + + #[test] + fn extension_rejects_same_confirmation() { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + // when current best confirmed is message#10 and we're trying to confirm message#10 => + // tx is rejected + confirm_message_10(); + assert!(!validate_message_confirmation(10)); + }); + } + + #[test] + fn extension_accepts_new_confirmation() { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + // when current best confirmed is message#10 and we're trying to confirm message#15 => + // tx is accepted + confirm_message_10(); + assert!(validate_message_confirmation(15)); + }); + } +} diff --git a/bin/runtime-common/src/messages_generation.rs b/bin/runtime-common/src/messages_generation.rs new file mode 100644 index 00000000000..560033d122a --- /dev/null +++ b/bin/runtime-common/src/messages_generation.rs @@ -0,0 +1,146 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Helpers for generating message storage proofs, that are used by tests and by benchmarks. + +#![cfg(any(feature = "runtime-benchmarks", test))] + +use crate::messages::{BridgedChain, HashOf, HasherOf, MessageBridge, RawStorageProof}; + +use bp_messages::{ + storage_keys, LaneId, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, +}; +use bp_runtime::{record_all_trie_keys, StorageProofSize}; +use codec::Encode; +use sp_core::Hasher; +use sp_std::{ops::RangeInclusive, prelude::*}; +use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, Recorder, TrieMut}; + +/// Simple and correct message data encode function. +pub(crate) fn encode_all_messages(_: MessageNonce, m: &MessagePayload) -> Option> { + Some(m.encode()) +} + +/// Simple and correct outbound lane data encode function. +pub(crate) fn encode_lane_data(d: &OutboundLaneData) -> Vec { + d.encode() +} + +/// Prepare storage proof of given messages. +/// +/// Returns state trie root and nodes with prepared messages. +pub(crate) fn prepare_messages_storage_proof( + lane: LaneId, + message_nonces: RangeInclusive, + outbound_lane_data: Option, + size: StorageProofSize, + message_payload: MessagePayload, + encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option>, + encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec, +) -> (HashOf>, RawStorageProof) +where + B: MessageBridge, + HashOf>: Copy + Default, +{ + // prepare Bridged chain storage with messages and (optionally) outbound lane state + let message_count = message_nonces.end().saturating_sub(*message_nonces.start()) + 1; + let mut storage_keys = Vec::with_capacity(message_count as usize + 1); + let mut root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = + TrieDBMutBuilderV1::>>::new(&mut mdb, &mut root).build(); + + // insert messages + for nonce in message_nonces { + let message_key = MessageKey { lane_id: lane, nonce }; + let message_payload = match encode_message(nonce, &message_payload) { + Some(message_payload) => message_payload, + None => continue, + }; + let storage_key = storage_keys::message_key( + B::BRIDGED_MESSAGES_PALLET_NAME, + &message_key.lane_id, + message_key.nonce, + ) + .0; + trie.insert(&storage_key, &message_payload) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + storage_keys.push(storage_key); + } + + // insert outbound lane state + if let Some(outbound_lane_data) = outbound_lane_data.as_ref().map(encode_outbound_lane_data) + { + let storage_key = + storage_keys::outbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &lane).0; + trie.insert(&storage_key, &outbound_lane_data) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + storage_keys.push(storage_key); + } + } + root = grow_trie(root, &mut mdb, size); + + // generate storage proof to be delivered to This chain + let mut proof_recorder = Recorder::>>>::new(); + record_all_trie_keys::>>, _>( + &mdb, + &root, + &mut proof_recorder, + ) + .map_err(|_| "record_all_trie_keys has failed") + .expect("record_all_trie_keys should not fail in benchmarks"); + let storage_proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect(); + + (root, storage_proof) +} + +/// Populate trie with dummy keys+values until trie has at least given size. +pub fn grow_trie( + mut root: H::Out, + mdb: &mut MemoryDB, + trie_size: StorageProofSize, +) -> H::Out { + let (iterations, leaf_size, minimal_trie_size) = match trie_size { + StorageProofSize::Minimal(_) => return root, + StorageProofSize::HasLargeLeaf(size) => (1, size, size), + StorageProofSize::HasExtraNodes(size) => (8, 1, size), + }; + + let mut key_index = 0; + loop { + // generate storage proof to be delivered to This chain + let mut proof_recorder = Recorder::>::new(); + record_all_trie_keys::, _>(mdb, &root, &mut proof_recorder) + .map_err(|_| "record_all_trie_keys has failed") + .expect("record_all_trie_keys should not fail in benchmarks"); + let size: usize = proof_recorder.drain().into_iter().map(|n| n.data.len()).sum(); + if size > minimal_trie_size as _ { + return root + } + + let mut trie = TrieDBMutBuilderV1::::from_existing(mdb, &mut root).build(); + for _ in 0..iterations { + trie.insert(&key_index.encode(), &vec![42u8; leaf_size as _]) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + key_index += 1; + } + trie.commit(); + } +} diff --git a/bin/runtime-common/src/parachains_benchmarking.rs b/bin/runtime-common/src/parachains_benchmarking.rs new file mode 100644 index 00000000000..fcd32ea28b8 --- /dev/null +++ b/bin/runtime-common/src/parachains_benchmarking.rs @@ -0,0 +1,85 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Everything required to run benchmarks of parachains finality module. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{ + messages_benchmarking::insert_header_to_grandpa_pallet, messages_generation::grow_trie, +}; + +use bp_parachains::parachain_head_storage_key_at_source; +use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; +use bp_runtime::{record_all_trie_keys, StorageProofSize}; +use codec::Encode; +use frame_support::traits::Get; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use sp_std::prelude::*; +use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, Recorder, TrieMut}; + +/// Prepare proof of messages for the `receive_messages_proof` call. +/// +/// In addition to returning valid messages proof, environment is prepared to verify this message +/// proof. +pub fn prepare_parachain_heads_proof( + parachains: &[ParaId], + parachain_head_size: u32, + size: StorageProofSize, +) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>) +where + R: pallet_bridge_parachains::Config + + pallet_bridge_grandpa::Config, + PI: 'static, + >::BridgedChain: + bp_runtime::Chain, +{ + let parachain_head = ParaHead(vec![0u8; parachain_head_size as usize]); + + // insert all heads to the trie + let mut parachain_heads = Vec::with_capacity(parachains.len()); + let mut storage_keys = Vec::with_capacity(parachains.len()); + let mut state_root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = + TrieDBMutBuilderV1::::new(&mut mdb, &mut state_root).build(); + + // insert parachain heads + for parachain in parachains { + let storage_key = + parachain_head_storage_key_at_source(R::ParasPalletName::get(), *parachain); + trie.insert(&storage_key.0, ¶chain_head.encode()) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + storage_keys.push(storage_key); + parachain_heads.push((*parachain, parachain_head.hash())) + } + } + state_root = grow_trie(state_root, &mut mdb, size); + + // generate heads storage proof + let mut proof_recorder = Recorder::>::new(); + record_all_trie_keys::, _>(&mdb, &state_root, &mut proof_recorder) + .map_err(|_| "record_all_trie_keys has failed") + .expect("record_all_trie_keys should not fail in benchmarks"); + let proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect(); + + let (relay_block_number, relay_block_hash) = + insert_header_to_grandpa_pallet::(state_root); + + (relay_block_number, relay_block_hash, ParaHeadsProof(proof), parachain_heads) +} diff --git a/ci.Dockerfile b/ci.Dockerfile new file mode 100644 index 00000000000..b419f6be54d --- /dev/null +++ b/ci.Dockerfile @@ -0,0 +1,53 @@ +# This file is a "runtime" part from a builder-pattern in Dockerfile, it's used in CI. +# The only different part is that the compilation happens externally, +# so COPY has a different source. +FROM docker.io/library/ubuntu:20.04 + +# show backtraces +ENV RUST_BACKTRACE 1 +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -eux; \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + curl ca-certificates libssl-dev && \ + update-ca-certificates && \ + groupadd -g 1000 user && \ + useradd -u 1000 -g user -s /bin/sh -m user && \ + # apt clean up + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# switch to non-root user +USER user + +WORKDIR /home/user + +ARG PROJECT=substrate-relay + +COPY --chown=user:user ./${PROJECT} ./ +COPY --chown=user:user ./bridge-entrypoint.sh ./ + +# check if executable works in this container +RUN ./${PROJECT} --version + +ENV PROJECT=$PROJECT +ENTRYPOINT ["/home/user/bridge-entrypoint.sh"] + +# metadata +ARG VCS_REF=master +ARG BUILD_DATE="" +ARG VERSION="" + +LABEL org.opencontainers.image.title="${PROJECT}" \ + org.opencontainers.image.description="${PROJECT} - component of Parity Bridges Common" \ + org.opencontainers.image.source="https://github.com/paritytech/parity-bridges-common/blob/${VCS_REF}/ci.Dockerfile" \ + org.opencontainers.image.url="https://github.com/paritytech/parity-bridges-common/blob/${VCS_REF}/ci.Dockerfile" \ + org.opencontainers.image.documentation="https://github.com/paritytech/parity-bridges-common/blob/${VCS_REF}/README.md" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.version="${VERSION}" \ + org.opencontainers.image.revision="${VCS_REF}" \ + org.opencontainers.image.authors="devops-team@parity.io" \ + org.opencontainers.image.vendor="Parity Technologies" \ + org.opencontainers.image.licenses="GPL-3.0 License" diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000000..3fa007bbe0a --- /dev/null +++ b/deny.toml @@ -0,0 +1,206 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url of the advisory database to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + # Comes from honggfuzz via storage-proof-fuzzer: 'memmap' + "RUSTSEC-2020-0077", + # time (origin: Substrate RPC + benchmarking crates) + "RUSTSEC-2020-0071", + # chrono (origin: Substrate benchmarking + cli + ...) + "RUSTSEC-2020-0159", + # lru 0.6.6 (origin: libp2p) + "RUSTSEC-2021-0130", + # ansi_term (The maintainer has adviced that this crate is deprecated and will not receive any maintenance. + # Once other crates will move to some alternative, we'll do that too) + "RUSTSEC-2021-0139", + # rocksdb (origin: Substrate and Polkadot kvdb-rocksdb - we need to upgrade soon) + "RUSTSEC-2022-0046", + # owning_ref (origin: Substrate, libp2p) + "RUSTSEC-2022-0040", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "allow" +# List of explictly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +allow = [ + "BlueOak-1.0.0" +] +# List of explictly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "allow" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "either" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.9 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +[[licenses.clarify]] +# The name of the crate the clarification applies to +name = "ring" +# THe optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +expression = "OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + { path = "LICENSE", hash = 0xbd0eed23 } +] + +[[licenses.clarify]] +name = "webpki" +expression = "ISC" +license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "lowest-version" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + { name = "parity-util-mem", version = "<0.6" } + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. +] +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "allow" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] diff --git a/deployments/README.md b/deployments/README.md new file mode 100644 index 00000000000..2f804b3ad32 --- /dev/null +++ b/deployments/README.md @@ -0,0 +1,261 @@ +# Bridge Deployments + +## Requirements +Make sure to install `docker` and `docker-compose` to be able to run and test bridge deployments. If +for whatever reason you can't or don't want to use Docker, you can find some scripts for running the +bridge [here](https://github.com/svyatonik/parity-bridges-common.test). + +## Networks +One of the building blocks we use for our deployments are _networks_. A network is a collection of +homogenous blockchain nodes. We have Docker Compose files for each network that we want to bridge. +Each of the compose files found in the `./networks` folder is able to independently spin up a +network like so: + +```bash +docker-compose -f ./networks/rialto.yml up +``` + +After running this command we would have a network of several nodes producing blocks. + +## Bridges +A _bridge_ is a way for several _networks_ to connect to one another. Bridge deployments have their +own Docker Compose files which can be found in the `./bridges` folder. These Compose files typically +contain bridge relayers, which are services external to blockchain nodes, and other components such +as testing infrastructure, or user interfaces. + +Unlike the network Compose files, these *cannot* be deployed on their own. They must be combined +with different networks. + +In general, we can deploy the bridge using `docker-compose up` in the following way: + +```bash +docker-compose -f .yml \ + -f .yml \ + -f .yml \ + -f .yml up +``` + +If you want to see how the Compose commands are actually run, check out the source code of the +[`./run.sh`](./run.sh). + +One thing worth noting is that we have a _monitoring_ Compose file. This adds support for Prometheus +and Grafana. We cover these in more details in the [Monitoring](#monitoring) section. At the moment +the monitoring Compose file is _not_ optional, and must be included for bridge deployments. + +### Running and Updating Deployments +We currently support three bridge deployments +1. Rialto Substrate to Millau Substrate +2. Rialto Parachain Substrate to Millau Substrate +2. Westend Substrate to Millau Substrate + +These bridges can be deployed using our [`./run.sh`](./run.sh) script. + +The first argument it takes is the name of the bridge you want to run. Right now we only support three +bridges: `rialto-millau`, `rialto-parachain-millau` and `westend-millau`. + +```bash +./run.sh rialto-millau +``` + +If you add a second `update` argument to the script it will pull the latest images from Docker Hub +and restart the deployment. + +```bash +./run.sh rialto-millau update +``` + +You can also bring down a deployment using the script with the `stop` argument. + +```bash +./run.sh rialto-millau stop +``` + +### Adding Deployments +We need two main things when adding a new deployment. First, the new network which we want to +bridge. A compose file for the network should be added in the `/networks/` folder. Secondly we'll +need a new bridge Compose file in `./bridges/`. This should configure the bridge relayer nodes +correctly for the two networks, and add any additional components needed for the deployment. If you +want you can also add support in the `./run` script for the new deployment. While recommended it's +not strictly required. + +## General Notes + +Rialto authorities are named: `Alice`, `Bob`, `Charlie`, `Dave`, `Eve`. +Millau authorities are named: `Alice`, `Bob`, `Charlie`, `Dave`, `Eve`. +RialtoParachain authorities are named: `Alice`, `Bob`. + +`Sudo` is a sudo account on all chains. + +Both authorities and following accounts have enough funds (for test purposes) on corresponding Substrate chains: + +- on Rialto: `Ferdie`. +- on Millau: `Ferdie`. +- on RialtoParachain: `Charlie`, `Dave`, `Eve`, `Ferdie`. + +Names of accounts on Substrate (Rialto and Millau) chains may be prefixed with `//` and used as +seeds for the `sr25519` keys. This seed may also be used in the signer argument in Substrate relays. +Example: + +```bash +./substrate-relay relay-headers rialto-to-millau \ + --source-host rialto-node-alice \ + --source-port 9944 \ + --target-host millau-node-alice \ + --target-port 9944 \ + --source-signer //Ferdie \ + --prometheus-host=0.0.0.0 +``` + +Some accounts are used by bridge components. Using these accounts to sign other transactions +is not recommended, because this may lead to nonces conflict. + +Following accounts are used when `rialto-millau` bridge is running: + +- Millau's `Rialto.HeadersAndMessagesRelay` signs complex headers+messages relay transactions on Millau chain; +- Rialto's `Millau.HeadersAndMessagesRelay` signs complex headers+messages relay transactions on Rialto chain; +- Millau's `Rialto.MessagesSender` signs Millau transactions which contain messages for Rialto; +- Rialto's `Millau.MessagesSender` signs Rialto transactions which contain messages for Millau; +- Millau's `Rialto.OutboundMessagesRelay.Lane00000001` signs relay transactions with message delivery confirmations (lane 00000001) from Rialto to Millau; +- Rialto's `Millau.InboundMessagesRelay.Lane00000001` signs relay transactions with messages (lane 00000001) from Millau to Rialto; +- Millau's `Millau.OutboundMessagesRelay.Lane00000001` signs relay transactions with messages (lane 00000001) from Rialto to Millau; +- Rialto's `Rialto.InboundMessagesRelay.Lane00000001` signs relay transactions with message delivery confirmations (lane 00000001) from Millau to Rialto; +- Millau's `Rialto.MessagesOwner` signs relay transactions with updated Rialto -> Millau conversion rate; +- Rialto's `Millau.MessagesOwner` signs relay transactions with updated Millau -> Rialto conversion rate. + +Following accounts are used when `westend-millau` bridge is running: + +- Millau's `Westend.GrandpaOwner` is signing with-Westend GRANDPA pallet initialization transaction. +- Millau's `Westend.HeadersRelay1` and `Westend.HeadersRelay2` are signing transactions with new Westend headers. +- Millau's `Westend.WestmintHeaders1` and `Westend.WestmintHeaders2` is signing transactions with new Westming headers. + +Following accounts are used when `rialto-parachain-millau` bridge is running: + +- RialtoParachain's `Millau.MessagesSender` signs RialtoParachain transactions which contain messages for Millau; +- Millau's `RialtoParachain.MessagesSender` signs Millau transactions which contain messages for RialtoParachain; +- Millau's `RialtoParachain.HeadersAndMessagesRelay` signs complex headers+parachains+messages relay transactions on Millau chain; +- RialtoParachain's `Millau.HeadersAndMessagesRelay` signs complex headers+messages relay transactions on RialtoParachain chain. + +### Docker Usage +When the network is running you can query logs from individual nodes using: + +```bash +docker logs rialto_millau-node-charlie_1 -f +``` + +To kill all leftover containers and start the network from scratch next time: +```bash +docker ps -a --format "{{.ID}}" | xargs docker rm # This removes all containers! +``` + +### Docker Compose Usage +If you're not familiar with how to use `docker-compose` here are some useful commands you'll need +when interacting with the bridge deployments: + +```bash +docker-compose pull # Get the latest images from the Docker Hub +docker-compose build # This is going to build images +docker-compose up # Start all the nodes +docker-compose up -d # Start the nodes in detached mode. +docker-compose down # Stop the network. +``` + +Note that you'll also need to add the appropriate `-f` arguments that were mentioned in the +[Bridges](#bridges) section. You can read more about using multiple Compose files +[here](https://docs.docker.com/compose/extends/#multiple-compose-files). One thing worth noting is +that the _order_ the compose files are specified in matters. A different order will result in a +different configuration. + +You can sanity check the final config like so: + +```bash +docker-compose -f docker-compose.yml -f docker-compose.override.yml config > docker-compose.merged.yml +``` + +## Docker and Git Deployment +It is also possible to avoid using images from the Docker Hub and instead build +containers from Git. There are two ways to build the images this way. + +### Git Repo +If you have cloned the bridges repo you can build local Docker images by running the following +command at the top level of the repo: + +```bash +docker build . -t local/ --build-arg=PROJECT= +``` + +This will build a local image of a particular component with a tag of +`local/`. This tag can be used in Docker Compose files. + +You can configure the build using Docker +[build arguments](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg). +Here are the arguments currently supported: + - `BRIDGE_REPO`: Git repository of the bridge node and relay code + - `BRIDGE_HASH`: Commit hash within that repo (can also be a branch or tag) + - `ETHEREUM_REPO`: Git repository of the OpenEthereum client + - `ETHEREUM_HASH`: Commit hash within that repo (can also be a branch or tag) + - `PROJECT`: Project to build withing bridges repo. Can be one of: + - `rialto-bridge-node` + - `millau-bridge-node` + - `substrate-relay` + +### GitHub Actions +We have a nightly job which runs and publishes Docker images for the different nodes and relayers to +the [ParityTech Docker Hub](https://hub.docker.com/u/paritytech) organization. These images are used +for our ephemeral (temporary) test networks. Additionally, any time a tag in the form of `v*` is +pushed to GitHub the publishing job is run. This will build all the components (nodes, relayers) and +publish them. + +With images built using either method, all you have to do to use them in a deployment is change the +`image` field in the existing Docker Compose files to point to the tag of the image you want to use. + +### Monitoring +[Prometheus](https://prometheus.io/) is used by the bridge relay to monitor information such as system +resource use, and block data (e.g the best blocks it knows about). In order to visualize this data +a [Grafana](https://grafana.com/) dashboard can be used. + +As part of the Rialto `docker-compose` setup we spin up a Prometheus server and Grafana dashboard. The +Prometheus server connects to the Prometheus data endpoint exposed by the bridge relay. The Grafana +dashboard uses the Prometheus server as its data source. + +The default port for the bridge relay's Prometheus data is `9616`. The host and port can be +configured though the `--prometheus-host` and `--prometheus-port` flags. The Prometheus server's +dashboard can be accessed at `http://localhost:9090`. The Grafana dashboard can be accessed at +`http://localhost:3000`. Note that the default log-in credentials for Grafana are `admin:admin`. + +### Environment Variables +Here is an example `.env` file which is used for production deployments and network updates. For +security reasons it is not kept as part of version control. When deploying a network this +file should be correctly populated and kept in the appropriate [`bridges`](`./bridges`) deployment +folder. + +The `UI_SUBSTRATE_PROVIDER` variable lets you define the url of the Substrate node that the user +interface will connect to. `UI_ETHEREUM_PROVIDER` is used only as a guidance for users to connect +Metamask to the right Ethereum network. `UI_EXPECTED_ETHEREUM_NETWORK_ID` is used by +the user interface as a fail safe to prevent users from connecting their Metamask extension to an +unexpected network. + +```bash +GRAFANA_ADMIN_PASS=admin_pass +GRAFANA_SERVER_ROOT_URL=%(protocol)s://%(domain)s:%(http_port)s/ +GRAFANA_SERVER_DOMAIN=server.domain.io +MATRIX_ACCESS_TOKEN="access-token" +WITH_PROXY=1 # Optional +UI_SUBSTRATE_PROVIDER=ws://localhost:9944 +UI_ETHEREUM_PROVIDER=http://localhost:8545 +UI_EXPECTED_ETHEREUM_NETWORK_ID=105 +``` + +### UI + +Use [wss://rialto.bridges.test-installations.parity.io/](https://polkadot.js.org/apps/) +as a custom endpoint for [https://polkadot.js.org/apps/](https://polkadot.js.org/apps/). + +### Polkadot.js UI + +To teach the UI decode our custom types used in the pallet, go to: `Settings -> Developer` +and import the [`./types.json`](./types.json) + +## Scripts + +There are some bash scripts in `scripts` folder that allow testing `Relay` +without running the entire network within docker. Use if needed for development. diff --git a/deployments/bridges/common/generate_messages.sh b/deployments/bridges/common/generate_messages.sh new file mode 100644 index 00000000000..565b82bc917 --- /dev/null +++ b/deployments/bridges/common/generate_messages.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# Script for generating messages from a source chain to a target chain. +# Prerequisites: mounting the common folder in the docker container (Adding the following volume entry): +# - ./bridges/common:/common +# It can be used by executing `source /common/generate_messages.sh` in a different script, +# after setting the following variables: +# SOURCE_CHAIN +# TARGET_CHAIN +# MAX_SUBMIT_DELAY_S +# SEND_MESSAGE - the command that is executed to send a message +# SECONDARY_EXTRA_ARGS - optional, for example "--use-xcm-pallet" +# EXTRA_ARGS - for example "--use-xcm-pallet" +# REGULAR_PAYLOAD +# BATCH_PAYLOAD +# MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE + +SECONDARY_EXTRA_ARGS=${SECONDARY_EXTRA_ARGS:-""} + +# Sleep a bit between messages +rand_sleep() { + SUBMIT_DELAY_S=`shuf -i 0-$MAX_SUBMIT_DELAY_S -n 1` + echo "Sleeping $SUBMIT_DELAY_S seconds..." + sleep $SUBMIT_DELAY_S + NOW=`date "+%Y-%m-%d %H:%M:%S"` + echo "Woke up at $NOW" +} + +# start sending large messages immediately +LARGE_MESSAGES_TIME=0 +# start sending message packs in a hour +BUNCH_OF_MESSAGES_TIME=3600 + +while true +do + rand_sleep + + # send regular message + echo "Sending Message from $SOURCE_CHAIN to $TARGET_CHAIN" + $SEND_MESSAGE $EXTRA_ARGS raw $REGULAR_PAYLOAD + + # every other hour we're sending 3 large (size, weight, size+weight) messages + if [ $SECONDS -ge $LARGE_MESSAGES_TIME ]; then + LARGE_MESSAGES_TIME=$((SECONDS + 7200)) + + rand_sleep + echo "Sending Maximal Size Message from $SOURCE_CHAIN to $TARGET_CHAIN" + $SEND_MESSAGE \ + sized max + fi + + # every other hour we're sending a bunch of small messages + if [ $SECONDS -ge $BUNCH_OF_MESSAGES_TIME ]; then + BUNCH_OF_MESSAGES_TIME=$((SECONDS + 7200)) + + for i in $(seq 0 $MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE); + do + $SEND_MESSAGE \ + $EXTRA_ARGS \ + raw $BATCH_PAYLOAD + done + + fi +done diff --git a/deployments/bridges/rialto-millau/dashboard/grafana/relay-millau-to-rialto-messages-dashboard.json b/deployments/bridges/rialto-millau/dashboard/grafana/relay-millau-to-rialto-messages-dashboard.json new file mode 100644 index 00000000000..af8749325de --- /dev/null +++ b/deployments/bridges/rialto-millau/dashboard/grafana/relay-millau-to-rialto-messages-dashboard.json @@ -0,0 +1,1153 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Millau_to_Rialto_MessageLane_00000000_best_target_block_number", + "instant": false, + "interval": "", + "legendFormat": "At Rialto", + "refId": "A" + }, + { + "expr": "Millau_to_Rialto_MessageLane_00000000_best_target_at_source_block_number", + "instant": false, + "interval": "", + "legendFormat": "At Millau", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Best finalized Rialto headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Millau_to_Rialto_MessageLane_00000000_best_source_block_number", + "interval": "", + "legendFormat": "At Millau", + "refId": "A" + }, + { + "expr": "Millau_to_Rialto_MessageLane_00000000_best_source_at_target_block_number", + "interval": "", + "legendFormat": "At Rialto", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Best finalized Millau headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Millau -> Rialto messages are not detected by relay", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "label_replace(label_replace(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=~\"source_latest_generated|target_latest_received\"}, \"type\", \"Latest message sent from Rialto\", \"type\", \"source_latest_generated\"), \"type\", \"Latest Rialto message received by Millau\", \"type\", \"target_latest_received\")", + "interval": "", + "legendFormat": "{{type}}", + "refId": "A" + }, + { + "expr": "increase(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"source_latest_generated\"}[10m]) OR on() vector(0)", + "hide": true, + "interval": "", + "legendFormat": "Messages generated in last 5 minutes (Millau -> Rialto, 00000000)", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Delivery race (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "label_replace(label_replace(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=~\"source_latest_confirmed|target_latest_received\"}, \"type\", \"Latest message confirmed by Rialto to Millau\", \"type\", \"source_latest_confirmed\"), \"type\", \"Latest Rialto message received by Millau\", \"type\", \"target_latest_received\")", + "interval": "", + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Confirmations race (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "1m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "sum" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "7m", + "frequency": "1m", + "handler": 1, + "name": "Messages from Millau to Rialto are not being delivered", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 20 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"source_latest_generated\"}[2m])) - scalar(max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[2m]))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "Undelivered messages at Rialto", + "refId": "A" + }, + { + "expr": "increase(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[5m]) OR on() vector(0)", + "interval": "", + "legendFormat": "Millau Messages delivered to Rialto in last 5m", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Delivery race lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 50 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Too many unconfirmed messages (Millau -> Rialto)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 20 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[2m]) OR on() vector(0)) - scalar(max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"source_latest_confirmed\"}[2m]) OR on() vector(0))", + "interval": "", + "legendFormat": "Unconfirmed messages at Millau", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Confirmations race lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 10 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Rewards are not being confirmed (Millau -> Rialto messages)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 20 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"source_latest_confirmed\"}[2m])) - scalar(max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"target_latest_confirmed\"}[2m]))", + "interval": "", + "legendFormat": "Unconfirmed rewards at Rialto", + "refId": "A" + }, + { + "expr": "(scalar(max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"source_latest_confirmed\"}[2m]) OR on() vector(0)) - scalar(max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"target_latest_confirmed\"}[2m]) OR on() vector(0))) * (max_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[2m]) > bool min_over_time(Millau_to_Rialto_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[2m]))", + "interval": "", + "legendFormat": "Unconfirmed rewards at Millau->Rialto (zero if messages are not being delivered to Rialto)", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Reward lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 38 + }, + "id": 16, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "7.1.3", + "targets": [ + { + "expr": "avg_over_time(process_cpu_usage_percentage{instance='relay-millau-rialto:9616'}[1m])", + "instant": true, + "interval": "", + "legendFormat": "1 CPU = 100", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Relay process CPU usage (1 CPU = 100)", + "type": "gauge" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 38 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "system_average_load{instance='relay-millau-rialto:9616'}", + "interval": "", + "legendFormat": "Average system load in last {{over}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "System load average", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 38 + }, + "hiddenSeries": false, + "id": 20, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_memory_usage_bytes{instance='relay-millau-rialto:9616'} / 1024 / 1024", + "interval": "", + "legendFormat": "Process memory, MB", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory used by relay process", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Millau to Rialto Message Sync Dashboard", + "uid": "relay-millau-to-rialto-messages", + "version": 1 +} diff --git a/deployments/bridges/rialto-millau/dashboard/grafana/relay-rialto-to-millau-messages-dashboard.json b/deployments/bridges/rialto-millau/dashboard/grafana/relay-rialto-to-millau-messages-dashboard.json new file mode 100644 index 00000000000..efee749c5c1 --- /dev/null +++ b/deployments/bridges/rialto-millau/dashboard/grafana/relay-rialto-to-millau-messages-dashboard.json @@ -0,0 +1,1145 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 4, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Rialto_to_Millau_MessageLane_00000000_best_target_block_number", + "instant": false, + "interval": "", + "legendFormat": "At Millau", + "refId": "A" + }, + { + "expr": "Rialto_to_Millau_MessageLane_00000000_best_target_at_source_block_number", + "instant": false, + "interval": "", + "legendFormat": "At Rialto", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Best finalized Millau headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Rialto_to_Millau_MessageLane_00000000_best_source_block_number", + "interval": "", + "legendFormat": "At Rialto", + "refId": "A" + }, + { + "expr": "Rialto_to_Millau_MessageLane_00000000_best_source_at_target_block_number", + "interval": "", + "legendFormat": "At Millau", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Best finalized Rialto headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Rialto -> Millau messages are not detected by relay", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "label_replace(label_replace(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=~\"source_latest_generated|target_latest_received\"}, \"type\", \"Latest message sent from Millau\", \"type\", \"source_latest_generated\"), \"type\", \"Latest message received by Rialto\", \"type\", \"target_latest_received\")", + "interval": "", + "legendFormat": "{{type}}", + "refId": "A" + }, + { + "expr": "increase(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"source_latest_generated\"}[10m]) OR on() vector(0)", + "hide": true, + "interval": "", + "legendFormat": "Messages generated in last 5 minutes (Rialto -> Millau, 00000000)", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Delivery race (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "label_replace(label_replace(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=~\"source_latest_confirmed|target_latest_received\"}, \"type\", \"Latest message confirmed by Millau to Rialto\", \"type\", \"source_latest_confirmed\"), \"type\", \"Latest message received by Rialto\", \"type\", \"target_latest_received\")", + "interval": "", + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Confirmations race (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "1m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "sum" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "7m", + "frequency": "1m", + "handler": 1, + "name": "Messages from Rialto to Millau are not being delivered", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 20 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"source_latest_generated\"}[2m])) - scalar(max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[2m]))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "Undelivered messages at Millau", + "refId": "A" + }, + { + "expr": "increase(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[5m]) OR on() vector(0)", + "interval": "", + "legendFormat": "Rialto Messages delivered to Millau in last 5m", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Delivery race lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 50 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Too many unconfirmed messages (Rialto -> Millau)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 20 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[2m]) OR on() vector(0)) - scalar(max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"source_latest_confirmed\"}[2m]) OR on() vector(0))", + "interval": "", + "legendFormat": "Unconfirmed messages at Rialto", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Confirmations race lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 10 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Rewards are not being confirmed (Rialto -> Millau messages)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 20 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"source_latest_confirmed\"}[2m])) - scalar(max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"target_latest_confirmed\"}[2m]))", + "interval": "", + "legendFormat": "Unconfirmed rewards at Millau", + "refId": "A" + }, + { + "expr": "(scalar(max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"source_latest_confirmed\"}[2m]) OR on() vector(0)) - scalar(max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"target_latest_confirmed\"}[2m]) OR on() vector(0))) * (max_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[2m]) > bool min_over_time(Rialto_to_Millau_MessageLane_00000000_lane_state_nonces{type=\"target_latest_received\"}[2m]))", + "interval": "", + "legendFormat": "Unconfirmed rewards at Millau (zero if messages are not being delivered to Millau)", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Reward lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 38 + }, + "id": 16, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "7.1.3", + "targets": [ + { + "expr": "avg_over_time(process_cpu_usage_percentage{instance='relay-millau-rialto:9616'}[1m])", + "instant": true, + "interval": "", + "legendFormat": "1 CPU = 100", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Relay process CPU usage (1 CPU = 100)", + "type": "gauge" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 38 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "system_average_load{instance='relay-millau-rialto:9616'}", + "interval": "", + "legendFormat": "Average system load in last {{over}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "System load average", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 38 + }, + "hiddenSeries": false, + "id": 20, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_memory_usage_bytes{instance='relay-millau-rialto:9616'} / 1024 / 1024", + "interval": "", + "legendFormat": "Process memory, MB", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory used by relay process", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Rialto to Millau Message Sync Dashboard", + "uid": "relay-rialto-to-millau-messages", + "version": 2 +} diff --git a/deployments/bridges/rialto-millau/dashboard/grafana/rialto-millau-maintenance-dashboard.json b/deployments/bridges/rialto-millau/dashboard/grafana/rialto-millau-maintenance-dashboard.json new file mode 100644 index 00000000000..e4695351105 --- /dev/null +++ b/deployments/bridges/rialto-millau/dashboard/grafana/rialto-millau-maintenance-dashboard.json @@ -0,0 +1,791 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "C", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "At-Rialto relay balances are too low", + "noDataState": "ok", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "at_Rialto_relay_MillauHeaders_balance", + "interval": "", + "legendFormat": "With-Millau headers relay account balance", + "refId": "A" + }, + { + "expr": "at_Rialto_relay_MillauMessages_balance", + "interval": "", + "legendFormat": "With-Millau messages relay account balance", + "refId": "B" + }, + { + "expr": "at_Rialto_relay_MillauMessagesPalletOwner_balance", + "interval": "", + "legendFormat": "With-Millau messages pallet owner account balance", + "refId": "C" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1000 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Rialto relay balances", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "C", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "At-Millau relay balances are too low", + "noDataState": "ok", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "hiddenSeries": false, + "id": 9, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "at_Millau_relay_RialtoHeaders_balance{instance=\"relay-millau-rialto:9616\"}", + "interval": "", + "legendFormat": "With-Rialto headers relay account balance", + "refId": "A" + }, + { + "expr": "at_Millau_relay_RialtoMessages_balance", + "interval": "", + "legendFormat": "With-Rialto messages relay account balance", + "refId": "B" + }, + { + "expr": "at_Millau_relay_RialtoMessagesPalletOwner_balance", + "interval": "", + "legendFormat": "With-Rialto messages pallet owner account balance", + "refId": "C" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1000 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Millau relay balances", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Whether with-Rialto-grandpa-pallet and Rialto itself are on different forks alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "hiddenSeries": false, + "id": 11, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Rialto_to_Millau_MessageLane_00000000_is_source_and_source_at_target_using_different_forks OR on() vector(0)", + "interval": "", + "legendFormat": "On different forks?", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Whether with-Rialto-grandpa-pallet and Rialto itself are on different forks", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Whether with-Rialto-grandpa-pallet and Rialto itself are on different forks alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Millau_to_Rialto_MessageLane_00000000_is_source_and_source_at_target_using_different_forks OR on() vector(0)", + "interval": "", + "legendFormat": "On different forks?", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Whether with-Millau-grandpa-pallet and Millau itself are on different forks", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Millau_to_Rialto_MessageLane_00000000_unprofitable_delivery_transactions", + "interval": "", + "legendFormat": "Millau -> Rialto, lane 00000000", + "refId": "A" + }, + { + "expr": "Rialto_to_Millau_MessageLane_00000000_unprofitable_delivery_transactions", + "interval": "", + "legendFormat": "Rialto -> Millau, lane 00000000", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Count of unprofitable message delivery transactions", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "10s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Rialto+Millau maintenance dashboard", + "uid": "7AuyrjlMz", + "version": 1 +} diff --git a/deployments/bridges/rialto-millau/dashboard/prometheus/targets.yml b/deployments/bridges/rialto-millau/dashboard/prometheus/targets.yml new file mode 100644 index 00000000000..16b798b5a25 --- /dev/null +++ b/deployments/bridges/rialto-millau/dashboard/prometheus/targets.yml @@ -0,0 +1,4 @@ +- targets: + - relay-millau-rialto:9616 + - relay-messages-millau-to-rialto-lane-00000001:9616 + - relay-messages-rialto-to-millau-lane-00000001:9616 diff --git a/deployments/bridges/rialto-millau/docker-compose.yml b/deployments/bridges/rialto-millau/docker-compose.yml new file mode 100644 index 00000000000..cd77dbbf122 --- /dev/null +++ b/deployments/bridges/rialto-millau/docker-compose.yml @@ -0,0 +1,110 @@ +# Exposed ports: 10016, 10116, 10216, 10316, 10416, 10516, 10716 + +version: '3.5' +services: + # We provide overrides for these particular nodes since they are public facing + # nodes which we use to connect from things like Polkadot JS Apps. + rialto-node-charlie: + environment: + VIRTUAL_HOST: wss.rialto.brucke.link + VIRTUAL_PORT: 9944 + LETSENCRYPT_HOST: wss.rialto.brucke.link + LETSENCRYPT_EMAIL: admin@parity.io + + millau-node-charlie: + environment: + VIRTUAL_HOST: wss.millau.brucke.link + VIRTUAL_PORT: 9944 + LETSENCRYPT_HOST: wss.millau.brucke.link + LETSENCRYPT_EMAIL: admin@parity.io + + relay-millau-rialto: &sub-bridge-relay + image: ${SUBSTRATE_RELAY_IMAGE:-paritytech/substrate-relay} + entrypoint: /entrypoints/relay-millau-rialto-entrypoint.sh + volumes: + - ./bridges/common:/common + - ./bridges/rialto-millau/entrypoints:/entrypoints + environment: + RUST_LOG: rpc=trace,bridge=trace + ports: + - "10016:9616" + depends_on: &all-nodes + - millau-node-alice + - millau-node-bob + - millau-node-charlie + - millau-node-dave + - millau-node-eve + - rialto-node-alice + - rialto-node-bob + - rialto-node-charlie + - rialto-node-dave + - rialto-node-eve + + relay-messages-millau-to-rialto-lane-00000001: + <<: *sub-bridge-relay + environment: + MSG_EXCHANGE_GEN_LANE: "00000001" + entrypoint: /entrypoints/relay-messages-millau-to-rialto-entrypoint.sh + ports: + - "10116:9616" + depends_on: + - relay-millau-rialto + + relay-messages-millau-to-rialto-generator: + <<: *sub-bridge-relay + environment: + RUST_LOG: bridge=trace + MSG_EXCHANGE_GEN_SECONDARY_LANE: "00000001" + entrypoint: /entrypoints/relay-messages-to-rialto-generator-entrypoint.sh + ports: + - "10216:9616" + depends_on: + - relay-millau-rialto + + relay-messages-millau-to-rialto-resubmitter: + <<: *sub-bridge-relay + environment: + RUST_LOG: bridge=trace + entrypoint: /entrypoints/relay-messages-to-rialto-resubmitter-entrypoint.sh + ports: + - "10316:9616" + depends_on: + - relay-messages-millau-to-rialto-generator + + relay-messages-rialto-to-millau-lane-00000001: + <<: *sub-bridge-relay + environment: + RUST_LOG: bridge=trace + MSG_EXCHANGE_GEN_LANE: "00000001" + entrypoint: /entrypoints/relay-messages-rialto-to-millau-entrypoint.sh + ports: + - "10416:9616" + depends_on: + - relay-millau-rialto + + relay-messages-rialto-to-millau-generator: + <<: *sub-bridge-relay + environment: + RUST_LOG: bridge=trace + MSG_EXCHANGE_GEN_SECONDARY_LANE: "00000001" + entrypoint: /entrypoints/relay-messages-to-millau-generator-entrypoint.sh + ports: + - "10516:9616" + depends_on: + - relay-millau-rialto + + # Note: These are being overridden from the top level `monitoring` compose file. + grafana-dashboard: + environment: + VIRTUAL_HOST: grafana.millau.brucke.link,grafana.rialto.brucke.link + VIRTUAL_PORT: 3000 + LETSENCRYPT_HOST: grafana.millau.brucke.link,grafana.rialto.brucke.link + LETSENCRYPT_EMAIL: admin@parity.io + volumes: + - ./bridges/rialto-millau/dashboard/grafana:/etc/grafana/dashboards/rialto-millau:ro + - ./networks/dashboard/grafana/beefy-dashboard.json:/etc/grafana/dashboards/beefy.json + + prometheus-metrics: + volumes: + - ./bridges/rialto-millau/dashboard/prometheus/targets.yml:/etc/prometheus/targets-rialto-millau.yml + depends_on: *all-nodes diff --git a/deployments/bridges/rialto-millau/entrypoints/relay-messages-millau-to-rialto-entrypoint.sh b/deployments/bridges/rialto-millau/entrypoints/relay-messages-millau-to-rialto-entrypoint.sh new file mode 100755 index 00000000000..3a55cc782ca --- /dev/null +++ b/deployments/bridges/rialto-millau/entrypoints/relay-messages-millau-to-rialto-entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -xeu + +sleep 15 + +MESSAGE_LANE=${MSG_EXCHANGE_GEN_LANE:-00000000} + +/home/user/substrate-relay relay-messages millau-to-rialto \ + --lane $MESSAGE_LANE \ + --source-host millau-node-bob \ + --source-port 9944 \ + --source-signer //Rialto.OutboundMessagesRelay.Lane00000001 \ + --target-host rialto-node-bob \ + --target-port 9944 \ + --target-signer //Millau.InboundMessagesRelay.Lane00000001 \ + --prometheus-host=0.0.0.0 diff --git a/deployments/bridges/rialto-millau/entrypoints/relay-messages-rialto-to-millau-entrypoint.sh b/deployments/bridges/rialto-millau/entrypoints/relay-messages-rialto-to-millau-entrypoint.sh new file mode 100755 index 00000000000..145cd20100d --- /dev/null +++ b/deployments/bridges/rialto-millau/entrypoints/relay-messages-rialto-to-millau-entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -xeu + +sleep 15 + +MESSAGE_LANE=${MSG_EXCHANGE_GEN_LANE:-00000000} + +/home/user/substrate-relay relay-messages rialto-to-millau \ + --lane $MESSAGE_LANE \ + --source-host rialto-node-bob \ + --source-port 9944 \ + --source-signer //Millau.OutboundMessagesRelay.Lane00000001 \ + --target-host millau-node-bob \ + --target-port 9944 \ + --target-signer //Rialto.InboundMessagesRelay.Lane00000001 \ + --prometheus-host=0.0.0.0 diff --git a/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-millau-generator-entrypoint.sh b/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-millau-generator-entrypoint.sh new file mode 100755 index 00000000000..790e9c82110 --- /dev/null +++ b/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-millau-generator-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# THIS SCRIPT IS NOT INTENDED FOR USE IN PRODUCTION ENVIRONMENT +# +# This scripts periodically calls the Substrate relay binary to generate messages. These messages +# are sent from the Rialto network to the Millau network. + +set -eu + +# Max delay before submitting transactions (s) +MAX_SUBMIT_DELAY_S=${MSG_EXCHANGE_GEN_MAX_SUBMIT_DELAY_S:-30} +MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE=1024 + +SHARED_CMD="/home/user/substrate-relay send-message rialto-to-millau" +SHARED_HOST="--source-host rialto-node-bob --source-port 9944" +SOURCE_SIGNER="--source-signer //Millau.MessagesSender" + +SEND_MESSAGE="$SHARED_CMD $SHARED_HOST $SOURCE_SIGNER" + +SOURCE_CHAIN="Rialto" +TARGET_CHAIN="Millau" +EXTRA_ARGS="" +REGULAR_PAYLOAD="020419ac" +BATCH_PAYLOAD="020419ac" + +source /common/generate_messages.sh diff --git a/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-rialto-generator-entrypoint.sh b/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-rialto-generator-entrypoint.sh new file mode 100755 index 00000000000..3f3105a4d0e --- /dev/null +++ b/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-rialto-generator-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# THIS SCRIPT IS NOT INTENDED FOR USE IN PRODUCTION ENVIRONMENT +# +# This scripts periodically calls the Substrate relay binary to generate messages. These messages +# are sent from the Millau network to the Rialto network. + +set -eu + +# Max delay before submitting transactions (s) +MAX_SUBMIT_DELAY_S=${MSG_EXCHANGE_GEN_MAX_SUBMIT_DELAY_S:-30} +MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE=128 + +SHARED_CMD=" /home/user/substrate-relay send-message millau-to-rialto" +SHARED_HOST="--source-host millau-node-bob --source-port 9944" +SOURCE_SIGNER="--source-signer //Rialto.MessagesSender" + +SEND_MESSAGE="$SHARED_CMD $SHARED_HOST $SOURCE_SIGNER" + +SOURCE_CHAIN="Millau" +TARGET_CHAIN="Rialto" +EXTRA_ARGS="" +REGULAR_PAYLOAD="020419ac" +BATCH_PAYLOAD="020419ac" + +source /common/generate_messages.sh diff --git a/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-rialto-resubmitter-entrypoint.sh b/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-rialto-resubmitter-entrypoint.sh new file mode 100755 index 00000000000..bd9bf800ad4 --- /dev/null +++ b/deployments/bridges/rialto-millau/entrypoints/relay-messages-to-rialto-resubmitter-entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -xeu + +sleep 15 + +# //Rialto.MessagesSender is signing Millau -> Rialto message-send transactions, which are causing problems. +# +# When large message is being sent from Millau to Rialto AND other transactions are +# blocking it from being mined, we'll see something like this in logs: +# +# Millau transaction priority with tip=0: 17800827994. Target priority: +# 526186677695 +# +# So since fee multiplier in Millau is `1` and `WeightToFee` is `IdentityFee`, then +# we need tip around `526186677695 - 17800827994 = 508_385_849_701`. Let's round it +# up to `1_000_000_000_000`. + +/home/user/substrate-relay resubmit-transactions millau \ + --target-host millau-node-alice \ + --target-port 9944 \ + --target-signer //Rialto.MessagesSender \ + --stalled-blocks 5 \ + --tip-limit 1000000000000 \ + --tip-step 1000000000 \ + make-it-best-transaction diff --git a/deployments/bridges/rialto-millau/entrypoints/relay-millau-rialto-entrypoint.sh b/deployments/bridges/rialto-millau/entrypoints/relay-millau-rialto-entrypoint.sh new file mode 100755 index 00000000000..367792121de --- /dev/null +++ b/deployments/bridges/rialto-millau/entrypoints/relay-millau-rialto-entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -xeu + +sleep 15 + +/home/user/substrate-relay init-bridge millau-to-rialto \ + --source-host millau-node-alice \ + --source-port 9944 \ + --target-host rialto-node-alice \ + --target-port 9944 \ + --target-signer //Sudo + +/home/user/substrate-relay init-bridge rialto-to-millau \ + --source-host rialto-node-alice \ + --source-port 9944 \ + --target-host millau-node-alice \ + --target-port 9944 \ + --target-signer //Sudo + +# Give chain a little bit of time to process initialization transaction +sleep 6 + +/home/user/substrate-relay relay-headers-and-messages millau-rialto \ + --millau-host millau-node-alice \ + --millau-port 9944 \ + --millau-signer //Rialto.HeadersAndMessagesRelay \ + --millau-messages-pallet-owner=//Rialto.MessagesOwner \ + --millau-transactions-mortality=64 \ + --rialto-host rialto-node-alice \ + --rialto-port 9944 \ + --rialto-signer //Millau.HeadersAndMessagesRelay \ + --rialto-messages-pallet-owner=//Millau.MessagesOwner \ + --rialto-transactions-mortality=64 \ + --lane=00000000 \ + --lane=73776170 \ + --prometheus-host=0.0.0.0 diff --git a/deployments/bridges/rialto-parachain-millau/dashboard/grafana/relay-millau-to-rialto-parachain-messages-dashboard.json b/deployments/bridges/rialto-parachain-millau/dashboard/grafana/relay-millau-to-rialto-parachain-messages-dashboard.json new file mode 100644 index 00000000000..b20348cc513 --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/dashboard/grafana/relay-millau-to-rialto-parachain-messages-dashboard.json @@ -0,0 +1,910 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Millau_to_RialtoParachain_MessageLane_00000000_best_target_block_number{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "instant": false, + "interval": "", + "legendFormat": "At RialtoParachain", + "refId": "A" + }, + { + "expr": "Millau_to_RialtoParachain_MessageLane_00000000_best_target_at_source_block_number{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "instant": false, + "interval": "", + "legendFormat": "At Millau", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Best finalized RialtoParachain headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Millau_to_RialtoParachain_MessageLane_00000000_best_source_block_number{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "At Millau", + "refId": "A" + }, + { + "expr": "Millau_to_RialtoParachain_MessageLane_00000000_best_source_at_target_block_number{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "At RialtoParachain", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Best finalized Millau headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Millau -> RialtoParachain messages are not detected by relay", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "label_replace(label_replace(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=~\"source_latest_generated|target_latest_received\"}, \"type\", \"Latest message sent from RialtoParachain\", \"type\", \"source_latest_generated\"), \"type\", \"Latest RialtoParachain message received by Millau\", \"type\", \"target_latest_received\")", + "interval": "", + "legendFormat": "{{type}}", + "refId": "A" + }, + { + "expr": "increase(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_generated\"}[10m]) OR on() vector(0)", + "hide": true, + "interval": "", + "legendFormat": "Messages generated in last 5 minutes (Millau -> RialtoParachain, 00000000)", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Delivery race (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "label_replace(label_replace(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=~\"source_latest_confirmed|target_latest_received\"}, \"type\", \"Latest message confirmed by RialtoParachain to Millau\", \"type\", \"source_latest_confirmed\"), \"type\", \"Latest RialtoParachain message received by Millau\", \"type\", \"target_latest_received\")", + "interval": "", + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Confirmations race (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "1m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "sum" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "7m", + "frequency": "1m", + "handler": 1, + "name": "Messages from Millau to RialtoParachain are not being delivered", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 20 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_generated\"}[2m])) - scalar(max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[2m]))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "Undelivered messages at RialtoParachain", + "refId": "A" + }, + { + "expr": "increase(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[5m]) OR on() vector(0)", + "interval": "", + "legendFormat": "Millau Messages delivered to RialtoParachain in last 5m", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Delivery race lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 50 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Too many unconfirmed messages (Millau -> RialtoParachain)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 20 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[2m]) OR on() vector(0)) - scalar(max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_confirmed\"}[2m]) OR on() vector(0))", + "interval": "", + "legendFormat": "Unconfirmed messages at Millau", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Confirmations race lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 10 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Rewards are not being confirmed (Millau -> RialtoParachain messages)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 20 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_confirmed\"}[2m])) - scalar(max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_confirmed\"}[2m]))", + "interval": "", + "legendFormat": "Unconfirmed rewards at RialtoParachain", + "refId": "A" + }, + { + "expr": "(scalar(max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_confirmed\"}[2m]) OR on() vector(0)) - scalar(max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_confirmed\"}[2m]) OR on() vector(0))) * (max_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[2m]) > bool min_over_time(Millau_to_RialtoParachain_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[2m]))", + "interval": "", + "legendFormat": "Unconfirmed rewards at Millau->RaltoParachain (zero if messages are not being delivered to RialtoParachain)", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Reward lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Millau to RialtoParachain Message Sync Dashboard", + "uid": "C61e-797z", + "version": 1 +} diff --git a/deployments/bridges/rialto-parachain-millau/dashboard/grafana/relay-rialto-parachain-to-millau-messages-dashboard.json b/deployments/bridges/rialto-parachain-millau/dashboard/grafana/relay-rialto-parachain-to-millau-messages-dashboard.json new file mode 100644 index 00000000000..064436145de --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/dashboard/grafana/relay-rialto-parachain-to-millau-messages-dashboard.json @@ -0,0 +1,908 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "RialtoParachain_to_Millau_MessageLane_00000000_best_target_block_number{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "instant": false, + "interval": "", + "legendFormat": "At Millau", + "refId": "A" + }, + { + "expr": "RialtoParachain_to_Millau_MessageLane_00000000_best_target_at_source_block_number{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "instant": false, + "interval": "", + "legendFormat": "At RialtoParachain", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Best finalized Millau headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "RialtoParachain_to_Millau_MessageLane_00000000_best_source_block_number{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "At RialtoParachain", + "refId": "A" + }, + { + "expr": "RialtoParachain_to_Millau_MessageLane_00000000_best_source_at_target_block_number{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "At Millau", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Best finalized RialtoParachain headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "RialtoParachain -> Millau messages are not detected by relay", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "label_replace(label_replace(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=~\"source_latest_generated|target_latest_received\"}, \"type\", \"Latest message sent from Millau\", \"type\", \"source_latest_generated\"), \"type\", \"Latest Millau message received by RialtoParachain\", \"type\", \"target_latest_received\")", + "interval": "", + "legendFormat": "{{type}}", + "refId": "A" + }, + { + "expr": "increase(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_generated\"}[10m]) OR on() vector(0)", + "hide": true, + "interval": "", + "legendFormat": "Messages generated in last 5 minutes (RialtoParachain -> Millau, 00000000)", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Delivery race (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "label_replace(label_replace(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=~\"source_latest_confirmed|target_latest_received\"}, \"type\", \"Latest message confirmed by Millau to RialtoParachain\", \"type\", \"source_latest_confirmed\"), \"type\", \"Latest Millau message received by RialtoParachain\", \"type\", \"target_latest_received\")", + "interval": "", + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Confirmations race (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "1m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "sum" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "7m", + "frequency": "1m", + "handler": 1, + "name": "Messages from RialtoParachain to Millau are not being delivered", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 20 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_generated\"}[2m])) - scalar(max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[2m]))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "Undelivered messages at Millau", + "refId": "A" + }, + { + "expr": "increase(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[5m]) OR on() vector(0)", + "interval": "", + "legendFormat": "RialtoParachain Messages delivered to Millau in last 5m", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Delivery race lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 50 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Too many unconfirmed messages (RialtoParachain -> Millau)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 20 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[2m]) OR on() vector(0)) - scalar(max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_confirmed\"}[2m]) OR on() vector(0))", + "interval": "", + "legendFormat": "Unconfirmed messages at RialtoParachain", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Confirmations race lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 10 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "20m", + "frequency": "1m", + "handler": 1, + "name": "Rewards are not being confirmed (RialtoParachain -> Millau messages)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 20 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scalar(max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_confirmed\"}[2m])) - scalar(max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_confirmed\"}[2m]))", + "interval": "", + "legendFormat": "Unconfirmed rewards at Millau", + "refId": "A" + }, + { + "expr": "(scalar(max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"source_latest_confirmed\"}[2m]) OR on() vector(0)) - scalar(max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_confirmed\"}[2m]) OR on() vector(0))) * (max_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[2m]) > bool min_over_time(RialtoParachain_to_Millau_MessageLane_00000000_lane_state_nonces{instance=\"relay-millau-rialto-parachain-1:9616\",type=\"target_latest_received\"}[2m]))", + "interval": "", + "legendFormat": "Unconfirmed rewards at Millau (zero if messages are not being delivered to Millau)", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Reward lags (00000000)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "RialtoParachain to Millau Message Sync Dashboard", + "uid": "-l61a7r7k", + "version": 1 +} diff --git a/deployments/bridges/rialto-parachain-millau/dashboard/grafana/rialto-parachain-millau-maintenance-dashboard.json b/deployments/bridges/rialto-parachain-millau/dashboard/grafana/rialto-parachain-millau-maintenance-dashboard.json new file mode 100644 index 00000000000..c125b08df32 --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/dashboard/grafana/rialto-parachain-millau-maintenance-dashboard.json @@ -0,0 +1,713 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "C", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "At-Rialto relay balances are too low", + "noDataState": "ok", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "at_RialtoParachain_relay_MillauHeaders_balance{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "With-Millau headers relay account balance", + "refId": "A" + }, + { + "expr": "at_RialtoParachain_relay_MillauMessages_balance{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "With-Millau messages relay account balance", + "refId": "B" + }, + { + "expr": "at_RialtoParachain_relay_MillauMessagesPalletOwner_balance{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "With-Millau messages pallet owner account balance", + "refId": "C" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1000 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "RialtoParachain relay balances", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1000 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "C", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "At-Millau relay balances are too low", + "noDataState": "ok", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "at_Millau_relay_RialtoHeaders_balance{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "With-Rialto headers relay account balance", + "refId": "A" + }, + { + "expr": "at_Millau_relay_RialtoParachainMessages_balance{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "With-RialtoParachain messages relay account balance", + "refId": "B" + }, + { + "expr": "at_Millau_relay_RialtoParachains_balance{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "With-Rialto parachains relay account balance", + "refId": "C" + }, + { + "expr": "at_Millau_relay_RialtoParachainMessagesPalletOwner_balance{instance=\"relay-millau-rialto-parachain-1:9616\"}", + "interval": "", + "legendFormat": "With-RialtoParachain messages pallet owner account balance", + "refId": "D" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1000 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Millau relay balances", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Whether with-RialtoParachain-parachains-pallet and Rialto itself are on different forks alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "RialtoParachain_to_Millau_MessageLane_00000000_is_source_and_source_at_target_using_different_forks{instance=\"relay-millau-rialto-parachain-1:9616\"} OR on() vector(0)", + "instant": false, + "interval": "", + "legendFormat": "On different forks?", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Whether with-RialtoParachain-parachains-pallet and RialtoParachain itself are on different forks", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Whether with-Millau-grandpa-pallet and Millau itself are on different forks", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Millau_to_RialtoParachain_MessageLane_00000000_is_source_and_source_at_target_using_different_forks{instance=\"relay-millau-rialto-parachain-1:9616\"} OR on() vector(0)", + "interval": "", + "legendFormat": "On different forks?", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Whether with-Millau-grandpa-pallet and Millau itself are on different forks", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "RialtoParachain+Millau maintenance dashboard", + "uid": "WALc3ajnk", + "version": 1 +} diff --git a/deployments/bridges/rialto-parachain-millau/dashboard/prometheus/targets.yml b/deployments/bridges/rialto-parachain-millau/dashboard/prometheus/targets.yml new file mode 100644 index 00000000000..ba683e3479a --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/dashboard/prometheus/targets.yml @@ -0,0 +1,3 @@ +- targets: + - relay-millau-rialto-parachain-1:9616 + - relay-millau-rialto-parachain-2:9616 diff --git a/deployments/bridges/rialto-parachain-millau/docker-compose.yml b/deployments/bridges/rialto-parachain-millau/docker-compose.yml new file mode 100644 index 00000000000..a62bda52eb3 --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/docker-compose.yml @@ -0,0 +1,90 @@ +# Exposed ports: 10816, 10916, 11016, 11017, 11018 + +version: '3.5' +services: + # We provide overrides for these particular nodes since they are public facing + # nodes which we use to connect from things like Polkadot JS Apps. + rialto-parachain-collator-charlie: + environment: + VIRTUAL_HOST: wss.rialto-parachain.brucke.link + VIRTUAL_PORT: 9944 + LETSENCRYPT_HOST: wss.rialto-parachain.brucke.link + LETSENCRYPT_EMAIL: admin@parity.io + + millau-node-charlie: + environment: + VIRTUAL_HOST: wss.millau.brucke.link + VIRTUAL_PORT: 9944 + LETSENCRYPT_HOST: wss.millau.brucke.link + LETSENCRYPT_EMAIL: admin@parity.io + + relay-millau-rialto-parachain-1: &sub-bridge-relay + image: ${SUBSTRATE_RELAY_IMAGE:-paritytech/substrate-relay} + entrypoint: /entrypoints/relay-millau-rialto-parachain-entrypoint.sh + volumes: + - ./bridges/common:/common + - ./bridges/rialto-parachain-millau/entrypoints:/entrypoints + environment: + RUST_LOG: rpc=trace,bridge=trace + ports: + - "10816:9616" + depends_on: &all-nodes + - millau-node-alice + - millau-node-bob + - millau-node-charlie + - millau-node-dave + - millau-node-eve + - rialto-parachain-collator-alice + - rialto-parachain-collator-bob + - rialto-parachain-collator-charlie + + relay-millau-rialto-parachain-2: + <<: *sub-bridge-relay + environment: + RUST_LOG: rpc=trace,bridge=trace + EXT_MILLAU_RELAY_ACCOUNT: //RialtoParachain.HeadersAndMessagesRelay2 + EXT_MILLAU_RELAY_ACCOUNT_HEADERS_OVERRIDE: //RialtoParachain.RialtoHeadersRelay2 + EXT_RIALTO_PARACHAIN_RELAY_ACCOUNT: //Millau.HeadersAndMessagesRelay2 + ports: + - "10916:9616" + relay-messages-millau-to-rialto-parachain-generator: + <<: *sub-bridge-relay + ports: + - "11016:9616" + entrypoint: /entrypoints/relay-messages-to-rialto-parachain-generator-entrypoint.sh + depends_on: + - relay-millau-rialto-parachain-1 + + relay-messages-rialto-parachain-to-millau-generator: + <<: *sub-bridge-relay + entrypoint: /entrypoints/relay-messages-to-millau-generator-entrypoint.sh + ports: + - "11017:9616" + depends_on: + - relay-millau-rialto-parachain-1 + + relay-messages-millau-to-rialto-parachain-resubmitter: + <<: *sub-bridge-relay + environment: + RUST_LOG: bridge=trace + entrypoint: /entrypoints/relay-messages-to-rialto-parachain-resubmitter-entrypoint.sh + ports: + - "11018:9616" + depends_on: + - relay-messages-millau-to-rialto-parachain-generator + + # Note: These are being overridden from the top level `monitoring` compose file. + grafana-dashboard: + environment: + VIRTUAL_HOST: grafana.millau.brucke.link,grafana.rialto.brucke.link + VIRTUAL_PORT: 3000 + LETSENCRYPT_HOST: grafana.millau.brucke.link,grafana.rialto.brucke.link + LETSENCRYPT_EMAIL: admin@parity.io + volumes: + - ./bridges/rialto-parachain-millau/dashboard/grafana:/etc/grafana/dashboards/rialto-parachain-millau:ro + - ./networks/dashboard/grafana/beefy-dashboard.json:/etc/grafana/dashboards/beefy.json + + prometheus-metrics: + volumes: + - ./bridges/rialto-parachain-millau/dashboard/prometheus/targets.yml:/etc/prometheus/targets-rialto-parachain-millau.yml + depends_on: *all-nodes diff --git a/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-millau-generator-entrypoint.sh b/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-millau-generator-entrypoint.sh new file mode 100755 index 00000000000..cf0c910a05b --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-millau-generator-entrypoint.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# THIS SCRIPT IS NOT INTENDED FOR USE IN PRODUCTION ENVIRONMENT +# +# This scripts periodically calls the Substrate relay binary to generate messages. These messages +# are sent from the Rialto network to the Millau network. + +set -eu + +# Max delay before submitting transactions (s) +MAX_SUBMIT_DELAY_S=${MSG_EXCHANGE_GEN_MAX_SUBMIT_DELAY_S:-30} +MESSAGE_LANE=${MSG_EXCHANGE_GEN_LANE:-00000000} +MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE=1024 + +SHARED_CMD="/home/user/substrate-relay send-message rialto-parachain-to-millau" +SHARED_HOST="--source-host rialto-parachain-collator-bob --source-port 9944" +SOURCE_SIGNER="--source-signer //Millau.MessagesSender" + +SEND_MESSAGE="$SHARED_CMD $SHARED_HOST $SOURCE_SIGNER" + +SOURCE_CHAIN="RialtoParachain" +TARGET_CHAIN="Millau" +EXTRA_ARGS="" +REGULAR_PAYLOAD="020419ac" +BATCH_PAYLOAD="010109020419A8" + +source /common/generate_messages.sh diff --git a/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-rialto-parachain-generator-entrypoint.sh b/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-rialto-parachain-generator-entrypoint.sh new file mode 100755 index 00000000000..7917f7739d5 --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-rialto-parachain-generator-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# THIS SCRIPT IS NOT INTENDED FOR USE IN PRODUCTION ENVIRONMENT +# +# This scripts periodically calls the Substrate relay binary to generate messages. These messages +# are sent from the Millau network to the Rialto network. + +set -eu + +# Max delay before submitting transactions (s) +MAX_SUBMIT_DELAY_S=${MSG_EXCHANGE_GEN_MAX_SUBMIT_DELAY_S:-30} +MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE=1024 + +SHARED_CMD=" /home/user/substrate-relay send-message millau-to-rialto-parachain" +SHARED_HOST="--source-host millau-node-bob --source-port 9944" +SOURCE_SIGNER="--source-signer //RialtoParachain.MessagesSender" + +SEND_MESSAGE="$SHARED_CMD $SHARED_HOST $SOURCE_SIGNER" + +SOURCE_CHAIN="Millau" +TARGET_CHAIN="RialtoParachain" +EXTRA_ARGS="" +REGULAR_PAYLOAD="020419ac" +BATCH_PAYLOAD="010109020419A8" + +source /common/generate_messages.sh diff --git a/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-rialto-parachain-resubmitter-entrypoint.sh b/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-rialto-parachain-resubmitter-entrypoint.sh new file mode 100755 index 00000000000..cf4c4612d69 --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/entrypoints/relay-messages-to-rialto-parachain-resubmitter-entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -xeu + +sleep 15 + +# //RialtoParachain.MessagesSender is signing Millau -> RialtoParachain message-send transactions, which are causing problems. +# +# When large message is being sent from Millau to RialtoParachain AND other transactions are +# blocking it from being mined, we'll see something like this in logs: +# +# Millau transaction priority with tip=0: 17800827994. Target priority: +# 526186677695 +# +# So since fee multiplier in Millau is `1` and `WeightToFee` is `IdentityFee`, then +# we need tip around `526186677695 - 17800827994 = 508_385_849_701`. Let's round it +# up to `1_000_000_000_000`. + +/home/user/substrate-relay resubmit-transactions millau \ + --target-host millau-node-alice \ + --target-port 9944 \ + --target-signer //RialtoParachain.MessagesSender \ + --stalled-blocks 7 \ + --tip-limit 1000000000000 \ + --tip-step 1000000000 \ + make-it-best-transaction diff --git a/deployments/bridges/rialto-parachain-millau/entrypoints/relay-millau-rialto-parachain-entrypoint.sh b/deployments/bridges/rialto-parachain-millau/entrypoints/relay-millau-rialto-parachain-entrypoint.sh new file mode 100755 index 00000000000..f2732e82f49 --- /dev/null +++ b/deployments/bridges/rialto-parachain-millau/entrypoints/relay-millau-rialto-parachain-entrypoint.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -xeu + +sleep 15 + +MILLAU_RELAY_ACCOUNT=${EXT_MILLAU_RELAY_ACCOUNT:-//RialtoParachain.HeadersAndMessagesRelay1} +MILLAU_RELAY_ACCOUNT_HEADERS_OVERRIDE=${EXT_MILLAU_RELAY_ACCOUNT_HEADERS_OVERRIDE:-//RialtoParachain.RialtoHeadersRelay1} +RIALTO_PARACHAIN_RELAY_ACCOUNT=${EXT_RIALTO_PARACHAIN_RELAY_ACCOUNT:-//Millau.HeadersAndMessagesRelay1} + +/home/user/substrate-relay init-bridge millau-to-rialto-parachain \ + --source-host millau-node-alice \ + --source-port 9944 \ + --target-host rialto-parachain-collator-alice \ + --target-port 9944 \ + --target-signer //Sudo + +/home/user/substrate-relay init-bridge rialto-to-millau \ + --source-host rialto-node-alice \ + --source-port 9944 \ + --target-host millau-node-alice \ + --target-port 9944 \ + --target-signer //Sudo + +# Give chain a little bit of time to process initialization transaction +sleep 6 + +/home/user/substrate-relay relay-headers-and-messages millau-rialto-parachain \ + --millau-host millau-node-alice \ + --millau-port 9944 \ + --millau-signer $MILLAU_RELAY_ACCOUNT \ + --rialto-headers-to-millau-signer $MILLAU_RELAY_ACCOUNT_HEADERS_OVERRIDE \ + --millau-messages-pallet-owner=//RialtoParachain.MessagesOwner \ + --millau-transactions-mortality=64 \ + --rialto-parachain-host rialto-parachain-collator-charlie \ + --rialto-parachain-port 9944 \ + --rialto-parachain-signer $RIALTO_PARACHAIN_RELAY_ACCOUNT \ + --rialto-parachain-messages-pallet-owner=//Millau.MessagesOwner \ + --rialto-parachain-transactions-mortality=64 \ + --rialto-host rialto-node-alice \ + --rialto-port 9944 \ + --lane=00000000 \ + --prometheus-host=0.0.0.0 diff --git a/deployments/bridges/westend-millau/dashboard/grafana/relay-westend-to-millau-headers-dashboard.json b/deployments/bridges/westend-millau/dashboard/grafana/relay-westend-to-millau-headers-dashboard.json new file mode 100644 index 00000000000..8f740d2ba5e --- /dev/null +++ b/deployments/bridges/westend-millau/dashboard/grafana/relay-westend-to-millau-headers-dashboard.json @@ -0,0 +1,781 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 32 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "60m", + "frequency": "5m", + "handler": 1, + "message": "", + "name": "Synced Header Difference is Over 32 (Westend to Millau)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "description": "Shows how many headers behind the target chain is from the source chain.", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "max(Westend_to_Millau_Sync_best_source_block_number) - max(Westend_to_Millau_Sync_best_source_at_target_block_number)", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 5 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Difference Between Source and Target Headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 32 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "2m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "60m", + "frequency": "5m", + "handler": 1, + "name": "No New Headers (Westend to Millau)", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "description": "How many headers has the relay synced from the source node in the last 2 mins?", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "max_over_time(Westend_to_Millau_Sync_best_source_block_number[10m])-min_over_time(Westend_to_Millau_Sync_best_source_block_number[10m])", + "interval": "", + "legendFormat": "Number of new Headers on Westend (Last 10 Mins)", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 5 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Headers Synced on Millau (Last 2 Mins)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": { + "align": null + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 2, + "interval": "5s", + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "7.1.3", + "targets": [ + { + "expr": "Westend_to_Millau_Sync_best_source_block_number", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Best Known Westend Header at Westend", + "refId": "A" + }, + { + "expr": "Westend_to_Millau_Sync_best_source_at_target_block_number", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Best Known Westend Header at Millau", + "refId": "B" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Best Blocks according to Relay", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 8 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "system_average_load{instance='relay-headers-westend-to-millau-1:9616'}", + "interval": "", + "legendFormat": "Average system load in last {{over}}", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": null + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average System Load", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 8 + }, + "id": 12, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "7.1.3", + "targets": [ + { + "expr": "avg_over_time(process_cpu_usage_percentage{instance='relay-headers-westend-to-millau-1:9616'}[1m])", + "instant": true, + "interval": "", + "legendFormat": "1 CPU = 100", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Relay Process CPU Usage ", + "type": "gauge" + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Whether with-Westend-grandpa-pallet and Westend itself are on different forks alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Westend_to_Millau_Sync_is_source_and_source_at_target_using_different_forks", + "interval": "", + "legendFormat": "On different forks?", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Whether with-Westend-grandpa-pallet and Westend itself are on different forks", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "description": "", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_memory_usage_bytes{instance='relay-headers-westend-to-millau-1:9616'} / 1024 / 1024", + "interval": "", + "legendFormat": "Process memory, MB", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory Usage for Relay Process", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Westend to Millau Header Sync Dashboard", + "uid": "relay-westend-to-millau-headers", + "version": 1 +} diff --git a/deployments/bridges/westend-millau/dashboard/grafana/relay-westend-to-millau-parachains-dashboard.json b/deployments/bridges/westend-millau/dashboard/grafana/relay-westend-to-millau-parachains-dashboard.json new file mode 100644 index 00000000000..007b6ef9325 --- /dev/null +++ b/deployments/bridges/westend-millau/dashboard/grafana/relay-westend-to-millau-parachains-dashboard.json @@ -0,0 +1,200 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 9, + "links": [], + "panels": [ + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 32 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "C", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Too many Westmint headers are missing at Millau", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "Westend_to_Millau_Parachains_best_parachain_block_number_at_source{parachain='para_2000'}", + "interval": "", + "legendFormat": "At Westend", + "refId": "A" + }, + { + "expr": "Westend_to_Millau_Parachains_best_parachain_block_number_at_target{parachain='para_2000'}", + "interval": "", + "legendFormat": "At Millau", + "refId": "B" + }, + { + "expr": "Westend_to_Millau_Parachains_best_parachain_block_number_at_source{parachain='para_2000'} - Westend_to_Millau_Parachains_best_parachain_block_number_at_target{parachain='para_2000'}", + "hide": true, + "interval": "", + "legendFormat": "Missing Westmint headers at Millau", + "refId": "C" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 32 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Westmint headers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Westend parachains at Millau", + "uid": "vUMhOlq7k", + "version": 1 + } + \ No newline at end of file diff --git a/deployments/bridges/westend-millau/dashboard/prometheus/targets.yml b/deployments/bridges/westend-millau/dashboard/prometheus/targets.yml new file mode 100644 index 00000000000..aed2d494582 --- /dev/null +++ b/deployments/bridges/westend-millau/dashboard/prometheus/targets.yml @@ -0,0 +1,3 @@ +- targets: + - relay-headers-westend-to-millau-1:9616 + - relay-parachains-westend-to-millau-1:9616 \ No newline at end of file diff --git a/deployments/bridges/westend-millau/docker-compose.yml b/deployments/bridges/westend-millau/docker-compose.yml new file mode 100644 index 00000000000..dfb6ebb0c64 --- /dev/null +++ b/deployments/bridges/westend-millau/docker-compose.yml @@ -0,0 +1,72 @@ +# Exposed ports: 10616, 10617, 10618, 10619 + +version: '3.5' +services: + relay-headers-westend-to-millau-1: + image: ${SUBSTRATE_RELAY_IMAGE:-paritytech/substrate-relay} + entrypoint: /entrypoints/relay-headers-westend-to-millau-entrypoint.sh + volumes: + - ./bridges/westend-millau/entrypoints:/entrypoints + environment: + RUST_LOG: rpc=trace,bridge=trace + ports: + - "10616:9616" + depends_on: + - millau-node-alice + + relay-headers-westend-to-millau-2: + image: ${SUBSTRATE_RELAY_IMAGE:-paritytech/substrate-relay} + entrypoint: /entrypoints/relay-headers-westend-to-millau-entrypoint.sh + volumes: + - ./bridges/westend-millau/entrypoints:/entrypoints + environment: + RUST_LOG: rpc=trace,bridge=trace + EXT_RELAY_ACCOUNT: //Westend.HeadersRelay2 + ports: + - "10617:9616" + depends_on: + - millau-node-alice + + relay-parachains-westend-to-millau-1: + image: ${SUBSTRATE_RELAY_IMAGE:-paritytech/substrate-relay} + entrypoint: /entrypoints/relay-parachains-westend-to-millau-entrypoint.sh + volumes: + - ./bridges/westend-millau/entrypoints:/entrypoints + environment: + RUST_LOG: rpc=trace,bridge=trace + ports: + - "10618:9616" + depends_on: + - millau-node-alice + + relay-parachains-westend-to-millau-2: + image: ${SUBSTRATE_RELAY_IMAGE:-paritytech/substrate-relay} + entrypoint: /entrypoints/relay-parachains-westend-to-millau-entrypoint.sh + volumes: + - ./bridges/westend-millau/entrypoints:/entrypoints + environment: + RUST_LOG: rpc=trace,bridge=trace + EXT_RELAY_ACCOUNT: //Westend.WestmintHeaders2 + ports: + - "10619:9616" + depends_on: + - millau-node-alice + + # Note: These are being overridden from the top level `monitoring` compose file. + grafana-dashboard: + environment: + VIRTUAL_HOST: grafana.millau.brucke.link,grafana.rialto.brucke.link + VIRTUAL_PORT: 3000 + LETSENCRYPT_HOST: grafana.millau.brucke.link,grafana.rialto.brucke.link + LETSENCRYPT_EMAIL: admin@parity.io + volumes: + - ./bridges/westend-millau/dashboard/grafana:/etc/grafana/dashboards/westend-millau:ro + + prometheus-metrics: + volumes: + - ./bridges/westend-millau/dashboard/prometheus/targets.yml:/etc/prometheus/targets-westend-millau.yml + depends_on: + - relay-headers-westend-to-millau-1 + - relay-headers-westend-to-millau-2 + - relay-parachains-westend-to-millau-1 + - relay-parachains-westend-to-millau-2 diff --git a/deployments/bridges/westend-millau/entrypoints/relay-headers-westend-to-millau-entrypoint.sh b/deployments/bridges/westend-millau/entrypoints/relay-headers-westend-to-millau-entrypoint.sh new file mode 100755 index 00000000000..8548e9f5a41 --- /dev/null +++ b/deployments/bridges/westend-millau/entrypoints/relay-headers-westend-to-millau-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -xeu + +sleep 15 + +RELAY_ACCOUNT=${EXT_RELAY_ACCOUNT:-//Westend.HeadersRelay1} + +/home/user/substrate-relay init-bridge westend-to-millau \ + --source-host westend-rpc.polkadot.io \ + --source-port 443 \ + --source-secure \ + --target-host millau-node-alice \ + --target-port 9944 \ + --target-signer //Westend.GrandpaOwner + +# Give chain a little bit of time to process initialization transaction +sleep 6 +/home/user/substrate-relay relay-headers westend-to-millau \ + --source-host westend-rpc.polkadot.io \ + --source-port 443 \ + --source-secure \ + --target-host millau-node-alice \ + --target-port 9944 \ + --target-signer $RELAY_ACCOUNT \ + --target-transactions-mortality=4\ + --prometheus-host=0.0.0.0 diff --git a/deployments/bridges/westend-millau/entrypoints/relay-parachains-westend-to-millau-entrypoint.sh b/deployments/bridges/westend-millau/entrypoints/relay-parachains-westend-to-millau-entrypoint.sh new file mode 100755 index 00000000000..fc7ccb3584f --- /dev/null +++ b/deployments/bridges/westend-millau/entrypoints/relay-parachains-westend-to-millau-entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -xeu + +sleep 15 + +RELAY_ACCOUNT=${EXT_RELAY_ACCOUNT:-//Westend.WestmintHeaders1} + +/home/user/substrate-relay relay-parachains westend-to-millau \ + --source-host westend-rpc.polkadot.io \ + --source-port 443 \ + --source-secure \ + --target-host millau-node-alice \ + --target-port 9944 \ + --target-signer $RELAY_ACCOUNT \ + --target-transactions-mortality=4\ + --prometheus-host=0.0.0.0 diff --git a/deployments/local-scripts/bridge-entrypoint.sh b/deployments/local-scripts/bridge-entrypoint.sh new file mode 100755 index 00000000000..5c1b6e90ec2 --- /dev/null +++ b/deployments/local-scripts/bridge-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -xeu + +# This will allow us to run whichever binary the user wanted +# with arguments passed through `docker run` +# e.g `docker run -it rialto-bridge-node-dev --dev --tmp` +/home/user/$PROJECT $@ diff --git a/deployments/local-scripts/relay-messages-millau-to-rialto.sh b/deployments/local-scripts/relay-messages-millau-to-rialto.sh new file mode 100755 index 00000000000..d420dc56c26 --- /dev/null +++ b/deployments/local-scripts/relay-messages-millau-to-rialto.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# A script for relaying Millau messages to the Rialto chain. +# +# Will not work unless both the Rialto and Millau are running (see `run-rialto-node.sh` +# and `run-millau-node.sh). +set -xeu + +MILLAU_PORT="${MILLAU_PORT:-9945}" +RIALTO_PORT="${RIALTO_PORT:-9944}" + +RUST_LOG=bridge=debug \ +./target/debug/substrate-relay relay-messages millau-to-rialto \ + --lane 00000000 \ + --source-host localhost \ + --source-port $MILLAU_PORT \ + --source-signer //Bob \ + --target-host localhost \ + --target-port $RIALTO_PORT \ + --target-signer //Bob \ + --prometheus-host=0.0.0.0 diff --git a/deployments/local-scripts/relay-messages-rialto-to-millau.sh b/deployments/local-scripts/relay-messages-rialto-to-millau.sh new file mode 100755 index 00000000000..0cd73c00454 --- /dev/null +++ b/deployments/local-scripts/relay-messages-rialto-to-millau.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# A script for relaying Rialto messages to the Millau chain. +# +# Will not work unless both the Rialto and Millau are running (see `run-rialto-node.sh` +# and `run-millau-node.sh). +set -xeu + +MILLAU_PORT="${MILLAU_PORT:-9945}" +RIALTO_PORT="${RIALTO_PORT:-9944}" + +RUST_LOG=bridge=debug \ +./target/debug/substrate-relay relay-messages rialto-to-millau \ + --lane 00000000 \ + --source-host localhost \ + --source-port $RIALTO_PORT \ + --source-signer //Bob \ + --target-host localhost \ + --target-port $MILLAU_PORT \ + --target-signer //Bob \ + --prometheus-host=0.0.0.0 diff --git a/deployments/local-scripts/relay-millau-to-rialto.sh b/deployments/local-scripts/relay-millau-to-rialto.sh new file mode 100755 index 00000000000..4758173f72f --- /dev/null +++ b/deployments/local-scripts/relay-millau-to-rialto.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# A script for relaying Millau headers to the Rialto chain. +# +# Will not work unless both the Rialto and Millau are running (see `run-rialto-node.sh` +# and `run-millau-node.sh). + +MILLAU_PORT="${MILLAU_PORT:-9945}" +RIALTO_PORT="${RIALTO_PORT:-9944}" + +RUST_LOG=bridge=debug \ +./target/debug/substrate-relay init-bridge millau-to-rialto \ + --source-host localhost \ + --source-port $MILLAU_PORT \ + --target-host localhost \ + --target-port $RIALTO_PORT \ + --target-signer //Sudo \ + --source-version-mode Bundle \ + --target-version-mode Bundle + +sleep 5 +RUST_LOG=bridge=debug \ +./target/debug/substrate-relay relay-headers millau-to-rialto \ + --source-host localhost \ + --source-port $MILLAU_PORT \ + --target-host localhost \ + --target-port $RIALTO_PORT \ + --target-signer //Alice \ + --prometheus-host=0.0.0.0 diff --git a/deployments/local-scripts/relay-rialto-to-millau.sh b/deployments/local-scripts/relay-rialto-to-millau.sh new file mode 100755 index 00000000000..5fd27840214 --- /dev/null +++ b/deployments/local-scripts/relay-rialto-to-millau.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# A script for relaying Rialto headers to the Millau chain. +# +# Will not work unless both the Rialto and Millau are running (see `run-rialto-node.sh` +# and `run-millau-node.sh). + +MILLAU_PORT="${MILLAU_PORT:-9945}" +RIALTO_PORT="${RIALTO_PORT:-9944}" + +RUST_LOG=bridge=debug \ +./target/debug/substrate-relay init-bridge rialto-to-millau \ + --target-host localhost \ + --target-port $MILLAU_PORT \ + --source-host localhost \ + --source-port $RIALTO_PORT \ + --target-signer //Sudo \ + +sleep 5 +RUST_LOG=bridge=debug \ +./target/debug/substrate-relay relay-headers rialto-to-millau \ + --target-host localhost \ + --target-port $MILLAU_PORT \ + --source-host localhost \ + --source-port $RIALTO_PORT \ + --target-signer //Alice \ + --prometheus-host=0.0.0.0 diff --git a/deployments/local-scripts/run-millau-node.sh b/deployments/local-scripts/run-millau-node.sh new file mode 100755 index 00000000000..916f876c536 --- /dev/null +++ b/deployments/local-scripts/run-millau-node.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Run a development instance of the Millau Substrate bridge node. +# To override the default port just export MILLAU_PORT=9945 + +MILLAU_PORT="${MILLAU_PORT:-9945}" + +RUST_LOG=runtime=trace \ +./target/debug/millau-bridge-node --dev --tmp \ + --rpc-cors=all --unsafe-rpc-external --unsafe-ws-external \ + --port 33044 --rpc-port 9934 --ws-port $MILLAU_PORT \ diff --git a/deployments/local-scripts/run-rialto-node.sh b/deployments/local-scripts/run-rialto-node.sh new file mode 100755 index 00000000000..e7987e2af36 --- /dev/null +++ b/deployments/local-scripts/run-rialto-node.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Run a development instance of the Rialto Substrate bridge node. +# To override the default port just export RIALTO_PORT=9944 + +RIALTO_PORT="${RIALTO_PORT:-9944}" + +RUST_LOG=runtime=trace \ + ./target/debug/rialto-bridge-node --dev --tmp \ + --rpc-cors=all --unsafe-rpc-external --unsafe-ws-external \ + --port 33033 --rpc-port 9933 --ws-port $RIALTO_PORT \ diff --git a/deployments/local-scripts/run-westend-node.sh b/deployments/local-scripts/run-westend-node.sh new file mode 100755 index 00000000000..1bb490fc1a8 --- /dev/null +++ b/deployments/local-scripts/run-westend-node.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Run a development instance of the Westend Substrate bridge node. +# To override the default port just export WESTEND_PORT=9945 +# +# Note: This script will not work out of the box with the bridges +# repo since it relies on a Polkadot binary. + +WESTEND_PORT="${WESTEND_PORT:-9944}" + +RUST_LOG=runtime=trace,runtime::bridge=trace \ +./target/debug/polkadot --chain=westend-dev --alice --tmp \ + --rpc-cors=all --unsafe-rpc-external --unsafe-ws-external \ + --port 33033 --rpc-port 9933 --ws-port $WESTEND_PORT \ diff --git a/deployments/monitoring/GrafanaMatrix.Dockerfile b/deployments/monitoring/GrafanaMatrix.Dockerfile new file mode 100644 index 00000000000..be1c80d4059 --- /dev/null +++ b/deployments/monitoring/GrafanaMatrix.Dockerfile @@ -0,0 +1,15 @@ +FROM ruby:alpine3.13 + +RUN apk add --no-cache git + +ENV APP_HOME /app +ENV RACK_ENV production +RUN mkdir $APP_HOME +WORKDIR $APP_HOME + +RUN git clone https://github.com/ananace/ruby-grafana-matrix.git $APP_HOME +RUN bundle install --without development + +RUN mkdir /config && touch /config/config.yml && ln -s /config/config.yml ./config.yml + +CMD ["bundle", "exec", "rackup", "-p4567"] diff --git a/deployments/monitoring/disabled.yml b/deployments/monitoring/disabled.yml new file mode 100644 index 00000000000..a0b4ed3aad0 --- /dev/null +++ b/deployments/monitoring/disabled.yml @@ -0,0 +1,15 @@ +# A disabled version of monitoring. +# +# We replace each service with a no-op container. We can't simply not include this file, +# cause the bridge-specific compose files might have overrides. +version: '3.5' +services: + prometheus-metrics: + image: alpine + + grafana-dashboard: + image: alpine + + grafana-matrix-notifier: + image: alpine + diff --git a/deployments/monitoring/docker-compose.yml b/deployments/monitoring/docker-compose.yml new file mode 100644 index 00000000000..1fa50c68d0e --- /dev/null +++ b/deployments/monitoring/docker-compose.yml @@ -0,0 +1,34 @@ +version: '3.5' +services: + prometheus-metrics: + image: prom/prometheus:v2.38.0 + volumes: + - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + + grafana-dashboard: + image: grafana/grafana:8.2.6 + environment: + GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASS:-admin} + GF_SERVER_ROOT_URL: ${GRAFANA_SERVER_ROOT_URL} + GF_SERVER_DOMAIN: ${GRAFANA_SERVER_DOMAIN} + volumes: + - ./monitoring/grafana/provisioning/:/etc/grafana/provisioning/:ro + - ./monitoring/grafana/dashboards/:/etc/grafana/dashboards/common:ro + ports: + - "3000:3000" + depends_on: + - prometheus-metrics + entrypoint: sh -c "echo 'sleeping for 10m' && sleep 600 && /run.sh" + + grafana-matrix-notifier: + build: + context: . + dockerfile: ./monitoring/GrafanaMatrix.Dockerfile + volumes: + - ./monitoring/grafana-matrix:/config + ports: + - "4567:4567" + depends_on: + - grafana-dashboard diff --git a/deployments/monitoring/grafana-matrix/config.yml b/deployments/monitoring/grafana-matrix/config.yml new file mode 100644 index 00000000000..123cf76a80b --- /dev/null +++ b/deployments/monitoring/grafana-matrix/config.yml @@ -0,0 +1,47 @@ +--- +# Webhook server configuration +# Or use the launch options `-o '::' -p 4567` + +# Set up your HS connections +matrix: +- name: matrix-parity-io + url: https://matrix.parity.io + # Create a user - log that user in using a post request + # curl -XPOST -d '{"type": "m.login.password", + # "user":"grafana", + # "password":"dummy-password"}' + # "https://my-matrix-server/_matrix/client/r0/login" + # Fill that access token in here + access_token: "" + #device_id: # Optional + +# The default message type for messages, should be either m.text or m.notice, +# defaults to m.text +msgtype: m.text + +# Set up notification ingress rules +rules: +- name: bridge # Name of the rule + room: "#bridges-rialto-millau-alerts:matrix.parity.io" # Room or ID + matrix: matrix-parity-io # The Matrix HS to use - defaults to first one + msgtype: m.notice + # The following values are optional: + image: true # Attach image to the notification? + embed_image: true # Upload and embed the image into the message? + #templates: + # Templates to use when rendering the notification, available placeholders: + # %TEMPLATES% - lib/grafana_matrix/templates + # $ - Environment variables + #html: "%TEMPLATES%/html.erb" # Path to HTML template + #plain: "%TEMPLATES%/plain.erb" # Path to plaintext template + #auth: + #user: example + #pass: any HTTP encodable string +#- name: other-hq +# room: "#hq:private.matrix.org +# matrix: matrix-priv + +# To use the webhook, you need to configure it into Grafana as: +# +# Url: http://:/hook?rule= +# Http Method: POST diff --git a/deployments/monitoring/grafana/dashboards/nodes.json b/deployments/monitoring/grafana/dashboards/nodes.json new file mode 100644 index 00000000000..db14c3cf1fa --- /dev/null +++ b/deployments/monitoring/grafana/dashboards/nodes.json @@ -0,0 +1,200 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "Running nodes", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "15m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "min" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "3m", + "frequency": "1m", + "handler": 1, + "name": "Nodes are not running", + "noDataState": "alerting", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "description": "", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "interval": null, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "maxDataPoints": null, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.2.6", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "up", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1, + "visible": true + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Running nodes", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:111", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:112", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "", + "schemaVersion": 32, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Running nodes", + "uid": "DHl-xSW4z", + "version": 1 +} diff --git a/deployments/monitoring/grafana/provisioning/dashboards/grafana-dashboard.yaml b/deployments/monitoring/grafana/provisioning/dashboards/grafana-dashboard.yaml new file mode 100644 index 00000000000..d14ed2637d5 --- /dev/null +++ b/deployments/monitoring/grafana/provisioning/dashboards/grafana-dashboard.yaml @@ -0,0 +1,6 @@ +- name: 'default' + orgId: 1 + folder: '' + type: file + options: + path: '/etc/grafana/dashboards' \ No newline at end of file diff --git a/deployments/monitoring/grafana/provisioning/datasources/grafana-datasource.yaml b/deployments/monitoring/grafana/provisioning/datasources/grafana-datasource.yaml new file mode 100644 index 00000000000..b85cf06e2bd --- /dev/null +++ b/deployments/monitoring/grafana/provisioning/datasources/grafana-datasource.yaml @@ -0,0 +1,16 @@ +# list of datasources to insert/update depending +# whats available in the database +datasources: + # name of the datasource. Required +- name: Prometheus + # datasource type. Required + type: prometheus + # access mode. direct or proxy. Required + access: proxy + # org id. will default to orgId 1 if not specified + orgId: 1 + # url + url: http://prometheus-metrics:9090 + # mark as default datasource. Max one per org + isDefault: true + version: 1 diff --git a/deployments/monitoring/grafana/provisioning/notifiers/grafana-notifier.yaml b/deployments/monitoring/grafana/provisioning/notifiers/grafana-notifier.yaml new file mode 100644 index 00000000000..4eb6ea3863e --- /dev/null +++ b/deployments/monitoring/grafana/provisioning/notifiers/grafana-notifier.yaml @@ -0,0 +1,15 @@ +notifiers: + - name: Matrix + type: webhook + uid: notifier1 + is_default: true + send_reminder: true + frequency: 1h + disable_resolve_message: false + settings: + url: http://grafana-matrix-notifier:4567/hook?rule=bridge + http_method: POST + +delete_notifiers: + - name: Matrix + uid: notifier1 diff --git a/deployments/monitoring/prometheus/prometheus.yml b/deployments/monitoring/prometheus/prometheus.yml new file mode 100644 index 00000000000..7092bd27314 --- /dev/null +++ b/deployments/monitoring/prometheus/prometheus.yml @@ -0,0 +1,7 @@ +global: + scrape_interval: 15s +scrape_configs: + - job_name: dummy + file_sd_configs: + - files: + - /etc/prometheus/targets-*.yml diff --git a/deployments/networks/dashboard/grafana/beefy-dashboard.json b/deployments/networks/dashboard/grafana/beefy-dashboard.json new file mode 100644 index 00000000000..a5df3449e67 --- /dev/null +++ b/deployments/networks/dashboard/grafana/beefy-dashboard.json @@ -0,0 +1,585 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "C", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "D", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Beefy best blocks not advancing", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": { + "align": null + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 14, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "substrate_beefy_best_block{chain=\"rialto_local\"}", + "legendFormat": "Rialto(Charlie)", + "refId": "A" + }, + { + "expr": "substrate_beefy_best_block{chain=\"millau_local\"}", + "legendFormat": "Millau(Charlie)", + "refId": "B" + }, + { + "expr": "max_over_time(substrate_beefy_best_block{chain=\"millau_local\"}[5m]) - min_over_time(substrate_beefy_best_block{chain=\"millau_local\"}[5m])", + "hide": true, + "legendFormat": "Millau Best Beefy blocks count in last 5 minutes", + "refId": "C" + }, + { + "expr": "max_over_time(substrate_beefy_best_block{chain=\"rialto_local\"}[5m]) - min_over_time(substrate_beefy_best_block{chain=\"rialto_local\"}[5m])", + "hide": true, + "legendFormat": "Rialto Best Beefy blocks count in last 5 minutes", + "refId": "D" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 1 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Beefy Best block", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "yellow", + "value": null + }, + { + "color": "yellow", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "7.1.3", + "targets": [ + { + "expr": "substrate_beefy_should_vote_on{chain=\"rialto_local\"}", + "legendFormat": "Rialto(Charlie) Should-Vote-On", + "refId": "C" + }, + { + "expr": "substrate_beefy_round_concluded{chain=\"rialto_local\"}", + "legendFormat": "Rialto(Charlie) Round-Concluded", + "refId": "A" + }, + { + "expr": "substrate_beefy_should_vote_on{chain=\"millau_local\"}", + "legendFormat": "Millau(Charlie) Should-Vote-On", + "refId": "D" + }, + { + "expr": "substrate_beefy_round_concluded{chain=\"millau_local\"}", + "legendFormat": "Millau(Charlie) Round-Concluded", + "refId": "B" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Beefy Voting Rounds", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 18, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "substrate_beefy_votes_sent{chain=\"rialto_local\"}", + "legendFormat": "Rialto (node Charlie)", + "refId": "A" + }, + { + "expr": "substrate_beefy_votes_sent{chain=\"millau_local\"}", + "legendFormat": "Millau (node Charlie)", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Beefy Votes Sent", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + }, + { + "evaluator": { + "params": [ + 0 + ], + "type": "gt" + }, + "operator": { + "type": "or" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "max" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Beefy Lagging Sessions alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": { + "align": null + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 5, + "x": 18, + "y": 14 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "substrate_beefy_lagging_sessions{chain=\"rialto_local\"}", + "legendFormat": "Rialto(Charlie)", + "refId": "A" + }, + { + "expr": "substrate_beefy_lagging_sessions{chain=\"millau_local\"}", + "legendFormat": "Millau(Charlie)", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Beefy Lagging Sessions", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Beefy", + "uid": "j6cRDRh7z", + "version": 1 +} diff --git a/deployments/networks/dashboard/prometheus/millau-targets.yml b/deployments/networks/dashboard/prometheus/millau-targets.yml new file mode 100644 index 00000000000..c7a06509276 --- /dev/null +++ b/deployments/networks/dashboard/prometheus/millau-targets.yml @@ -0,0 +1,2 @@ +- targets: + - millau-node-charlie:9615 diff --git a/deployments/networks/dashboard/prometheus/rialto-targets.yml b/deployments/networks/dashboard/prometheus/rialto-targets.yml new file mode 100644 index 00000000000..9de26b9a2d7 --- /dev/null +++ b/deployments/networks/dashboard/prometheus/rialto-targets.yml @@ -0,0 +1,2 @@ +- targets: + - rialto-node-charlie:9615 diff --git a/deployments/networks/entrypoints/rialto-chainspec-exporter-entrypoint.sh b/deployments/networks/entrypoints/rialto-chainspec-exporter-entrypoint.sh new file mode 100755 index 00000000000..0898978096d --- /dev/null +++ b/deployments/networks/entrypoints/rialto-chainspec-exporter-entrypoint.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -xeu + +/home/user/rialto-bridge-node build-spec \ + --chain local \ + --raw \ + --disable-default-bootnode \ + > /rialto-share/rialto-relaychain-spec-raw.json + +# we're using local driver + tmpfs for shared `/rialto-share` volume, which is populated +# by the container running this script. If this script ends, the volume will be detached +# and our chain spec will be lost when it'll go online again. Hence the never-ending +# script which keeps volume online until container is stopped. +tail -f /dev/null diff --git a/deployments/networks/entrypoints/rialto-parachain-registrar-entrypoint.sh b/deployments/networks/entrypoints/rialto-parachain-registrar-entrypoint.sh new file mode 100755 index 00000000000..bb201ac5dad --- /dev/null +++ b/deployments/networks/entrypoints/rialto-parachain-registrar-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -xeu + +sleep 15 + +/home/user/substrate-relay register-parachain rialto-parachain \ + --parachain-host rialto-parachain-collator-alice \ + --parachain-port 9944 \ + --relaychain-host rialto-node-alice \ + --relaychain-port 9944 \ + --relaychain-signer //Sudo diff --git a/deployments/networks/millau.yml b/deployments/networks/millau.yml new file mode 100644 index 00000000000..6c57a3863ea --- /dev/null +++ b/deployments/networks/millau.yml @@ -0,0 +1,101 @@ +# Compose file for quickly spinning up a local instance of the Millau Substrate network. +# +# Note that the Millau network is only used for testing, so the configuration settings you see here +# are *not* recommended for a production environment. +# +# For example, do *not* keep your `node-key` in version control, and unless you're _really_ sure you +# want to provide public access to your nodes do *not* publicly expose RPC methods. +version: '3.5' +services: + millau-node-alice: &millau-bridge-node + image: ${MILLAU_BRIDGE_NODE_IMAGE:-paritytech/millau-bridge-node} + entrypoint: + - /home/user/millau-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/millau-node-bob/tcp/30333/p2p/12D3KooWM5LFR5ne4yTQ4sBSXJ75M4bDo2MAhAW2GhL3i8fe5aRb + - --alice + - --node-key=0f900c89f4e626f4a217302ab8c7d213737d00627115f318ad6fb169717ac8e0 + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + environment: + RUST_LOG: runtime=trace,rpc=debug,txpool=trace,runtime::bridge=trace,sc_basic_authorship=trace,beefy=debug + ports: + - "19933:9933" + - "19944:9944" + + millau-node-bob: + <<: *millau-bridge-node + entrypoint: + - /home/user/millau-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/millau-node-alice/tcp/30333/p2p/12D3KooWFqiV73ipQ1jpfVmCfLqBCp8G9PLH3zPkY9EhmdrSGA4H + - --bob + - --node-key=db383639ff2905d79f8e936fd5dc4416ef46b514b2f83823ec3c42753d7557bb + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + ports: + - "20033:9933" + - "20044:9944" + + millau-node-charlie: + <<: *millau-bridge-node + entrypoint: + - /home/user/millau-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/millau-node-alice/tcp/30333/p2p/12D3KooWFqiV73ipQ1jpfVmCfLqBCp8G9PLH3zPkY9EhmdrSGA4H + - --charlie + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + - --prometheus-external + ports: + - "20133:9933" + - "20144:9944" + - "20615:9615" + + millau-node-dave: + <<: *millau-bridge-node + entrypoint: + - /home/user/millau-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/millau-node-alice/tcp/30333/p2p/12D3KooWFqiV73ipQ1jpfVmCfLqBCp8G9PLH3zPkY9EhmdrSGA4H + - --dave + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + ports: + - "20233:9933" + - "20244:9944" + + millau-node-eve: + <<: *millau-bridge-node + entrypoint: + - /home/user/millau-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/millau-node-alice/tcp/30333/p2p/12D3KooWFqiV73ipQ1jpfVmCfLqBCp8G9PLH3zPkY9EhmdrSGA4H + - --eve + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + ports: + - "20333:9933" + - "20344:9944" + + # Note: These are being overridden from the top level `monitoring` compose file. + prometheus-metrics: + volumes: + - ./networks/dashboard/prometheus/millau-targets.yml:/etc/prometheus/targets-millau-nodes.yml + depends_on: + - millau-node-charlie diff --git a/deployments/networks/rialto-parachain.yml b/deployments/networks/rialto-parachain.yml new file mode 100644 index 00000000000..c6dab73ff72 --- /dev/null +++ b/deployments/networks/rialto-parachain.yml @@ -0,0 +1,90 @@ +# Compose file for quickly spinning up a local instance of the Rialto Parachain network. +# +# Since Rialto Parachain is unusable without Rialto, this file depends on some Rialto +# network nodes. +version: '3.5' +services: + rialto-parachain-collator-alice: &rialto-parachain-collator + image: ${RIALTO_PARACHAIN_COLLATOR_IMAGE:-paritytech/rialto-parachain-collator} + entrypoint: > + /home/user/rialto-parachain-collator + --alice + --collator + --force-authoring + --parachain-id 2000 + --rpc-port 9933 + --ws-port 9944 + --rpc-cors=all + --unsafe-rpc-external + --unsafe-ws-external + -- + --execution wasm + --chain /rialto-share/rialto-relaychain-spec-raw.json + --rpc-port 9934 + --ws-port 9945 + volumes: + - rialto-share:/rialto-share:z + environment: + RUST_LOG: runtime=trace,rpc=trace,txpool=trace,parachain=trace,parity_ws=trace,sc_basic_authorship=trace + depends_on: + - rialto-chainspec-exporter + ports: + - "20433:9933" + - "20444:9944" + + rialto-parachain-collator-bob: + <<: *rialto-parachain-collator + entrypoint: > + /home/user/rialto-parachain-collator + --bob + --collator + --force-authoring + --parachain-id 2000 + --rpc-port 9933 + --ws-port 9944 + --rpc-cors=all + --unsafe-rpc-external + --unsafe-ws-external + -- + --execution wasm + --chain /rialto-share/rialto-relaychain-spec-raw.json + --rpc-port 9934 + --ws-port 9945 + ports: + - "20533:9933" + - "20544:9944" + + rialto-parachain-collator-charlie: + <<: *rialto-parachain-collator + entrypoint: > + /home/user/rialto-parachain-collator + --charlie + --collator + --force-authoring + --parachain-id 2000 + --rpc-port 9933 + --ws-port 9944 + --rpc-cors=all + --unsafe-rpc-external + --unsafe-ws-external + -- + --execution wasm + --chain /rialto-share/rialto-relaychain-spec-raw.json + --rpc-port 9934 + --ws-port 9945 + ports: + - "20633:9933" + - "20644:9944" + + rialto-parachain-registrar: + image: ${SUBSTRATE_RELAY_IMAGE:-paritytech/substrate-relay} + entrypoint: /entrypoints/rialto-parachain-registrar-entrypoint.sh + volumes: + - ./networks/entrypoints:/entrypoints + - rialto-share:/rialto-share:z + environment: + RUST_LOG: bridge=trace + depends_on: + - rialto-node-alice + - rialto-parachain-collator-alice + diff --git a/deployments/networks/rialto.yml b/deployments/networks/rialto.yml new file mode 100644 index 00000000000..1e2bdd3e41b --- /dev/null +++ b/deployments/networks/rialto.yml @@ -0,0 +1,118 @@ +# Compose file for quickly spinning up a local instance of the Rialto Substrate network. +# +# Note that the Rialto network is only used for testing, so the configuration settings you see here +# are *not* recommended for a production environment. +# +# For example, do *not* keep your `node-key` in version control, and unless you're _really_ sure you +# want to provide public access to your nodes do *not* publicly expose RPC methods. +version: '3.5' +services: + rialto-node-alice: &rialto-bridge-node + image: ${RIALTO_BRIDGE_NODE_IMAGE:-paritytech/rialto-bridge-node} + entrypoint: + - /home/user/rialto-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/rialto-node-bob/tcp/30333/p2p/12D3KooWSEpHJj29HEzgPFcRYVc5X3sEuP3KgiUoqJNCet51NiMX + - --alice + - --node-key=79cf382988364291a7968ae7825c01f68c50d679796a8983237d07fe0ccf363b + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + environment: + RUST_LOG: runtime=trace,rpc=debug,txpool=trace,runtime::bridge=trace,beefy=debug + ports: + - "9933:9933" + - "9944:9944" + + rialto-node-bob: + <<: *rialto-bridge-node + entrypoint: + - /home/user/rialto-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/rialto-node-alice/tcp/30333/p2p/12D3KooWMF6JvV319a7kJn5pqkKbhR3fcM2cvK5vCbYZHeQhYzFE + - --bob + - --node-key=4f9d0146dd9b7b3bf5a8089e3880023d1df92057f89e96e07bb4d8c2ead75bbd + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + ports: + - "10033:9933" + - "10044:9944" + + rialto-node-charlie: + <<: *rialto-bridge-node + entrypoint: + - /home/user/rialto-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/rialto-node-alice/tcp/30333/p2p/12D3KooWMF6JvV319a7kJn5pqkKbhR3fcM2cvK5vCbYZHeQhYzFE + - --charlie + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + - --prometheus-external + ports: + - "10133:9933" + - "10144:9944" + - "10615:9615" + + rialto-node-dave: + <<: *rialto-bridge-node + entrypoint: + - /home/user/rialto-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/rialto-node-alice/tcp/30333/p2p/12D3KooWMF6JvV319a7kJn5pqkKbhR3fcM2cvK5vCbYZHeQhYzFE + - --dave + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + ports: + - "10233:9933" + - "10244:9944" + + rialto-node-eve: + <<: *rialto-bridge-node + entrypoint: + - /home/user/rialto-bridge-node + - --execution=Native + - --chain=local + - --bootnodes=/dns4/rialto-node-alice/tcp/30333/p2p/12D3KooWMF6JvV319a7kJn5pqkKbhR3fcM2cvK5vCbYZHeQhYzFE + - --eve + - --rpc-cors=all + - --enable-offchain-indexing=true + - --unsafe-rpc-external + - --unsafe-ws-external + ports: + - "10333:9933" + - "10344:9944" + + rialto-chainspec-exporter: + image: ${RIALTO_BRIDGE_NODE_IMAGE:-paritytech/rialto-bridge-node} + entrypoint: /entrypoints/rialto-chainspec-exporter-entrypoint.sh + volumes: + - ./networks/entrypoints:/entrypoints + - rialto-share:/rialto-share:z + + # Note: These are being overridden from the top level `monitoring` compose file. + prometheus-metrics: + volumes: + - ./networks/dashboard/prometheus/rialto-targets.yml:/etc/prometheus/targets-rialto-nodes.yml + depends_on: + - rialto-node-charlie + +# we're using `/rialto-share` to expose Rialto chain spec to those who are interested. Right +# now it is Rialto Parachain collator nodes. Local + tmpfs combination allows sharing writable +# in-memory volumes, which are dropped when containers are stopped. +volumes: + rialto-share: + driver: local + driver_opts: + type: "tmpfs" + device: "tmpfs" diff --git a/deployments/reverse-proxy/README.md b/deployments/reverse-proxy/README.md new file mode 100644 index 00000000000..ded81f80a1b --- /dev/null +++ b/deployments/reverse-proxy/README.md @@ -0,0 +1,15 @@ +# nginx-proxy + +This is a nginx reverse proxy configuration with Let's encrypt companion. +Main purpose is to be able to use `https://polkadot.js.org/apps` to connect to +a running network. + +## How to? + +In current directory: +```bash +docker-compose up -d +``` + +Then start `rialto` network with the same command (one folder up). `nginx` should +pick up new containers being created and automatically create a proxy setup for `Charlie`. diff --git a/deployments/reverse-proxy/docker-compose.yml b/deployments/reverse-proxy/docker-compose.yml new file mode 100644 index 00000000000..ee49e96afdd --- /dev/null +++ b/deployments/reverse-proxy/docker-compose.yml @@ -0,0 +1,45 @@ +version: '2' +services: + nginx-proxy: + image: jwilder/nginx-proxy + container_name: nginx-proxy + networks: + - nginx-proxy + - deployments_default + ports: + - "80:80" + - "443:443" + volumes: + - conf:/etc/nginx/conf.d + - vhost:/etc/nginx/vhost.d + - html:/usr/share/nginx/html + - dhparam:/etc/nginx/dhparam + - certs:/etc/nginx/certs:ro + - /var/run/docker.sock:/tmp/docker.sock:ro + - acme:/etc/acme.sh + + letsencrypt: + image: jrcs/letsencrypt-nginx-proxy-companion + container_name: nginx-proxy-le + networks: + - nginx-proxy + volumes_from: + - nginx-proxy + volumes: + - certs:/etc/nginx/certs:rw + - /var/run/docker.sock:/var/run/docker.sock:ro + - acme:/etc/acme.sh + +volumes: + conf: + vhost: + html: + dhparam: + certs: + acme: + +networks: + nginx-proxy: + driver: bridge + deployments_default: + external: true diff --git a/deployments/run.sh b/deployments/run.sh new file mode 100755 index 00000000000..bbae4733176 --- /dev/null +++ b/deployments/run.sh @@ -0,0 +1,210 @@ +#!/bin/bash + +# Script used for running and updating bridge deployments. +# +# To deploy a network you can run this script with the name of the bridge (or multiple bridges) you want to run. +# +# `./run.sh westend-millau rialto-millau` +# +# To update a deployment to use the latest images available from the Docker Hub add the `update` +# argument after the bridge name. +# +# `./run.sh rialto-millau update` +# +# Once you've stopped having fun with your deployment you can take it down with: +# +# `./run.sh rialto-millau stop` +# +# Stopping the bridge will also bring down all networks that it uses. So if you have started multiple bridges +# that are using the same network (like Millau in rialto-millau and westend-millau bridges), then stopping one +# of these bridges will cause the other bridge to break. + +set -xeu + +# Since the Compose commands are using relative paths we need to `cd` into the `deployments` folder. +cd "$( dirname "${BASH_SOURCE[0]}" )" + +function show_help () { + set +x + echo " " + echo Error: $1 + echo " " + echo "Usage:" + echo " ./run.sh rialto-millau [stop|update] Run Rialto <> Millau Networks & Bridge" + echo " ./run.sh rialto-parachain-millau [stop|update] Run RialtoParachain <> Millau Networks & Bridge" + echo " ./run.sh westend-millau [stop|update] Run Westend -> Millau Networks & Bridge" + echo " ./run.sh everything|all [stop|update] Run all available Networks & Bridges" + echo " " + echo "Options:" + echo " --no-monitoring Disable monitoring" + echo " --no-ui Disable UI" + echo " --local Use prebuilt local images when starting relay and nodes" + echo " --local-substrate-relay Use prebuilt local/substrate-realy image when starting relay" + echo " --local-rialto Use prebuilt local/rialto-bridge-node image when starting nodes" + echo " --local-rialto-parachain Use prebuilt local/rialto-parachain-collator image when starting nodes" + echo " --local-millau Use prebuilt local/millau-bridge-node image when starting nodes" + echo " " + echo "You can start multiple bridges at once by passing several bridge names:" + echo " ./run.sh rialto-millau rialto-parachain-millau westend-millau [stop|update]" + exit 1 +} + +RIALTO=' -f ./networks/rialto.yml' +RIALTO_PARACHAIN=' -f ./networks/rialto-parachain.yml' +MILLAU=' -f ./networks/millau.yml' + +RIALTO_MILLAU='rialto-millau' +RIALTO_PARACHAIN_MILLAU='rialto-parachain-millau' +WESTEND_MILLAU='westend-millau' + +MONITORING=' -f ./monitoring/docker-compose.yml' +UI=' -f ./ui/docker-compose.yml' + +BRIDGES=() +NETWORKS='' +SUB_COMMAND='start' +for i in "$@" +do + case $i in + --no-monitoring) + MONITORING=" -f ./monitoring/disabled.yml" + shift + continue + ;; + --no-ui) + UI="" + shift + continue + ;; + --local) + export SUBSTRATE_RELAY_IMAGE=local/substrate-relay + export RIALTO_BRIDGE_NODE_IMAGE=local/rialto-bridge-node + export RIALTO_PARACHAIN_COLLATOR_IMAGE=local/rialto-parachain-collator + export MILLAU_BRIDGE_NODE_IMAGE=local/millau-bridge-node + shift + continue + ;; + --local-substrate-relay) + export SUBSTRATE_RELAY_IMAGE=local/substrate-relay + shift + continue + ;; + --local-rialto) + export RIALTO_BRIDGE_NODE_IMAGE=local/rialto-bridge-node + shift + continue + ;; + --local-rialto-parachain) + export RIALTO_PARACHAIN_COLLATOR_IMAGE=local/rialto-parachain-collator + shift + continue + ;; + --local-millau) + export MILLAU_BRIDGE_NODE_IMAGE=local/millau-bridge-node + shift + continue + ;; + everything|all) + BRIDGES=(${RIALTO_MILLAU:-} ${RIALTO_PARACHAIN_MILLAU:-} ${WESTEND_MILLAU:-}) + NETWORKS="${RIALTO:-} ${RIALTO_PARACHAIN:-} ${MILLAU:-}" + unset RIALTO RIALTO_PARACHAIN MILLAU RIALTO_MILLAU RIALTO_PARACHAIN_MILLAU WESTEND_MILLAU + shift + ;; + rialto-millau) + BRIDGES+=(${RIALTO_MILLAU:-}) + NETWORKS+="${RIALTO:-} ${MILLAU:-}" + unset RIALTO MILLAU RIALTO_MILLAU + shift + ;; + rialto-parachain-millau) + BRIDGES+=(${RIALTO_PARACHAIN_MILLAU:-}) + NETWORKS+="${RIALTO:-} ${RIALTO_PARACHAIN:-} ${MILLAU:-}" + unset RIALTO RIALTO_PARACHAIN MILLAU RIALTO_PARACHAIN_MILLAU + shift + ;; + westend-millau) + BRIDGES+=(${WESTEND_MILLAU:-}) + NETWORKS+=${MILLAU:-} + unset MILLAU WESTEND_MILLAU + shift + ;; + start|stop|update) + SUB_COMMAND=$i + shift + ;; + *) + show_help "Unknown option: $i" + ;; + esac +done + +if [ ${#BRIDGES[@]} -eq 0 ]; then + show_help "Missing bridge name." +fi + +COMPOSE_FILES=$NETWORKS$MONITORING$UI + +# Compose looks for .env files in the the current directory by default, we don't want that +COMPOSE_ARGS="--project-directory ." +# Path to env file that we want to use. Compose only accepts single `--env-file` argument, +# so we'll be using the last .env file we'll found. +COMPOSE_ENV_FILE='' + +for BRIDGE in "${BRIDGES[@]}" +do + BRIDGE_PATH="./bridges/$BRIDGE" + BRIDGE=" -f $BRIDGE_PATH/docker-compose.yml" + COMPOSE_FILES=$BRIDGE$COMPOSE_FILES + + # Remember .env file to use in docker-compose call + if [[ -f "$BRIDGE_PATH/.env" ]]; then + COMPOSE_ENV_FILE=" --env-file $BRIDGE_PATH/.env" + fi + + # Read and source variables from .env file so we can use them here + grep -e MATRIX_ACCESS_TOKEN -e WITH_PROXY $BRIDGE_PATH/.env > .env2 && . ./.env2 && rm .env2 + if [ ! -z ${MATRIX_ACCESS_TOKEN+x} ]; then + sed -i "s/access_token.*/access_token: \"$MATRIX_ACCESS_TOKEN\"/" ./monitoring/grafana-matrix/config.yml + fi +done + +# Final COMPOSE_ARGS +COMPOSE_ARGS="$COMPOSE_ARGS $COMPOSE_ENV_FILE" + +# Check the sub-command, perhaps we just mean to stop the network instead of starting it. +if [ "$SUB_COMMAND" == "stop" ]; then + + if [ ! -z ${WITH_PROXY+x} ]; then + cd ./reverse-proxy + docker-compose down + cd - + fi + + docker-compose $COMPOSE_ARGS $COMPOSE_FILES down + + exit 0 +fi + +# See if we want to update the docker images before starting the network. +if [ "$SUB_COMMAND" == "update" ]; then + + # Stop the proxy cause otherwise the network can't be stopped + if [ ! -z ${WITH_PROXY+x} ]; then + cd ./reverse-proxy + docker-compose down + cd - + fi + + + docker-compose $COMPOSE_ARGS $COMPOSE_FILES pull + docker-compose $COMPOSE_ARGS $COMPOSE_FILES down + docker-compose $COMPOSE_ARGS $COMPOSE_FILES build +fi + +docker-compose $COMPOSE_ARGS $COMPOSE_FILES up -d + +# Start the proxy if needed +if [ ! -z ${WITH_PROXY+x} ]; then + cd ./reverse-proxy + docker-compose up -d +fi diff --git a/deployments/types-millau.json b/deployments/types-millau.json new file mode 100644 index 00000000000..6d651b4c7cf --- /dev/null +++ b/deployments/types-millau.json @@ -0,0 +1,192 @@ +{ + "--1": "Millau Types", + "MillauAddress": "AccountId", + "MillauLookupSource": "AccountId", + "MillauBalance": "u64", + "MillauBlockHash": "H512", + "MillauBlockNumber": "u64", + "MillauHeader": { + "parent_Hash": "MillauBlockHash", + "number": "Compact", + "state_root": "MillauBlockHash", + "extrinsics_root": "MillauBlockHash", + "digest": "MillauDigest" + }, + "MillauDigest": { + "logs": "Vec" + }, + "MillauDigestItem": { + "_enum": { + "Other": "Vec", + "AuthoritiesChange": "Vec", + "ChangesTrieRoot": "MillauBlockHash", + "SealV0": "SealV0", + "Consensus": "Consensus", + "Seal": "Seal", + "PreRuntime": "PreRuntime" + } + }, + "--2": "Rialto Types", + "RialtoAddress": "MultiAddress", + "RialtoLookupSource": "MultiAddress", + "RialtoBalance": "u128", + "RialtoBlockHash": "H256", + "RialtoBlockNumber": "u32", + "RialtoHeader": { + "parent_Hash": "RialtoBlockHash", + "number": "Compact", + "state_root": "RialtoBlockHash", + "extrinsics_root": "RialtoBlockHash", + "digest": "RialtoDigest" + }, + "RialtoDigest": { + "logs": "Vec" + }, + "RialtoDigestItem": { + "_enum": { + "Other": "Vec", + "AuthoritiesChange": "Vec", + "ChangesTrieRoot": "RialtoBlockHash", + "SealV0": "SealV0", + "Consensus": "Consensus", + "Seal": "Seal", + "PreRuntime": "PreRuntime" + } + }, + "--3": "Common types", + "AccountSigner": "MultiSigner", + "SpecVersion": "u32", + "RelayerId": "AccountId", + "SourceAccountId": "AccountId", + "ImportedHeader": { + "header": "BridgedHeader", + "requires_justification": "bool", + "is_finalized": "bool", + "signal_hash": "Option" + }, + "AuthoritySet": { + "authorities": "AuthorityList", + "set_id": "SetId" + }, + "Id": "[u8; 4]", + "ChainId": "Id", + "LaneId": "Id", + "MessageNonce": "u64", + "BridgeMessageId": "(Id, u64)", + "MessageKey": { + "lane_id": "LaneId", + "nonce:": "MessageNonce" + }, + "InboundRelayer": "AccountId", + "InboundLaneData": { + "relayers": "Vec", + "last_confirmed_nonce": "MessageNonce" + }, + "UnrewardedRelayer": { + "relayer": "RelayerId", + "messages": "DeliveredMessages" + }, + "DeliveredMessages": { + "begin": "MessageNonce", + "end": "MessageNonce", + "dispatch_results": "BitVec" + }, + "OutboundLaneData": { + "oldest_unpruned_nonce": "MessageNonce", + "latest_received_nonce": "MessageNonce", + "latest_generated_nonce": "MessageNonce" + }, + "MessageData": { + "payload": "MessagePayload", + "fee": "Fee" + }, + "MessagePayload": "Vec", + "BridgedOpaqueCall": "Vec", + "OutboundMessageFee": "Fee", + "OutboundPayload": { + "spec_version": "SpecVersion", + "weight": "Weight", + "origin": "CallOrigin", + "dispatch_fee_payment": "DispatchFeePayment", + "call": "BridgedOpaqueCall" + }, + "CallOrigin": { + "_enum": { + "SourceRoot": "()", + "TargetAccount": "(SourceAccountId, MultiSigner, MultiSignature)", + "SourceAccount": "SourceAccountId" + } + }, + "DispatchFeePayment": { + "_enum": { + "AtSourceChain": "()", + "AtTargetChain": "()" + } + }, + "MultiSigner": { + "_enum": { + "Ed25519": "H256", + "Sr25519": "H256", + "Ecdsa": "[u8;33]" + } + }, + "MessagesProofOf": { + "bridged_header_hash": "BridgedBlockHash", + "storage_proof": "Vec", + "lane": "LaneId", + "nonces_start": "MessageNonce", + "nonces_end": "MessageNonce" + }, + "StorageProofItem": "Vec", + "MessagesDeliveryProofOf": { + "bridged_header_hash": "BridgedBlockHash", + "storage_proof": "Vec", + "lane": "LaneId" + }, + "UnrewardedRelayersState": { + "unrewarded_relayer_entries": "MessageNonce", + "messages_in_oldest_entry": "MessageNonce", + "total_messages": "MessageNonce" + }, + "AncestryProof": "()", + "MessageFeeData": { + "lane_id": "LaneId", + "payload": "OutboundPayload" + }, + "Precommit": { + "target_hash": "BridgedBlockHash", + "target_number": "BridgedBlockNumber" + }, + "AuthoritySignature": "[u8;64]", + "AuthorityId": "[u8;32]", + "SignedPrecommit": { + "precommit": "Precommit", + "signature": "AuthoritySignature", + "id": "AuthorityId" + }, + "Commit": { + "target_hash": "BridgedBlockHash", + "target_number": "BridgedBlockNumber", + "precommits": "Vec" + }, + "GrandpaJustification": { + "round": "u64", + "commit": "Commit", + "votes_ancestries": "Vec" + }, + "Address": "MillauAddress", + "LookupSource": "MillauLookupSource", + "Fee": "MillauBalance", + "Balance": "MillauBalance", + "Hash": "MillauBlockHash", + "BlockHash": "MillauBlockHash", + "BlockNumber": "MillauBlockNumber", + "BridgedBlockHash": "RialtoBlockHash", + "BridgedBlockNumber": "RialtoBlockNumber", + "BridgedHeader": "RialtoHeader", + "Parameter": { + "_enum": { + "MillauToRialtoConversionRate": "u128" + } + } +} diff --git a/deployments/types-rialto.json b/deployments/types-rialto.json new file mode 100644 index 00000000000..a574e117893 --- /dev/null +++ b/deployments/types-rialto.json @@ -0,0 +1,192 @@ +{ + "--1": "Millau Types", + "MillauAddress": "AccountId", + "MillauLookupSource": "AccountId", + "MillauBalance": "u64", + "MillauBlockHash": "H512", + "MillauBlockNumber": "u64", + "MillauHeader": { + "parent_Hash": "MillauBlockHash", + "number": "Compact", + "state_root": "MillauBlockHash", + "extrinsics_root": "MillauBlockHash", + "digest": "MillauDigest" + }, + "MillauDigest": { + "logs": "Vec" + }, + "MillauDigestItem": { + "_enum": { + "Other": "Vec", + "AuthoritiesChange": "Vec", + "ChangesTrieRoot": "MillauBlockHash", + "SealV0": "SealV0", + "Consensus": "Consensus", + "Seal": "Seal", + "PreRuntime": "PreRuntime" + } + }, + "--2": "Rialto Types", + "RialtoAddress": "MultiAddress", + "RialtoLookupSource": "MultiAddress", + "RialtoBalance": "u128", + "RialtoBlockHash": "H256", + "RialtoBlockNumber": "u32", + "RialtoHeader": { + "parent_Hash": "RialtoBlockHash", + "number": "Compact", + "state_root": "RialtoBlockHash", + "extrinsics_root": "RialtoBlockHash", + "digest": "RialtoDigest" + }, + "RialtoDigest": { + "logs": "Vec" + }, + "RialtoDigestItem": { + "_enum": { + "Other": "Vec", + "AuthoritiesChange": "Vec", + "ChangesTrieRoot": "RialtoBlockHash", + "SealV0": "SealV0", + "Consensus": "Consensus", + "Seal": "Seal", + "PreRuntime": "PreRuntime" + } + }, + "--3": "Common types", + "AccountSigner": "MultiSigner", + "SpecVersion": "u32", + "RelayerId": "AccountId", + "SourceAccountId": "AccountId", + "ImportedHeader": { + "header": "BridgedHeader", + "requires_justification": "bool", + "is_finalized": "bool", + "signal_hash": "Option" + }, + "AuthoritySet": { + "authorities": "AuthorityList", + "set_id": "SetId" + }, + "Id": "[u8; 4]", + "ChainId": "Id", + "LaneId": "Id", + "MessageNonce": "u64", + "BridgeMessageId": "(Id, u64)", + "MessageKey": { + "lane_id": "LaneId", + "nonce:": "MessageNonce" + }, + "InboundRelayer": "AccountId", + "InboundLaneData": { + "relayers": "Vec", + "last_confirmed_nonce": "MessageNonce" + }, + "UnrewardedRelayer": { + "relayer": "RelayerId", + "messages": "DeliveredMessages" + }, + "DeliveredMessages": { + "begin": "MessageNonce", + "end": "MessageNonce", + "dispatch_results": "BitVec" + }, + "OutboundLaneData": { + "oldest_unpruned_nonce": "MessageNonce", + "latest_received_nonce": "MessageNonce", + "latest_generated_nonce": "MessageNonce" + }, + "MessageData": { + "payload": "MessagePayload", + "fee": "Fee" + }, + "MessagePayload": "Vec", + "BridgedOpaqueCall": "Vec", + "OutboundMessageFee": "Fee", + "OutboundPayload": { + "spec_version": "SpecVersion", + "weight": "Weight", + "origin": "CallOrigin", + "dispatch_fee_payment": "DispatchFeePayment", + "call": "BridgedOpaqueCall" + }, + "CallOrigin": { + "_enum": { + "SourceRoot": "()", + "TargetAccount": "(SourceAccountId, MultiSigner, MultiSignature)", + "SourceAccount": "SourceAccountId" + } + }, + "DispatchFeePayment": { + "_enum": { + "AtSourceChain": "()", + "AtTargetChain": "()" + } + }, + "MultiSigner": { + "_enum": { + "Ed25519": "H256", + "Sr25519": "H256", + "Ecdsa": "[u8;33]" + } + }, + "MessagesProofOf": { + "bridged_header_hash": "BridgedBlockHash", + "storage_proof": "Vec", + "lane": "LaneId", + "nonces_start": "MessageNonce", + "nonces_end": "MessageNonce" + }, + "StorageProofItem": "Vec", + "MessagesDeliveryProofOf": { + "bridged_header_hash": "BridgedBlockHash", + "storage_proof": "Vec", + "lane": "LaneId" + }, + "UnrewardedRelayersState": { + "unrewarded_relayer_entries": "MessageNonce", + "messages_in_oldest_entry": "MessageNonce", + "total_messages": "MessageNonce" + }, + "AncestryProof": "()", + "MessageFeeData": { + "lane_id": "LaneId", + "payload": "OutboundPayload" + }, + "Precommit": { + "target_hash": "BridgedBlockHash", + "target_number": "BridgedBlockNumber" + }, + "AuthoritySignature": "[u8;64]", + "AuthorityId": "[u8;32]", + "SignedPrecommit": { + "precommit": "Precommit", + "signature": "AuthoritySignature", + "id": "AuthorityId" + }, + "Commit": { + "target_hash": "BridgedBlockHash", + "target_number": "BridgedBlockNumber", + "precommits": "Vec" + }, + "GrandpaJustification": { + "round": "u64", + "commit": "Commit", + "votes_ancestries": "Vec" + }, + "Address": "RialtoAddress", + "LookupSource": "RialtoLookupSource", + "Fee": "RialtoBalance", + "Balance": "RialtoBalance", + "BlockHash": "RialtoBlockHash", + "BlockNumber": "RialtoBlockNumber", + "BridgedBlockHash": "MillauBlockHash", + "BridgedBlockNumber": "MillauBlockNumber", + "BridgedHeader": "MillauHeader", + "Parameter": { + "_enum": { + "RialtoToMillauConversionRate": "u128" + } + }, + "ValidationCodeHash": "H256" +} diff --git a/deployments/types/build.sh b/deployments/types/build.sh new file mode 100755 index 00000000000..be96459a6da --- /dev/null +++ b/deployments/types/build.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# The script generates JSON type definition files in `./deployment` directory to be used for +# JS clients. +# +# It works by creating definitions for each side of the different bridge pairs we support +# (Rialto<>Millau at the moment). +# +# To avoid duplication each bridge pair has a JSON file with common definitions, as well as a +# general JSON file with common definitions regardless of the bridge pair. These files are then +# merged with chain-specific type definitions. + +set -eux + +# Make sure we are in the right dir. +cd $(dirname $(realpath $0)) + +# Create types for our supported bridge pairs (Rialto<>Millau) +jq -s '.[0] * .[1] * .[2]' rialto-millau.json common.json rialto.json > ../types-rialto.json +jq -s '.[0] * .[1] * .[2]' rialto-millau.json common.json millau.json > ../types-millau.json diff --git a/deployments/types/common.json b/deployments/types/common.json new file mode 100644 index 00000000000..4e129f7132b --- /dev/null +++ b/deployments/types/common.json @@ -0,0 +1,124 @@ +{ + "--3": "Common types", + "AccountSigner": "MultiSigner", + "SpecVersion": "u32", + "RelayerId": "AccountId", + "SourceAccountId": "AccountId", + "ImportedHeader": { + "header": "BridgedHeader", + "requires_justification": "bool", + "is_finalized": "bool", + "signal_hash": "Option" + }, + "AuthoritySet": { + "authorities": "AuthorityList", + "set_id": "SetId" + }, + "Id": "[u8; 4]", + "ChainId": "Id", + "LaneId": "Id", + "MessageNonce": "u64", + "BridgeMessageId": "(Id, u64)", + "MessageKey": { + "lane_id": "LaneId", + "nonce:": "MessageNonce" + }, + "InboundRelayer": "AccountId", + "InboundLaneData": { + "relayers": "Vec", + "last_confirmed_nonce": "MessageNonce" + }, + "UnrewardedRelayer": { + "relayer": "RelayerId", + "messages": "DeliveredMessages" + }, + "DeliveredMessages": { + "begin": "MessageNonce", + "end": "MessageNonce", + "dispatch_results": "BitVec" + }, + "OutboundLaneData": { + "oldest_unpruned_nonce": "MessageNonce", + "latest_received_nonce": "MessageNonce", + "latest_generated_nonce": "MessageNonce" + + }, + "MessageData": { + "payload": "MessagePayload", + "fee": "Fee" + }, + "MessagePayload": "Vec", + "BridgedOpaqueCall": "Vec", + "OutboundMessageFee": "Fee", + "OutboundPayload": { + "spec_version": "SpecVersion", + "weight": "Weight", + "origin": "CallOrigin", + "dispatch_fee_payment": "DispatchFeePayment", + "call": "BridgedOpaqueCall" + }, + "CallOrigin": { + "_enum": { + "SourceRoot": "()", + "TargetAccount": "(SourceAccountId, MultiSigner, MultiSignature)", + "SourceAccount": "SourceAccountId" + } + }, + "DispatchFeePayment": { + "_enum": { + "AtSourceChain": "()", + "AtTargetChain": "()" + } + }, + "MultiSigner": { + "_enum": { + "Ed25519": "H256", + "Sr25519": "H256", + "Ecdsa": "[u8;33]" + } + }, + "MessagesProofOf": { + "bridged_header_hash": "BridgedBlockHash", + "storage_proof": "Vec", + "lane": "LaneId", + "nonces_start": "MessageNonce", + "nonces_end": "MessageNonce" + }, + "StorageProofItem": "Vec", + "MessagesDeliveryProofOf": { + "bridged_header_hash": "BridgedBlockHash", + "storage_proof": "Vec", + "lane": "LaneId" + }, + "UnrewardedRelayersState": { + "unrewarded_relayer_entries": "MessageNonce", + "messages_in_oldest_entry": "MessageNonce", + "total_messages": "MessageNonce" + }, + "AncestryProof": "()", + "MessageFeeData": { + "lane_id": "LaneId", + "payload": "OutboundPayload" + }, + "Precommit": { + "target_hash": "BridgedBlockHash", + "target_number": "BridgedBlockNumber" + }, + "AuthoritySignature": "[u8;64]", + "AuthorityId": "[u8;32]", + "SignedPrecommit": { + "precommit": "Precommit", + "signature": "AuthoritySignature", + "id": "AuthorityId" + }, + "Commit": { + "target_hash": "BridgedBlockHash", + "target_number": "BridgedBlockNumber", + "precommits": "Vec" + }, + "GrandpaJustification": { + "round": "u64", + "commit": "Commit", + "votes_ancestries": "Vec" + } +} diff --git a/deployments/types/millau.json b/deployments/types/millau.json new file mode 100644 index 00000000000..589d5619df4 --- /dev/null +++ b/deployments/types/millau.json @@ -0,0 +1,17 @@ +{ + "Address": "MillauAddress", + "LookupSource": "MillauLookupSource", + "Fee": "MillauBalance", + "Balance": "MillauBalance", + "Hash": "MillauBlockHash", + "BlockHash": "MillauBlockHash", + "BlockNumber": "MillauBlockNumber", + "BridgedBlockHash": "RialtoBlockHash", + "BridgedBlockNumber": "RialtoBlockNumber", + "BridgedHeader": "RialtoHeader", + "Parameter": { + "_enum": { + "MillauToRialtoConversionRate": "u128" + } + } +} diff --git a/deployments/types/rialto-millau.json b/deployments/types/rialto-millau.json new file mode 100644 index 00000000000..971cf666d47 --- /dev/null +++ b/deployments/types/rialto-millau.json @@ -0,0 +1,56 @@ +{ + "--1": "Millau Types", + "MillauAddress": "AccountId", + "MillauLookupSource": "AccountId", + "MillauBalance": "u64", + "MillauBlockHash": "H512", + "MillauBlockNumber": "u64", + "MillauHeader": { + "parent_Hash": "MillauBlockHash", + "number": "Compact", + "state_root": "MillauBlockHash", + "extrinsics_root": "MillauBlockHash", + "digest": "MillauDigest" + }, + "MillauDigest": { + "logs": "Vec" + }, + "MillauDigestItem": { + "_enum": { + "Other": "Vec", + "AuthoritiesChange": "Vec", + "ChangesTrieRoot": "MillauBlockHash", + "SealV0": "SealV0", + "Consensus": "Consensus", + "Seal": "Seal", + "PreRuntime": "PreRuntime" + } + }, + "--2": "Rialto Types", + "RialtoAddress": "MultiAddress", + "RialtoLookupSource": "MultiAddress", + "RialtoBalance": "u128", + "RialtoBlockHash": "H256", + "RialtoBlockNumber": "u32", + "RialtoHeader": { + "parent_Hash": "RialtoBlockHash", + "number": "Compact", + "state_root": "RialtoBlockHash", + "extrinsics_root": "RialtoBlockHash", + "digest": "RialtoDigest" + }, + "RialtoDigest": { + "logs": "Vec" + }, + "RialtoDigestItem": { + "_enum": { + "Other": "Vec", + "AuthoritiesChange": "Vec", + "ChangesTrieRoot": "RialtoBlockHash", + "SealV0": "SealV0", + "Consensus": "Consensus", + "Seal": "Seal", + "PreRuntime": "PreRuntime" + } + } +} diff --git a/deployments/types/rialto.json b/deployments/types/rialto.json new file mode 100644 index 00000000000..77c30b7cc2d --- /dev/null +++ b/deployments/types/rialto.json @@ -0,0 +1,17 @@ +{ + "Address": "RialtoAddress", + "LookupSource": "RialtoLookupSource", + "Fee": "RialtoBalance", + "Balance": "RialtoBalance", + "BlockHash": "RialtoBlockHash", + "BlockNumber": "RialtoBlockNumber", + "BridgedBlockHash": "MillauBlockHash", + "BridgedBlockNumber": "MillauBlockNumber", + "BridgedHeader": "MillauHeader", + "Parameter": { + "_enum": { + "RialtoToMillauConversionRate": "u128" + } + }, + "ValidationCodeHash": "H256" +} diff --git a/deployments/ui/README.md b/deployments/ui/README.md new file mode 100644 index 00000000000..ad946fc699b --- /dev/null +++ b/deployments/ui/README.md @@ -0,0 +1,23 @@ +# bridges-ui + +This is a Bridges UI docker configuration file. The source of the Bridges UI code +can be found in [the repository](https://github.com/paritytech/parity-bridges-ui). +The CI should create and publish a docker image that is used by this configuration +file, so that the code is always using the latest version. +The UI is configured to point to local Rialto and Millau nodes to retrieve the require +data. + +This image can be used together with `nginx-proxy` to expose the UI externally. See +`VIRTUAL_*` and `LETSENCRYPT_*` environment variables. + +After start the UI is available at `http://localhost:8080` + +## How to? + +In current directory: +```bash +docker-compose up -d +``` + +Then start `rialto` & `millau` networks with the same command (one folder up) or +run the full setup by using `../run.sh` script. diff --git a/deployments/ui/docker-compose.yml b/deployments/ui/docker-compose.yml new file mode 100644 index 00000000000..8b3f8178c36 --- /dev/null +++ b/deployments/ui/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.5' +services: + bridges-ui: + image: paritytech/parity-bridges-ui + environment: + VIRTUAL_HOST: ui.brucke.link + VIRTUAL_PORT: 80 + LETSENCRYPT_HOST: ui.brucke.link + LETSENCRYPT_EMAIL: admin@parity.io + CHAIN_1_SUBSTRATE_PROVIDER: ${UI_CHAIN_1:-ws://localhost:9944} + CHAIN_2_SUBSTRATE_PROVIDER: ${UI_CHAIN_2:-ws://localhost:19944} + ports: + - "8080:80" diff --git a/docs/high-level-overview.md b/docs/high-level-overview.md new file mode 100644 index 00000000000..2642c20c86a --- /dev/null +++ b/docs/high-level-overview.md @@ -0,0 +1,165 @@ +# High-Level Bridge Documentation + +## Purpose + +Trustless connecting between two Substrate-based chains using GRANDPA finality. + +## Overview + +Even though we support two-way bridging, the documentation will generally talk about a one-sided +interaction. That's to say, we will only talk about syncing headers and messages from a _source_ +chain to a _target_ chain. This is because the two-sided interaction is really just the one-sided +interaction with the source and target chains switched. + +To understand the full interaction with the bridge, take a look at the +[testing scenarios](./testing-scenarios.md) document. It describes potential use cases and describes +how each of the layers outlined below is involved. + +The bridge is built from various components. Here is a quick overview of the important ones. + +### Header Sync + +A light client of the source chain built into the target chain's runtime. It is a single FRAME +pallet. It provides a "source of truth" about the source chain headers which have been finalized. +This is useful for higher level applications. + +### Headers Relayer + +A standalone application connected to both chains. It submits every source chain header it sees to +the target chain through RPC. + +### Message Delivery + +A FRAME pallet built on top of the header sync pallet. It allows users to submit messages to the +source chain, which are to be delivered to the target chain. The delivery protocol doesn't care +about the payload more than it has to. Handles replay protection and message ordering. + +### Message Dispatch + +A FRAME pallet responsible for interpreting the payload of delivered messages. + +### Message Relayer + +A standalone application handling delivery of the messages from source chain to the target chain. + +## Processes + +High level sequence charts of the process can be found in [a separate document](./high-level.html). + +### Substrate (GRANDPA) Header Sync + +The header sync pallet (`pallet-bridge-grandpa`) is an on-chain light client for chains which use +GRANDPA finality. It is part of the target chain's runtime, and accepts finality proofs from the source +chain. Verify GRANDPA finality proofs (a.k.a justifications) and track GRANDPA finality set changes. + +The pallet does not care about what block production mechanism is used for the source chain +(e.g Aura or BABE) as long as it uses the GRANDPA finality gadget. In fact the pallet does not +necessarily store all produced headers, we only import headers with valid GRANDPA justifications. + +Referer to the [pallet documentation](../modules/grandpa/src/lib.rs) for more details. + +#### Header Relayer strategy + +There is currently no reward strategy for the relayers at all. They also are not required to be +staked or registered on-chain, unlike in other bridge designs. We consider the header sync to be +an essential part of the bridge and the incentivization should be happening on the higher layers. + +At the moment, signed transactions are the only way to submit headers to the header sync pallet. +However, in the future we would like to use unsigned transactions for headers delivery. This will +allow transaction de-duplication to be done at the transaction pool level and also remove the cost +for message relayers to run header relayers. + +### Message Passing + +Once header sync is maintained, the target side of the bridge can receive and verify proofs about +events happening on the source chain, or its internal state. On top of this, we built a message +passing protocol which consists of two parts described in following sections: message delivery and +message dispatch. + +#### Message Lanes Delivery + +The [Message delivery pallet](../modules/messages/src/lib.rs) is responsible for queueing up +messages and delivering them in order on the target chain. It also dispatches messages, but we will +cover that in the next section. + +The pallet supports multiple lanes (channels) where messages can be added. Every lane can be +considered completely independent from others, which allows them to make progress in parallel. +Different lanes can be configured to validated messages differently (e.g higher rewards, specific +types of payload, etc.) and may be associated with a particular "user application" built on top of +the bridge. Note that messages in the same lane MUST be delivered _in the same order_ they were +queued up. + +The message delivery protocol does not care about the payload it transports and can be coupled +with an arbitrary message dispatch mechanism that will interpret and execute the payload if delivery +conditions are met. Each delivery on the target chain is confirmed back to the source chain by the +relayer. This is so that she can collect the reward for delivering these messages. + +Users of the pallet add their messages to an "outbound lane" on the source chain. When a block is +finalized message relayers are responsible for reading the current queue of messages and submitting +some (or all) of them to the "inbound lane" of the target chain. Each message has a `nonce` +associated with it, which serves as the ordering of messages. The inbound lane stores the last +delivered nonce to prevent replaying messages. To successfully deliver the message to the inbound lane +on target chain the relayer has to present present a storage proof which shows that the message was +part of the outbound lane on the source chain. + +During delivery of messages they are immediately dispatched on the target chain and the relayer is +required to declare the correct `weight` to cater for all messages dispatch and pay all required +fees of the target chain. To make sure the relayer is incentivised to do so, on the source chain: +- the user provides a declared dispatch weight of the payload +- the pallet calculates the expected fee on the target chain based on the declared weight +- the pallet converts the target fee into source tokens (based on a price oracle) and reserves + enough tokens to cover for the delivery, dispatch, confirmation and additional relayers reward. + +If the declared weight turns out to be too low on the target chain the message is delivered but +it immediately fails to dispatch. The fee and reward is collected by the relayer upon confirmation +of delivery. + +Due to the fact that message lanes require delivery confirmation transactions, they also strictly +require bi-directional header sync (i.e. you can't use message delivery with one-way header sync). + +#### Dispatching Messages + +The [Message dispatch pallet](../modules/dispatch/src/lib.rs) is used to perform the actions +specified by messages which have come over the bridge. For Substrate-based chains this means +interpreting the source chain's message as a `Call` on the target chain. + +An example `Call` of the target chain would look something like this: + +```rust +target_runtime::Call::Balances(target_runtime::pallet_balances::Call::transfer(recipient, amount)) +``` + +When sending a `Call` it must first be SCALE encoded and then sent to the source chain. The `Call` +is then delivered by the message lane delivery mechanism from the source chain to the target chain. +When a message is received the inbound message lane on the target chain will try and decode the +message payload into a `Call` enum. If it's successful it will be dispatched after we check that the +weight of the call does not exceed the weight declared by the sender. The relayer pays fees for +executing the transaction on the target chain, but her costs should be covered by the sender on the +source chain. + +When dispatching messages there are three Origins which can be used by the target chain: +1. Root Origin +2. Source Origin +3. Target Origin + +Senders of a message can indicate which one of the three origins they would like to dispatch their +message with. However, there are restrictions on who/what is allowed to dispatch messages with a +particular origin. + +The Root origin represents the source chain's Root account on the target chain. This origin can can +only be dispatched on the target chain if the "send message" request was made by the Root origin of +the source chain - otherwise the message will fail to be dispatched. + +The Source origin represents an account without a private key on the target chain. This account will +be generated/derived using the account ID of the sender on the source chain. We don't necessarily +require the source account id to be associated with a private key on the source chain either. This +is useful for representing things such as source chain proxies or pallets. + +The Target origin represents an account with a private key on the target chain. The sender on the +source chain needs to prove ownership of this account by using their target chain private key to +sign: `(Call, SourceChainAccountId).encode()`. This will be included in the message payload and +verified by the target chain before dispatch. + +See [`CallOrigin` documentation](../primitives/message-dispatch/src/lib.rs) for more details. + +#### Message Relayers Strategy diff --git a/docs/high-level.html b/docs/high-level.html new file mode 100644 index 00000000000..3c4c6178c95 --- /dev/null +++ b/docs/high-level.html @@ -0,0 +1,55 @@ + + + + + + High Level Bridge Components + + +

Header Sync

+

Header pallet on the target chain, keeps track of the forks, but requires finality for blocks that perform authority set changes. That means, it won't sync a fork with authority set change unless that change finalized.

+
+ sequenceDiagram + participant Source Chain + participant Relayer + participant Target Chain + Note right of Target Chain: Best: 0, Finalized: 0 + Source Chain ->> Source Chain: Import Block 1 + Source Chain ->> Source Chain: Import Block 2 + Relayer ->> Target Chain: Submit Block 1 + Note right of Target Chain: Best: 1, Finalized: 0 + Relayer ->> Target Chain: Submit Block 2 + Note right of Target Chain: Best: 2, Finalized: 0 + Source Chain ->> Source Chain: Import Block 2' + Relayer ->> Target Chain: Submit Block 2' + Note right of Target Chain: Best: 2 or 2', Finalized: 0 + Source Chain ->> Source Chain: Finalize Block 2' + Relayer ->> Target Chain: Submit Finality of Block 2' + Note right of Target Chain: Best: 2', Finalized: 2' +
+

Message Delivery (single lane)

+

Pending messages are stored on-chain (source) so the relayer code is completely stateless - it can read all the details from the chain.

+

Delivering pending messages requires finality first.

+
+ sequenceDiagram + participant Source Chain + participant Relayer + participant Target Chain + Source Chain ->> Source Chain: Queue Message 1 + Source Chain ->> Source Chain: Queue Message 2 + Source Chain ->> Source Chain: Queue Message 3 + Note left of Source Chain: Queued Messages: [1, 2, 3, ] + Note left of Source Chain: Reward for [1, 2, 3, ] reserved + Relayer ->> Target Chain: Deliver Messages 1..2 + Note right of Target Chain: Target chain dispatches the messages.
To Confirm: {1..2 => relayer_1} + Relayer ->> Source Chain: Delivery Confirmation of 1..2 + Note left of Source Chain: Queued Messages: [3, ] + Note left of Source Chain: Reward payout for [1, 2, ] + Relayer -->> Target Chain: Confirmed Messages 1..2 + Note right of Target Chain: To Confirm: {} + Note over Relayer, Target Chain: (this is not a separate transaction,
it's bundled with the "Deliver Messages" proof) +
+ + + + diff --git a/docs/plan.md b/docs/plan.md new file mode 100644 index 00000000000..9c4106d9ade --- /dev/null +++ b/docs/plan.md @@ -0,0 +1,22 @@ +Plan for the Internal Audit: +1. High-level overview (describing layers, maybe with pictures) + - what have we done already. + [Tomek to present] + [Hernando to help with diagrams today] + +2. Demo? How to play with the network. + [Hernando] + +3. Demo of token transfer on Millau. + [Hernando] + +4. Go through the scenario description and let people ask questions in the meantime. + Jump to the code on demand. + [Tomek, Hernando, Slava] + + ... + +5. The roadmap + - outstanding issues. + [Tomek] + diff --git a/docs/scenario1.html b/docs/scenario1.html new file mode 100644 index 00000000000..808a0c34f0d --- /dev/null +++ b/docs/scenario1.html @@ -0,0 +1,47 @@ + + + + + + Flow Chart of Millau to Rialto Transfer + + +

Scenario: mDave sending RLT to rEve

+
+ sequenceDiagram + participant mDave + participant Millau + participant Bridge Relayer + participant Rialto + participant rEve + Rialto->>Rialto: Endow r(mDave) with RLT. + mDave->>Millau: send_message(transfer, 5 RLT, rEve) + Millau->>Millau: Locks fee & reward for the relayer and queues the message. + rect rgb(205, 226, 244) + Bridge Relayer->>+Millau: What's your best header? + Millau-->>-Bridge Relayer: It's header 5. + Bridge Relayer->>+Rialto: What's the best Millau header you know about? + Rialto-->>-Bridge Relayer: I only know about 4. + Bridge Relayer->>Rialto: Cool, here is Millau header 5 [`submit_signed_header()`]. + Bridge Relayer->>+Rialto: What's the best finalized Millau header you know about? + Rialto-->>-Bridge Relayer: I only know about 3. + Bridge Relayer->>+Millau: Do you have a finality proof for 4..5? + Millau-->>-Bridge Relayer: Yes I do, here it is. + Bridge Relayer->>Rialto: Here is the finality proof for 5 [`finalize_header()`]. + end + rect rgb(218, 195, 244) + Bridge Relayer->>+Millau: Do you have any messages for me to deliver (at 5)? + Millau-->>-Bridge Relayer: Yes, here they are. + Bridge Relayer->>+Rialto: I have some new messages for you [`receive_messages_proof()`]. + Rialto->>Rialto: Validate and Dispatch Message. + Rialto->>rEve: Transfer(5 RLT) from r(mDave). + Rialto-->>-Bridge Relayer: Event(Message Succesfully Dispatched). + Bridge Relayer->>Millau: I sent your message, can I get paid now [`receive_messages_delivery_proof`]? + Millau-->>Bridge Relayer: Yes, here you go $$$. + Bridge Relayer ->>Rialto: These messages are confirmed now, feel free to clean up. + end +
+ + + + diff --git a/docs/send-message.md b/docs/send-message.md new file mode 100644 index 00000000000..6984c56d67f --- /dev/null +++ b/docs/send-message.md @@ -0,0 +1,131 @@ +# How to send messages + +The Substrate-to-Substrate relay comes with a command line interface (CLI) which is implemented +by the `substrate-relay` binary. + +``` +Substrate-to-Substrate relay + +USAGE: + substrate-relay + +FLAGS: + -h, --help + Prints help information + + -V, --version + Prints version information + + +SUBCOMMANDS: + help Prints this message or the help of the given subcommand(s) + init-bridge Initialize on-chain bridge pallet with current header data + relay-headers Start headers relay between two chains + relay-messages Start messages relay between two chains + send-message Send custom message over the bridge +``` +The relay related commands `relay-headers` and `relay-messages` are basically continously running a +sync loop between the `Millau` and `Rialto` chains. The `init-bridge` command submitts initialization +transactions. An initialization transaction brings an initial header and authorities set from a source +chain to a target chain. The header synchronization then starts from that header. + +For sending custom messages over an avialable bridge, the `send-message` command is used. + +``` +Send custom message over the bridge. + +Allows interacting with the bridge by sending messages over `Messages` component. The message is being sent to the +source chain, delivered to the target chain and dispatched there. + +USAGE: + substrate-relay send-message + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +SUBCOMMANDS: + help Prints this message or the help of the given subcommand(s) + millau-to-rialto Submit message to given Millau -> Rialto lane + rialto-to-millau Submit message to given Rialto -> Millau lane + +``` +Messages are send from a source chain to a target chain using a so called `message lane`. Message lanes handle +both, message transport and message dispatch. There is one command for submitting a message to each of the two +available bridges, namely `millau-to-rialto` and `rialto-to-millau`. + +Submitting a message requires a number of arguments to be provided. Those arguments are essentially the same +for both submit message commands, hence only the output for `millau-to-rialto` is shown below. + +``` +Submit message to given Millau -> Rialto lane + +USAGE: + substrate-relay send-message millau-to-rialto [OPTIONS] --lane --source-host --source-port --source-signer --origin --target-signer + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + --fee + Delivery and dispatch fee. If not passed, determined automatically + + --lane Hex-encoded lane id + --source-host Connect to Source node at given host + --source-port Connect to Source node websocket server at given port + --source-signer + The SURI of secret key to use when transactions are submitted to the Source node + + --source-signer-password + The password for the SURI of secret key to use when transactions are submitted to the Source node + + --origin + The origin to use when dispatching the message on the target chain [possible values: Target, Source] + + --target-signer + The SURI of secret key to use when transactions are submitted to the Target node + + --target-signer-password + The password for the SURI of secret key to use when transactions are submitted to the Target node + + +SUBCOMMANDS: + help Prints this message or the help of the given subcommand(s) + remark Make an on-chain remark (comment) + transfer Transfer the specified `amount` of native tokens to a particular `recipient` + +``` +As can be seen from the output, there are two types of messages available: `remark` and `transfer`. +A remark is some opaque message which will be placed on-chain. For basic testing, a remark is +the easiest to go with. + +Usage of the arguments is best explained with an example. Below you can see, how a remark +would look like: + +``` +substrate-relay send-message millau-to-rialto \ + --source-host=127.0.0.1 \ + --source-port=10946 \ + --source-signer=//Dave \ + --target-signer=//Dave \ + --lane=00000000 \ + --origin Target \ + remark +``` +Messages are basically regular transactions. That means, they have to be signed. In order +to send a message, you have to control an account private key on both, the source and +the target chain. Those accounts are specified using the `--source-signer` and `--target-signer` +arguments in the example above. + +Message delivery and dispatch requires a fee to be paid. In the example above, we have not +specified the `--fee` argument. Hence, the fee will be estimated automatically. Note that +in order to pay the fee, the message sender account has to have sufficient funds available. + +The `--origin` argument allows to denote under which authority the message will be dispatched +on the target chain. Accepted values are `Target` and `Source`. + +Although not strictly necessary, it is recommended, to use one of the well-known development +accounts (`Alice`, `Bob`, `Charlie`, `Dave`, `Eve`) for message sending. Those accounts are +endowed with funds for fee payment. In addtion, the development `Seed URI` syntax +(like `//Dave`) for the signer can be used, which will remove the need for a password. diff --git a/docs/testing-scenarios.md b/docs/testing-scenarios.md new file mode 100644 index 00000000000..343720524ec --- /dev/null +++ b/docs/testing-scenarios.md @@ -0,0 +1,221 @@ +# Testing Scenarios + +In the scenarios, for simplicity, we call the chains Kusama (KSM token) and Polkadot (DOT token), +but they should be applicable to any other chains. The first scenario has detailed description about +the entire process (also see the [sequence diagram](./scenario1.html)). Other scenarios only contain +a simplified interaction focusing on things that are unique for that particular scenario. + +Notation: +- kX - user X interacting with Kusama chain. +- `k(kX)` - Kusama account id of user kX (native account id; usable on Kusama) +- `p(kX)` - Polkadot account id of user kX (account id derived from `k(kX)` usable on Polkadot) +- [Kusama] ... - Interaction happens on Kusama (e.g. the user interacts with Kusama chain) +- [Polkadot] ... - Interaction happens on Polkadot + +Basic Scenarios +=========================== + +Scenario 1: Kusama's Alice receiving & spending DOTs +--------------------------- + +Kusama's Alice (kAlice) receives 5 DOTs from Polkadot's Bob (pBob) and sends half of them to +kCharlie. + +1. Generate kAlice's DOT address (`p(kAlice)`). + See function: + + ```rust + bp_runtime::derive_account_id(b"pdot", kAlice) + ``` + + or: + + ```rust + let hash = bp_polkadot::derive_kusama_account_id(kAlice); + let p_kAlice = bp_polkadot::AccountIdConverter::convert(hash); + ``` + +2. [Polkadot] pBob transfers 5 DOTs to `p(kAlice)` + 1. Creates & Signs a transaction with `Call::Transfer(..)` + 1. It is included in block. + 1. kAlice observers Polkadot chain to see her balance at `p(kAlice)` updated. + +3. [Kusama] kAlice sends 2.5 DOTs to `p(kCharlie)` + 1. kAlice prepares: + ```rust + let call = polkadot::Call::Balances(polkadot::Balances::Transfer(p(kCharlie), 2.5DOT)).encode(); + let weight = call.get_dispatch_info().weight; + ``` + + 1. kAlice prepares Kusama transaction: + ```rust + kusama::Call::Messages::::send_message( + // dot-transfer-lane (truncated to 4bytes) + lane_id, + payload: MessagePayload { + // Get from current polkadot runtime (kind of hardcoded) + spec_version: 1, + // kAlice should know the exact dispatch weight of the call on the target + // source verifies: at least to cover call.length() and below max weight + weight, + // simply bytes, we don't know anything about that on the source chain + call, + // origin that should be used during dispatch on the target chain + origin: CallOrigin::SourceAccount(kAlice), + }, + delivery_and_dispatch_fee: { + (single_message_delivery_weight + // source weight = X * target weight + + convert_target_weight_to_source_weight(weight) + + confirmation_transaction_weight + ) + // This uses an on-chain oracle to convert weights of the target chain to source fee + * weight_to_fee + // additional reward for the relayer (pallet parameter) + + relayers_fee + }, + ) + ``` + + 1. [Kusama] kAlice sends Kusama transaction with the above `Call` and pays regular fees. The + dispatch additionally reservers target-chain delivery and dispatch fees (including relayer's + reward). + +4. [Kusama] kAlice's transaction is included in block `B1` + +### Syncing headers loop + +5. Relayer sees that `B1` has not yet been delivered to the target chain. + [Sync loop code](https://github.com/paritytech/parity-bridges-common/blob/8b327a94595c4a6fae6d7866e24ecf2390501e32/relays/headers-relay/src/sync_loop.rs#L199). + +1. Relayer prepares transaction which delivers `B1` and with all of the missing + ancestors to the target chain (one header per transaction). + +1. After the transaction is succesfully dispatched the Polkadot on-chain light client of the Kusama + chain learns about block `B1` - it is stored in the on-chain storage. + +### Syncing finality loop + +8. Relayer is subscribed to finality events on Kusama. Relayer gets a finality notification for + block `B3`. + +1. The header sync informs the target chain about `B1..B3` blocks (see point 6). + +1. Relayer learns about missing finalization of `B1..B3` on the target chain, see + [finality maintenance code](https://github.com/paritytech/parity-bridges-common/blob/8b327a94595c4a6fae6d7866e24ecf2390501e32/relays/substrate/src/headers_maintain.rs#L107). + +1. Relayer submits justification for `B3` to the target chain (`finalize_header`). + See [#421](https://github.com/paritytech/parity-bridges-common/issues/421) for multiple + authority set changes support in Relayer (i.e. what block the target chain expects, not only + what I have). + + Relayer is doing two things: + - syncing on demand (what blocks miss finality) + - and syncing as notifications are received (recently finalized on-chain) + +1. Eventually Polkadot on-chain light client of Kusama learns about finality of `B1`. + +### Syncing messages loop + +13. The relayer checks the on-chain storage (last finalized header on the source, best header on the + target): + - Kusama outbound lane + - Polkadot inbound lane + Lanes contains `latest_generated_nonce` and `latest_received_nonce` respectively. The relayer + syncs messages between that range. + +1. The relayer gets a proof for every message in that range (using the RPC of messages module) + +1. The relayer creates a message delivery transaction (but it has weight, size, and count limits). + The count limit is there to make the loop of delivery code bounded. + ```rust + receive_message_proof( + relayer_id, // account id of the source chain + proof, // messages + proofs (hash of source block `B1`, nonces, lane_id + storage proof) + dispatch_weight // relayer declares how much it will take to dispatch all messages in that transaction, + ) + ``` + The `proof` can also contain an update of outbound lane state of source chain, which indicates + the delivery confirmation of these messages and reward payment, so that the target chain can + truncate its unpayed rewards vector. + + The target chain stores `relayer_ids` that delivered messages because the relayer can generate + a storage proof to show that they did indeed deliver those messages. The reward is paid on the + source chain and we inform the target chain about that fact so it can prune these `relayer_ids`. + + It's totally fine if there are no messages, and we only include the reward payment proof + when calling that function. + +1. 🥳 the message is now delivered and dispatched on the target chain! + +1. The relayer now needs to confirm the delivery to claim her payment and reward on the source + chain. + +1. The relayer creates a transaction on the source chain with call: + + ```rust + receive_messages_delivery_proof( + proof, // hash of the finalized target chain block, lane_id, storage proof + ) + ``` + +### UI challenges + +- The UI should warn before (or prevent) sending to `k(kCharlie)`! + + +Scenario 2: Kusama's Alice nominating validators with her DOTs +--------------------------- + +kAlice receives 10 DOTs from pBob and nominates `p(pCharlie)` and `p(pDave)`. + +1. Generate kAlice's DOT address (`p(kAlice)`) +2. [Polkadot] pBob transfers 5 DOTs to `p(kAlice)` +3. [Kusama] kAlice sends a batch transaction: + - `staking::Bond` transaction to create stash account choosing `p(kAlice)` as the controller account. + - `staking::Nominate(vec![p(pCharlie)])` to nominate pCharlie using the controller account. + + +Scenario 3: Kusama Treasury receiving & spending DOTs +--------------------------- + +pBob sends 15 DOTs to Kusama Treasury which Kusama Governance decides to transfer to kCharlie. + +1. Generate source account for the treasury (`kTreasury`). +2. [Polkadot] pBob tarnsfers 15 DOTs to `p(kTreasury)`. +2. [Kusama] Send a governance proposal to send a bridge message which transfers funds to `p(kCharlie)`. +3. [Kusama] Dispatch the governance proposal using `kTreasury` account id. + +Extra scenarios +=========================== + +Scenario 4: Kusama's Alice setting up 1-of-2 multi-sig to spend from either Kusama or Polkadot +--------------------------- + +Assuming `p(pAlice)` has at least 7 DOTs already. + +1. Generate multisig account id: `pMultiSig = multi_account_id(&[p(kAlice), p(pAlice)], 1)`. +2. [Kusama] Transfer 7 DOTs to `pMultiSig` using `TargetAccount` origin of `pAlice`. +3. [Kusama] Transfer 2 DOTs to `p(kAlice)` from the multisig: + - Send `multisig::as_multi_threshold_1(vec![p(pAlice)], balances::Transfer(p(kAlice), 2))` + +Scenario 5: Kusama Treasury staking & nominating validators with DOTs +--------------------------- + +Scenario 6: Kusama Treasury voting in Polkadot's democracy proposal +--------------------------- + +Potentially interesting scenarios +=========================== + +Scenario 7: Polkadot's Bob spending his DOTs by using Kusama chain +--------------------------- + +We can assume he holds KSM. Problem: he can pay fees, but can't really send (sign) a transaction? +Shall we support some kind of dispatcher? + +Scenario 8: Kusama Governance taking over Kusama's Alice DOT holdings +--------------------------- + +We use `SourceRoot` call to transfer her's DOTs to Kusama treasury. Source chain root +should also be able to send messages as `CallOrigin::SourceAccount(Alice)` though. diff --git a/fuzz/storage-proof/Cargo.lock b/fuzz/storage-proof/Cargo.lock new file mode 100644 index 00000000000..1c40a1a0d3b --- /dev/null +++ b/fuzz/storage-proof/Cargo.lock @@ -0,0 +1,2663 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.7", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" + +[[package]] +name = "arbitrary" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38b6b6b79f671c25e1a3e785b7b82d7562ffc9cd3efdc98627e5668a2472490" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bp-runtime" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "hash-db", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "winapi", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.4", + "rand_core 0.6.1", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ccfd8c0ee4cce11e45b3fd6f9d5e69e0cc62912aa6a0cb1bf4617b0eba5a12f" +dependencies = [ + "generic-array 0.14.4", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434e1720189a637d44fe464f4df1e6eb900b4835255b14354497c78af37d9bb8" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f627126b946c25a4638eec0ea634fc52506dea98db118aae985118ce7c3d723f" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "derive_more" +version = "0.99.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c66a534cbb46ab4ea03477eae19d5c22c01da8258030280b7bd9d8433fb6ef" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.0.2", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array 0.14.4", + "group", + "rand_core 0.6.1", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "environmental" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.1", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.2", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "frame-metadata" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "bitflags", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-core-hashing-proc-macro", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-tracing", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "Inflector", + "frame-support-procedural-tools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.1", + "subtle", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.4", + "hmac 0.8.1", +] + +[[package]] +name = "honggfuzz" +version = "0.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea09577d948a98a5f59b7c891e274c4fb35ad52f67782b3d0cb53b9c05301f1" +dependencies = [ + "arbitrary", + "lazy_static", + "memmap", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sec1", +] + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libsecp256k1" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.2", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "memory-db" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" +dependencies = [ + "hash-db", + "hashbrown", + "parity-util-mem", +] + +[[package]] +name = "memory_units" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" + +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-format" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +dependencies = [ + "arrayvec 0.4.12", + "itoa 0.4.7", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parity-scale-codec" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b44461635bbb1a0300f100a841e571e7d919c81c73075ef5d152ffdb521066" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parity-util-mem" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" +dependencies = [ + "cfg-if", + "hashbrown", + "impl-trait-for-tuples", + "parity-util-mem-derive", + "parking_lot", + "primitive-types", + "winapi", +] + +[[package]] +name = "parity-util-mem-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] + +[[package]] +name = "parity-wasm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac 0.8.0", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac 0.11.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18519b42a40024d661e1714153e9ad0c3de27cd495760ceb09710920f1098b1e" +dependencies = [ + "libc", + "rand_chacha 0.3.0", + "rand_core 0.6.1", + "rand_hc 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.1", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scale-info" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "schnorrkel" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "curve25519-dalek 2.1.2", + "getrandom 0.1.16", + "merlin", + "rand 0.7.3", + "rand_core 0.5.1", + "sha2 0.8.2", + "subtle", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array 0.14.4", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa 1.0.2", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.1", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "sp-application-crypto" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-debug-derive", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-core" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "base58", + "bitflags", + "blake2-rfc", + "byteorder", + "dyn-clonable", + "ed25519-dalek", + "futures", + "hash-db", + "hash256-std-hasher", + "hex", + "impl-serde", + "lazy_static", + "libsecp256k1", + "log", + "merlin", + "num-traits", + "parity-scale-codec", + "parity-util-mem", + "parking_lot", + "primitive-types", + "rand 0.7.3", + "regex", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "serde", + "sp-core-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tiny-bip39", + "wasmi", + "zeroize", +] + +[[package]] +name = "sp-core-hashing" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "blake2", + "byteorder", + "digest 0.10.3", + "sha2 0.10.2", + "sha3", + "sp-std", + "twox-hash", +] + +[[package]] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "proc-macro2", + "quote", + "sp-core-hashing", + "syn", +] + +[[package]] +name = "sp-debug-derive" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-externalities" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std", + "sp-storage", +] + +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", + "thiserror", +] + +[[package]] +name = "sp-io" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "futures", + "hash-db", + "libsecp256k1", + "log", + "parity-scale-codec", + "parking_lot", + "secp256k1", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-trie", + "sp-wasm-interface", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keystore" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "async-trait", + "futures", + "merlin", + "parity-scale-codec", + "parking_lot", + "schnorrkel", + "sp-core", + "sp-externalities", + "thiserror", +] + +[[package]] +name = "sp-panic-handler" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-runtime" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "rand 0.7.3", + "scale-info", + "serde", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-runtime-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-state-machine" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "hash-db", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot", + "rand 0.7.3", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-std", + "sp-trie", + "thiserror", + "tracing", + "trie-root", +] + +[[package]] +name = "sp-std" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" + +[[package]] +name = "sp-storage" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-tracing" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "parity-scale-codec", + "sp-std", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sp-trie" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "hash-db", + "memory-db", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-std", + "thiserror", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-core-hashing-proc-macro", + "sp-runtime", + "sp-std", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-wasm-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#367dab0d4bd7fd7b6c222dd15c753169c057dd42" +dependencies = [ + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std", + "wasmi", +] + +[[package]] +name = "ss58-registry" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ef98aedad3dc52e10995e7ed15f1279e11d4da35795f5dac7305742d0feb66" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "storage-proof-fuzzer" +version = "0.1.0" +dependencies = [ + "bp-runtime", + "env_logger", + "honggfuzz", + "log", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", +] + +[[package]] +name = "substrate-bip39" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" +dependencies = [ + "hmac 0.11.0", + "pbkdf2 0.8.0", + "schnorrkel", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "subtle" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "trie-db" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" +dependencies = [ + "hash-db", + "hashbrown", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" +dependencies = [ + "hash-db", +] + +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest 0.10.3", + "rand 0.8.2", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-normalization" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "wasmi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca00c5147c319a8ec91ec1a0edbec31e566ce2c9cc93b3f9bb86a9efd0eb795d" +dependencies = [ + "downcast-rs", + "libc", + "memory_units", + "num-rational", + "num-traits", + "parity-wasm", + "wasmi-validation", +] + +[[package]] +name = "wasmi-validation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" +dependencies = [ + "parity-wasm", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b578acffd8516a6c3f2a1bdefc1ec37e547bb4e0fb8b6b01a4cafc886b4442" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/fuzz/storage-proof/Cargo.toml b/fuzz/storage-proof/Cargo.toml new file mode 100644 index 00000000000..b406054bc6e --- /dev/null +++ b/fuzz/storage-proof/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "storage-proof-fuzzer" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +honggfuzz = "0.5.54" +log = "0.4.0" +env_logger = "0.8.3" + +# Bridge Dependencies + +bp-runtime = { path = "../../primitives/runtime" } + +# Substrate Dependencies + +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/fuzz/storage-proof/README.md b/fuzz/storage-proof/README.md new file mode 100644 index 00000000000..1eeec7562a9 --- /dev/null +++ b/fuzz/storage-proof/README.md @@ -0,0 +1,34 @@ +# Storage Proof Fuzzer + +## How to run? + +Install dependencies: +``` +$ sudo apt install build-essential binutils-dev libunwind-dev +``` +or on nix: +``` +$ nix-shell -p honggfuzz +``` + +Install `cargo hfuzz` plugin: +``` +$ cargo install honggfuzz +``` + +Run: +``` +$ cargo hfuzz run storage-proof-fuzzer +``` + +Use `HFUZZ_RUN_ARGS` to customize execution: +``` +# 1 second of timeout +# use 12 fuzzing thread +# be verbose +# stop after 1000000 fuzzing iteration +# exit upon crash +HFUZZ_RUN_ARGS="-t 1 -n 12 -v -N 1000000 --exit_upon_crash" cargo hfuzz run example +``` + +More details in the [official documentation](https://docs.rs/honggfuzz/0.5.52/honggfuzz/#about-honggfuzz). diff --git a/fuzz/storage-proof/src/main.rs b/fuzz/storage-proof/src/main.rs new file mode 100644 index 00000000000..26564372cb0 --- /dev/null +++ b/fuzz/storage-proof/src/main.rs @@ -0,0 +1,77 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Storage Proof Checker fuzzer. + +#![warn(missing_docs)] + +use honggfuzz::fuzz; +// Logic for checking Substrate storage proofs. + +use sp_core::{Blake2Hasher, H256}; +use sp_state_machine::{backend::Backend, prove_read, InMemoryBackend}; +use sp_std::vec::Vec; +use sp_trie::StorageProof; +use std::collections::HashMap; + +fn craft_known_storage_proof(input_vec: Vec<(Vec, Vec)>) -> (H256, StorageProof) { + let storage_proof_vec = + vec![(None, input_vec.iter().map(|x| (x.0.clone(), Some(x.1.clone()))).collect())]; + log::info!("Storage proof vec {:?}", storage_proof_vec); + let state_version = sp_runtime::StateVersion::default(); + let backend = >::from((storage_proof_vec, state_version)); + let root = backend.storage_root(std::iter::empty(), state_version).0; + let vector_element_proof = + prove_read(backend, input_vec.iter().map(|x| x.0.as_slice())).unwrap(); + (root, vector_element_proof) +} + +fn transform_into_unique(input_vec: Vec<(Vec, Vec)>) -> Vec<(Vec, Vec)> { + let mut output_hashmap = HashMap::new(); + let mut output_vec = Vec::new(); + for key_value_pair in input_vec { + output_hashmap.insert(key_value_pair.0, key_value_pair.1); //Only 1 value per key + } + for (key, val) in output_hashmap.iter() { + output_vec.push((key.clone(), val.clone())); + } + output_vec +} + +fn run_fuzzer() { + fuzz!(|input_vec: Vec<(Vec, Vec)>| { + if input_vec.is_empty() { + return + } + let unique_input_vec = transform_into_unique(input_vec); + let (root, craft_known_storage_proof) = craft_known_storage_proof(unique_input_vec.clone()); + let checker = + >::new(root, craft_known_storage_proof) + .expect("Valid proof passed; qed"); + for key_value_pair in unique_input_vec { + log::info!("Reading value for pair {:?}", key_value_pair); + assert_eq!(checker.read_value(&key_value_pair.0), Ok(Some(key_value_pair.1.clone()))); + } + }) +} + +fn main() { + env_logger::init(); + + loop { + run_fuzzer(); + } +} diff --git a/modules/beefy/Cargo.toml b/modules/beefy/Cargo.toml new file mode 100644 index 00000000000..25905126d53 --- /dev/null +++ b/modules/beefy/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "pallet-bridge-beefy" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.14", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0", optional = true } + +# Bridge Dependencies + +bp-beefy = { path = "../../primitives/beefy", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } + +# Substrate Dependencies + +beefy-merkle-tree = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +primitive-types = { version = "0.12.0", default-features = false, features = ["impl-codec"] } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master" } +mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.3.2" } +pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master" } +rand = "0.8" +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } +bp-test-utils = { path = "../../primitives/test-utils" } + +[features] +default = ["std"] +std = [ + "beefy-merkle-tree/std", + "bp-beefy/std", + "bp-runtime/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "primitive-types/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/modules/beefy/src/lib.rs b/modules/beefy/src/lib.rs new file mode 100644 index 00000000000..5ef58c5a3cb --- /dev/null +++ b/modules/beefy/src/lib.rs @@ -0,0 +1,644 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! BEEFY bridge pallet. +//! +//! This pallet is an on-chain BEEFY light client for Substrate-based chains that are using the +//! following pallets bundle: `pallet-mmr`, `pallet-beefy` and `pallet-beefy-mmr`. +//! +//! The pallet is able to verify MMR leaf proofs and BEEFY commitments, so it has access +//! to the following data of the bridged chain: +//! +//! - header hashes +//! - changes of BEEFY authorities +//! - extra data of MMR leafs +//! +//! Given the header hash, other pallets are able to verify header-based proofs +//! (e.g. storage proofs, transaction inclusion proofs, etc.). + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_beefy::{ChainWithBeefy, InitializationData}; +use sp_std::{boxed::Box, prelude::*}; + +// Re-export in crate namespace for `construct_runtime!` +pub use pallet::*; + +mod utils; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod mock_chain; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-beefy"; + +/// Configured bridged chain. +pub type BridgedChain = >::BridgedChain; +/// Block number, used by configured bridged chain. +pub type BridgedBlockNumber = bp_runtime::BlockNumberOf>; +/// Block hash, used by configured bridged chain. +pub type BridgedBlockHash = bp_runtime::HashOf>; + +/// Pallet initialization data. +pub type InitializationDataOf = + InitializationData, bp_beefy::MmrHashOf>>; +/// BEEFY commitment hasher, used by configured bridged chain. +pub type BridgedBeefyCommitmentHasher = bp_beefy::BeefyCommitmentHasher>; +/// BEEFY validator id, used by configured bridged chain. +pub type BridgedBeefyAuthorityId = bp_beefy::BeefyAuthorityIdOf>; +/// BEEFY validator set, used by configured bridged chain. +pub type BridgedBeefyAuthoritySet = bp_beefy::BeefyAuthoritySetOf>; +/// BEEFY authority set, used by configured bridged chain. +pub type BridgedBeefyAuthoritySetInfo = bp_beefy::BeefyAuthoritySetInfoOf>; +/// BEEFY signed commitment, used by configured bridged chain. +pub type BridgedBeefySignedCommitment = bp_beefy::BeefySignedCommitmentOf>; +/// MMR hashing algorithm, used by configured bridged chain. +pub type BridgedMmrHashing = bp_beefy::MmrHashingOf>; +/// MMR hashing output type of `BridgedMmrHashing`. +pub type BridgedMmrHash = bp_beefy::MmrHashOf>; +/// The type of the MMR leaf extra data used by the configured bridged chain. +pub type BridgedBeefyMmrLeafExtra = bp_beefy::BeefyMmrLeafExtraOf>; +/// BEEFY MMR proof type used by the pallet +pub type BridgedMmrProof = bp_beefy::MmrProofOf>; +/// MMR leaf type, used by configured bridged chain. +pub type BridgedBeefyMmrLeaf = bp_beefy::BeefyMmrLeafOf>; +/// Imported commitment data, stored by the pallet. +pub type ImportedCommitment = bp_beefy::ImportedCommitment< + BridgedBlockNumber, + BridgedBlockHash, + BridgedMmrHash, +>; + +/// Some high level info about the imported commitments. +#[derive(codec::Encode, codec::Decode, scale_info::TypeInfo)] +pub struct ImportedCommitmentsInfoData { + /// Best known block number, provided in a BEEFY commitment. However this is not + /// the best proven block. The best proven block is this block's parent. + best_block_number: BlockNumber, + /// The head of the `ImportedBlockNumbers` ring buffer. + next_block_number_index: u32, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use bp_runtime::{BasicOperatingMode, OwnedBridgeModule}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The upper bound on the number of requests allowed by the pallet. + /// + /// A request refers to an action which writes a header to storage. + /// + /// Once this bound is reached the pallet will reject all commitments + /// until the request count has decreased. + #[pallet::constant] + type MaxRequests: Get; + + /// Maximal number of imported commitments to keep in the storage. + /// + /// The setting is there to prevent growing the on-chain state indefinitely. Note + /// the setting does not relate to block numbers - we will simply keep as much items + /// in the storage, so it doesn't guarantee any fixed timeframe for imported commitments. + #[pallet::constant] + type CommitmentsToKeep: Get; + + /// The chain we are bridging to here. + type BridgedChain: ChainWithBeefy; + } + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn on_initialize(_n: T::BlockNumber) -> frame_support::weights::Weight { + >::mutate(|count| *count = count.saturating_sub(1)); + + Weight::from_ref_time(0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + } + + impl, I: 'static> OwnedBridgeModule for Pallet { + const LOG_TARGET: &'static str = LOG_TARGET; + type OwnerStorage = PalletOwner; + type OperatingMode = BasicOperatingMode; + type OperatingModeStorage = PalletOperatingMode; + } + + #[pallet::call] + impl, I: 'static> Pallet + where + BridgedMmrHashing: 'static + Send + Sync, + { + /// Initialize pallet with BEEFY authority set and best known finalized block number. + #[pallet::weight((T::DbWeight::get().reads_writes(2, 3), DispatchClass::Operational))] + pub fn initialize( + origin: OriginFor, + init_data: InitializationDataOf, + ) -> DispatchResult { + Self::ensure_owner_or_root(origin)?; + + let is_initialized = >::exists(); + ensure!(!is_initialized, >::AlreadyInitialized); + + log::info!(target: LOG_TARGET, "Initializing bridge BEEFY pallet: {:?}", init_data); + Ok(initialize::(init_data)?) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + + /// Submit a commitment generated by BEEFY authority set. + /// + /// It will use the underlying storage pallet to fetch information about the current + /// authority set and best finalized block number in order to verify that the commitment + /// is valid. + /// + /// If successful in verification, it will update the underlying storage with the data + /// provided in the newly submitted commitment. + #[pallet::weight(0)] + pub fn submit_commitment( + origin: OriginFor, + commitment: BridgedBeefySignedCommitment, + validator_set: BridgedBeefyAuthoritySet, + mmr_leaf: Box>, + mmr_proof: BridgedMmrProof, + ) -> DispatchResult + where + BridgedBeefySignedCommitment: Clone, + { + Self::ensure_not_halted().map_err(Error::::BridgeModule)?; + ensure_signed(origin)?; + + ensure!(Self::request_count() < T::MaxRequests::get(), >::TooManyRequests); + + // Ensure that the commitment is for a better block. + let commitments_info = + ImportedCommitmentsInfo::::get().ok_or(Error::::NotInitialized)?; + ensure!( + commitment.commitment.block_number > commitments_info.best_block_number, + Error::::OldCommitment + ); + + // Verify commitment and mmr leaf. + let current_authority_set_info = CurrentAuthoritySetInfo::::get(); + let mmr_root = utils::verify_commitment::( + &commitment, + ¤t_authority_set_info, + &validator_set, + )?; + utils::verify_beefy_mmr_leaf::(&mmr_leaf, mmr_proof, mmr_root)?; + + // Update request count. + RequestCount::::mutate(|count| *count += 1); + // Update authority set if needed. + if mmr_leaf.beefy_next_authority_set.id > current_authority_set_info.id { + CurrentAuthoritySetInfo::::put(mmr_leaf.beefy_next_authority_set); + } + + // Import commitment. + let block_number_index = commitments_info.next_block_number_index; + let to_prune = ImportedBlockNumbers::::try_get(block_number_index); + ImportedCommitments::::insert( + commitment.commitment.block_number, + ImportedCommitment:: { + parent_number_and_hash: mmr_leaf.parent_number_and_hash, + mmr_root, + }, + ); + ImportedBlockNumbers::::insert( + block_number_index, + commitment.commitment.block_number, + ); + ImportedCommitmentsInfo::::put(ImportedCommitmentsInfoData { + best_block_number: commitment.commitment.block_number, + next_block_number_index: (block_number_index + 1) % T::CommitmentsToKeep::get(), + }); + if let Ok(old_block_number) = to_prune { + log::debug!( + target: LOG_TARGET, + "Pruning commitment for old block: {:?}.", + old_block_number + ); + ImportedCommitments::::remove(old_block_number); + } + + log::info!( + target: LOG_TARGET, + "Successfully imported commitment for block {:?}", + commitment.commitment.block_number, + ); + + Ok(()) + } + } + + /// The current number of requests which have written to storage. + /// + /// If the `RequestCount` hits `MaxRequests`, no more calls will be allowed to the pallet until + /// the request capacity is increased. + /// + /// The `RequestCount` is decreased by one at the beginning of every block. This is to ensure + /// that the pallet can always make progress. + #[pallet::storage] + #[pallet::getter(fn request_count)] + pub type RequestCount, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + + /// High level info about the imported commitments. + /// + /// Contains the following info: + /// - best known block number of the bridged chain, finalized by BEEFY + /// - the head of the `ImportedBlockNumbers` ring buffer + #[pallet::storage] + pub type ImportedCommitmentsInfo, I: 'static = ()> = + StorageValue<_, ImportedCommitmentsInfoData>>; + + /// A ring buffer containing the block numbers of the commitments that we have imported, + /// ordered by the insertion time. + #[pallet::storage] + pub(super) type ImportedBlockNumbers, I: 'static = ()> = + StorageMap<_, Identity, u32, BridgedBlockNumber>; + + /// All the commitments that we have imported and haven't been pruned yet. + #[pallet::storage] + pub type ImportedCommitments, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, BridgedBlockNumber, ImportedCommitment>; + + /// The current BEEFY authority set at the bridged chain. + #[pallet::storage] + pub type CurrentAuthoritySetInfo, I: 'static = ()> = + StorageValue<_, BridgedBeefyAuthoritySetInfo, ValueQuery>; + + /// Optional pallet owner. + /// + /// Pallet owner has the right to halt all pallet operations and then resume it. If it is + /// `None`, then there are no direct ways to halt/resume pallet operations, but other + /// runtime methods may still be used to do that (i.e. `democracy::referendum` to update halt + /// flag directly or calling `halt_operations`). + #[pallet::storage] + pub type PalletOwner, I: 'static = ()> = + StorageValue<_, T::AccountId, OptionQuery>; + + /// The current operating mode of the pallet. + /// + /// Depending on the mode either all, or no transactions will be allowed. + #[pallet::storage] + pub type PalletOperatingMode, I: 'static = ()> = + StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + /// Optional module owner account. + pub owner: Option, + /// Optional module initialization data. + pub init_data: Option>, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { owner: None, init_data: None } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + if let Some(ref owner) = self.owner { + >::put(owner); + } + + if let Some(init_data) = self.init_data.clone() { + initialize::(init_data) + .expect("invalid initialization data of BEEFY bridge pallet"); + } else { + // Since the bridge hasn't been initialized we shouldn't allow anyone to perform + // transactions. + >::put(BasicOperatingMode::Halted); + } + } + } + + #[pallet::error] + pub enum Error { + /// The pallet has not been initialized yet. + NotInitialized, + /// The pallet has already been initialized. + AlreadyInitialized, + /// Invalid initial authority set. + InvalidInitialAuthoritySet, + /// There are too many requests for the current window to handle. + TooManyRequests, + /// The imported commitment is older than the best commitment known to the pallet. + OldCommitment, + /// The commitment is signed by unknown validator set. + InvalidCommitmentValidatorSetId, + /// The id of the provided validator set is invalid. + InvalidValidatorSetId, + /// The number of signatures in the commitment is invalid. + InvalidCommitmentSignaturesLen, + /// The number of validator ids provided is invalid. + InvalidValidatorSetLen, + /// There aren't enough correct signatures in the commitment to finalize the block. + NotEnoughCorrectSignatures, + /// MMR root is missing from the commitment. + MmrRootMissingFromCommitment, + /// MMR proof verification has failed. + MmrProofVerificationFailed, + /// The validators are not matching the merkle tree root of the authority set. + InvalidValidatorSetRoot, + /// Error generated by the `OwnedBridgeModule` trait. + BridgeModule(bp_runtime::OwnedBridgeModuleError), + } + + /// Initialize pallet with given parameters. + pub(super) fn initialize, I: 'static>( + init_data: InitializationDataOf, + ) -> Result<(), Error> { + if init_data.authority_set.len == 0 { + return Err(Error::::InvalidInitialAuthoritySet) + } + CurrentAuthoritySetInfo::::put(init_data.authority_set); + + >::put(init_data.operating_mode); + ImportedCommitmentsInfo::::put(ImportedCommitmentsInfoData { + best_block_number: init_data.best_block_number, + next_block_number_index: 0, + }); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use beefy_primitives::mmr::BeefyAuthoritySet; + use bp_runtime::{BasicOperatingMode, OwnedBridgeModuleError}; + use bp_test_utils::generate_owned_bridge_module_tests; + use frame_support::{assert_noop, assert_ok, traits::Get}; + use mock::*; + use mock_chain::*; + use sp_runtime::DispatchError; + + fn next_block() { + use frame_support::traits::OnInitialize; + + let current_number = frame_system::Pallet::::block_number(); + frame_system::Pallet::::set_block_number(current_number + 1); + let _ = Pallet::::on_initialize(current_number); + } + + fn import_header_chain(headers: Vec) { + for header in headers { + if header.commitment.is_some() { + assert_ok!(import_commitment(header)); + } + } + } + + #[test] + fn fails_to_initialize_if_already_initialized() { + run_test_with_initialize(32, || { + assert_noop!( + Pallet::::initialize( + RuntimeOrigin::root(), + InitializationData { + operating_mode: BasicOperatingMode::Normal, + best_block_number: 0, + authority_set: BeefyAuthoritySet { id: 0, len: 1, root: [0u8; 32].into() } + } + ), + Error::::AlreadyInitialized, + ); + }); + } + + #[test] + fn fails_to_initialize_if_authority_set_is_empty() { + run_test(|| { + assert_noop!( + Pallet::::initialize( + RuntimeOrigin::root(), + InitializationData { + operating_mode: BasicOperatingMode::Normal, + best_block_number: 0, + authority_set: BeefyAuthoritySet { id: 0, len: 0, root: [0u8; 32].into() } + } + ), + Error::::InvalidInitialAuthoritySet, + ); + }); + } + + #[test] + fn fails_to_import_commitment_if_halted() { + run_test_with_initialize(1, || { + assert_ok!(Pallet::::set_operating_mode( + RuntimeOrigin::root(), + BasicOperatingMode::Halted + )); + assert_noop!( + import_commitment(ChainBuilder::new(1).append_finalized_header().to_header()), + Error::::BridgeModule(OwnedBridgeModuleError::Halted), + ); + }) + } + + #[test] + fn fails_to_import_commitment_if_too_many_requests() { + run_test_with_initialize(1, || { + let max_requests = <::MaxRequests as Get>::get() as u64; + let mut chain = ChainBuilder::new(1); + for _ in 0..max_requests + 2 { + chain = chain.append_finalized_header(); + } + + // import `max_request` headers + for i in 0..max_requests { + assert_ok!(import_commitment(chain.header(i + 1))); + } + + // try to import next header: it fails because we are no longer accepting commitments + assert_noop!( + import_commitment(chain.header(max_requests + 1)), + Error::::TooManyRequests, + ); + + // when next block is "started", we allow import of next header + next_block(); + assert_ok!(import_commitment(chain.header(max_requests + 1))); + + // but we can't import two headers until next block and so on + assert_noop!( + import_commitment(chain.header(max_requests + 2)), + Error::::TooManyRequests, + ); + }) + } + + #[test] + fn fails_to_import_commitment_if_not_initialized() { + run_test(|| { + assert_noop!( + import_commitment(ChainBuilder::new(1).append_finalized_header().to_header()), + Error::::NotInitialized, + ); + }) + } + + #[test] + fn submit_commitment_works_with_long_chain_with_handoffs() { + run_test_with_initialize(3, || { + let chain = ChainBuilder::new(3) + .append_finalized_header() + .append_default_headers(16) // 2..17 + .append_finalized_header() // 18 + .append_default_headers(16) // 19..34 + .append_handoff_header(9) // 35 + .append_default_headers(8) // 36..43 + .append_finalized_header() // 44 + .append_default_headers(8) // 45..52 + .append_handoff_header(17) // 53 + .append_default_headers(4) // 54..57 + .append_finalized_header() // 58 + .append_default_headers(4); // 59..63 + import_header_chain(chain.to_chain()); + + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().best_block_number, + 58 + ); + assert_eq!(CurrentAuthoritySetInfo::::get().id, 2); + assert_eq!(CurrentAuthoritySetInfo::::get().len, 17); + + let imported_commitment = ImportedCommitments::::get(58).unwrap(); + assert_eq!( + imported_commitment, + bp_beefy::ImportedCommitment { + parent_number_and_hash: (57, chain.header(57).header.hash()), + mmr_root: chain.header(58).mmr_root, + }, + ); + }) + } + + #[test] + fn commitment_pruning_works() { + run_test_with_initialize(3, || { + let commitments_to_keep = >::CommitmentsToKeep::get(); + let commitments_to_import: Vec = ChainBuilder::new(3) + .append_finalized_headers(commitments_to_keep as usize + 2) + .to_chain(); + + // import exactly `CommitmentsToKeep` commitments + for index in 0..commitments_to_keep { + next_block(); + import_commitment(commitments_to_import[index as usize].clone()) + .expect("must succeed"); + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().next_block_number_index, + (index + 1) % commitments_to_keep + ); + } + + // ensure that all commitments are in the storage + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().best_block_number, + commitments_to_keep as TestBridgedBlockNumber + ); + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().next_block_number_index, + 0 + ); + for index in 0..commitments_to_keep { + assert!(ImportedCommitments::::get( + index as TestBridgedBlockNumber + 1 + ) + .is_some()); + assert_eq!( + ImportedBlockNumbers::::get(index), + Some(index + 1).map(Into::into) + ); + } + + // import next commitment + next_block(); + import_commitment(commitments_to_import[commitments_to_keep as usize].clone()) + .expect("must succeed"); + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().next_block_number_index, + 1 + ); + assert!(ImportedCommitments::::get( + commitments_to_keep as TestBridgedBlockNumber + 1 + ) + .is_some()); + assert_eq!( + ImportedBlockNumbers::::get(0), + Some(commitments_to_keep + 1).map(Into::into) + ); + // the side effect of the import is that the commitment#1 is pruned + assert!(ImportedCommitments::::get(1).is_none()); + + // import next commitment + next_block(); + import_commitment(commitments_to_import[commitments_to_keep as usize + 1].clone()) + .expect("must succeed"); + assert_eq!( + ImportedCommitmentsInfo::::get().unwrap().next_block_number_index, + 2 + ); + assert!(ImportedCommitments::::get( + commitments_to_keep as TestBridgedBlockNumber + 2 + ) + .is_some()); + assert_eq!( + ImportedBlockNumbers::::get(1), + Some(commitments_to_keep + 2).map(Into::into) + ); + // the side effect of the import is that the commitment#2 is pruned + assert!(ImportedCommitments::::get(1).is_none()); + assert!(ImportedCommitments::::get(2).is_none()); + }); + } + + generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted); +} diff --git a/modules/beefy/src/mock.rs b/modules/beefy/src/mock.rs new file mode 100644 index 00000000000..927c39ff9aa --- /dev/null +++ b/modules/beefy/src/mock.rs @@ -0,0 +1,226 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate as beefy; +use crate::{ + utils::get_authorities_mmr_root, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo, + BridgedBeefyCommitmentHasher, BridgedBeefyMmrLeafExtra, BridgedBeefySignedCommitment, + BridgedMmrHash, BridgedMmrHashing, BridgedMmrProof, +}; + +use bp_beefy::{BeefyValidatorSignatureOf, ChainWithBeefy, Commitment, MmrDataOrHash}; +use bp_runtime::{BasicOperatingMode, Chain}; +use codec::Encode; +use frame_support::{construct_runtime, parameter_types, weights::Weight}; +use sp_core::{sr25519::Signature, Pair}; +use sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, Hash, IdentityLookup}, + Perbill, +}; + +pub use beefy_primitives::crypto::{AuthorityId as BeefyId, Pair as BeefyPair}; +use sp_core::crypto::Wraps; +use sp_runtime::traits::Keccak256; + +pub type TestAccountId = u64; +pub type TestBridgedBlockNumber = u64; +pub type TestBridgedBlockHash = H256; +pub type TestBridgedHeader = Header; +pub type TestBridgedAuthoritySetInfo = BridgedBeefyAuthoritySetInfo; +pub type TestBridgedValidatorSet = BridgedBeefyAuthoritySet; +pub type TestBridgedCommitment = BridgedBeefySignedCommitment; +pub type TestBridgedValidatorSignature = BeefyValidatorSignatureOf; +pub type TestBridgedCommitmentHasher = BridgedBeefyCommitmentHasher; +pub type TestBridgedMmrHashing = BridgedMmrHashing; +pub type TestBridgedMmrHash = BridgedMmrHash; +pub type TestBridgedBeefyMmrLeafExtra = BridgedBeefyMmrLeafExtra; +pub type TestBridgedMmrProof = BridgedMmrProof; +pub type TestBridgedRawMmrLeaf = beefy_primitives::mmr::MmrLeaf< + TestBridgedBlockNumber, + TestBridgedBlockHash, + TestBridgedMmrHash, + TestBridgedBeefyMmrLeafExtra, +>; +pub type TestBridgedMmrNode = MmrDataOrHash; + +type TestBlock = frame_system::mocking::MockBlock; +type TestUncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +construct_runtime! { + pub enum TestRuntime where + Block = TestBlock, + NodeBlock = TestBlock, + UncheckedExtrinsic = TestUncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Beefy: beefy::{Pallet}, + } +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = TestAccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = (); + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type DbWeight = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl beefy::Config for TestRuntime { + type MaxRequests = frame_support::traits::ConstU32<16>; + type BridgedChain = TestBridgedChain; + type CommitmentsToKeep = frame_support::traits::ConstU32<16>; +} + +#[derive(Debug)] +pub struct TestBridgedChain; + +impl Chain for TestBridgedChain { + type BlockNumber = TestBridgedBlockNumber; + type Hash = H256; + type Hasher = BlakeTwo256; + type Header = ::Header; + + type AccountId = TestAccountId; + type Balance = u64; + type Index = u64; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + unreachable!() + } + fn max_extrinsic_weight() -> Weight { + unreachable!() + } +} + +impl ChainWithBeefy for TestBridgedChain { + type CommitmentHasher = Keccak256; + type MmrHashing = Keccak256; + type MmrHash = ::Output; + type BeefyMmrLeafExtra = (); + type AuthorityId = BeefyId; + type Signature = beefy_primitives::crypto::AuthoritySignature; + type AuthorityIdToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; +} + +/// Run test within test runtime. +pub fn run_test(test: impl FnOnce() -> T) -> T { + sp_io::TestExternalities::new(Default::default()).execute_with(test) +} + +/// Initialize pallet and run test. +pub fn run_test_with_initialize(initial_validators_count: u32, test: impl FnOnce() -> T) -> T { + run_test(|| { + let validators = validator_ids(0, initial_validators_count); + let authority_set = authority_set_info(0, &validators); + + crate::Pallet::::initialize( + RuntimeOrigin::root(), + bp_beefy::InitializationData { + operating_mode: BasicOperatingMode::Normal, + best_block_number: 0, + authority_set, + }, + ) + .expect("initialization data is correct"); + + test() + }) +} + +/// Import given commitment. +pub fn import_commitment( + header: crate::mock_chain::HeaderAndCommitment, +) -> sp_runtime::DispatchResult { + crate::Pallet::::submit_commitment( + RuntimeOrigin::signed(1), + header + .commitment + .expect("thou shall not call import_commitment on header without commitment"), + header.validator_set, + Box::new(header.leaf), + header.leaf_proof, + ) +} + +pub fn validator_pairs(index: u32, count: u32) -> Vec { + (index..index + count) + .map(|index| { + let mut seed = [1u8; 32]; + seed[0..8].copy_from_slice(&(index as u64).encode()); + BeefyPair::from_seed(&seed) + }) + .collect() +} + +/// Return identifiers of validators, starting at given index. +pub fn validator_ids(index: u32, count: u32) -> Vec { + validator_pairs(index, count).into_iter().map(|pair| pair.public()).collect() +} + +pub fn authority_set_info(id: u64, validators: &Vec) -> TestBridgedAuthoritySetInfo { + let merkle_root = get_authorities_mmr_root::(validators.iter()); + + TestBridgedAuthoritySetInfo { id, len: validators.len() as u32, root: merkle_root } +} + +/// Sign BEEFY commitment. +pub fn sign_commitment( + commitment: Commitment, + validator_pairs: &[BeefyPair], + signature_count: usize, +) -> TestBridgedCommitment { + let total_validators = validator_pairs.len(); + let random_validators = + rand::seq::index::sample(&mut rand::thread_rng(), total_validators, signature_count); + + let commitment_hash = TestBridgedCommitmentHasher::hash(&commitment.encode()); + let mut signatures = vec![None; total_validators]; + for validator_idx in random_validators.iter() { + let validator = &validator_pairs[validator_idx]; + signatures[validator_idx] = + Some(validator.as_inner_ref().sign_prehashed(commitment_hash.as_fixed_bytes()).into()); + } + + TestBridgedCommitment { commitment, signatures } +} diff --git a/modules/beefy/src/mock_chain.rs b/modules/beefy/src/mock_chain.rs new file mode 100644 index 00000000000..4896f9bf909 --- /dev/null +++ b/modules/beefy/src/mock_chain.rs @@ -0,0 +1,299 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Utilities to build bridged chain and BEEFY+MMR structures. + +use crate::{ + mock::{ + sign_commitment, validator_pairs, BeefyPair, TestBridgedBlockNumber, TestBridgedCommitment, + TestBridgedHeader, TestBridgedMmrHash, TestBridgedMmrHashing, TestBridgedMmrNode, + TestBridgedMmrProof, TestBridgedRawMmrLeaf, TestBridgedValidatorSet, + TestBridgedValidatorSignature, TestRuntime, + }, + utils::get_authorities_mmr_root, +}; + +use beefy_primitives::mmr::{BeefyNextAuthoritySet, MmrLeafVersion}; +use bp_beefy::{BeefyPayload, Commitment, ValidatorSetId, MMR_ROOT_PAYLOAD_ID}; +use codec::Encode; +use pallet_mmr::NodeIndex; +use rand::Rng; +use sp_core::Pair; +use sp_runtime::traits::{Hash, Header as HeaderT}; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub struct HeaderAndCommitment { + pub header: TestBridgedHeader, + pub commitment: Option, + pub validator_set: TestBridgedValidatorSet, + pub leaf: TestBridgedRawMmrLeaf, + pub leaf_proof: TestBridgedMmrProof, + pub mmr_root: TestBridgedMmrHash, +} + +impl HeaderAndCommitment { + pub fn customize_signatures( + &mut self, + f: impl FnOnce(&mut Vec>), + ) { + if let Some(commitment) = &mut self.commitment { + f(&mut commitment.signatures); + } + } + + pub fn customize_commitment( + &mut self, + f: impl FnOnce(&mut Commitment), + validator_pairs: &[BeefyPair], + signature_count: usize, + ) { + if let Some(mut commitment) = self.commitment.take() { + f(&mut commitment.commitment); + self.commitment = + Some(sign_commitment(commitment.commitment, validator_pairs, signature_count)); + } + } +} + +pub struct ChainBuilder { + headers: Vec, + validator_set_id: ValidatorSetId, + validator_keys: Vec, + mmr: mmr_lib::MMR, +} + +struct BridgedMmrStorage { + nodes: HashMap, +} + +impl mmr_lib::MMRStore for BridgedMmrStorage { + fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result> { + Ok(self.nodes.get(&pos).cloned()) + } + + fn append(&mut self, pos: NodeIndex, elems: Vec) -> mmr_lib::Result<()> { + for (i, elem) in elems.into_iter().enumerate() { + self.nodes.insert(pos + i as NodeIndex, elem); + } + Ok(()) + } +} + +impl ChainBuilder { + /// Creates new chain builder with given validator set size. + pub fn new(initial_validators_count: u32) -> Self { + ChainBuilder { + headers: Vec::new(), + validator_set_id: 0, + validator_keys: validator_pairs(0, initial_validators_count), + mmr: mmr_lib::MMR::new(0, BridgedMmrStorage { nodes: HashMap::new() }), + } + } + + /// Get header with given number. + pub fn header(&self, number: TestBridgedBlockNumber) -> HeaderAndCommitment { + self.headers[number as usize - 1].clone() + } + + /// Returns single built header. + pub fn to_header(&self) -> HeaderAndCommitment { + assert_eq!(self.headers.len(), 1); + self.headers[0].clone() + } + + /// Returns built chain. + pub fn to_chain(&self) -> Vec { + self.headers.clone() + } + + /// Appends header, that has been finalized by BEEFY (so it has a linked signed commitment). + pub fn append_finalized_header(self) -> Self { + let next_validator_set_id = self.validator_set_id; + let next_validator_keys = self.validator_keys.clone(); + HeaderBuilder::with_chain(self, next_validator_set_id, next_validator_keys).finalize() + } + + /// Append multiple finalized headers at once. + pub fn append_finalized_headers(mut self, count: usize) -> Self { + for _ in 0..count { + self = self.append_finalized_header(); + } + self + } + + /// Appends header, that enacts new validator set. + /// + /// Such headers are explicitly finalized by BEEFY. + pub fn append_handoff_header(self, next_validators_len: u32) -> Self { + let new_validator_set_id = self.validator_set_id + 1; + let new_validator_pairs = + validator_pairs(rand::thread_rng().gen::() % (u32::MAX / 2), next_validators_len); + + HeaderBuilder::with_chain(self, new_validator_set_id, new_validator_pairs).finalize() + } + + /// Append several default header without commitment. + pub fn append_default_headers(mut self, count: usize) -> Self { + for _ in 0..count { + let next_validator_set_id = self.validator_set_id; + let next_validator_keys = self.validator_keys.clone(); + self = + HeaderBuilder::with_chain(self, next_validator_set_id, next_validator_keys).build() + } + self + } +} + +/// Custom header builder. +pub struct HeaderBuilder { + chain: ChainBuilder, + header: TestBridgedHeader, + leaf: TestBridgedRawMmrLeaf, + leaf_proof: Option, + next_validator_set_id: ValidatorSetId, + next_validator_keys: Vec, +} + +impl HeaderBuilder { + fn with_chain( + chain: ChainBuilder, + next_validator_set_id: ValidatorSetId, + next_validator_keys: Vec, + ) -> Self { + // we're starting with header#1, since header#0 is always finalized + let header_number = chain.headers.len() as TestBridgedBlockNumber + 1; + let header = TestBridgedHeader::new( + header_number, + Default::default(), + Default::default(), + chain.headers.last().map(|h| h.header.hash()).unwrap_or_default(), + Default::default(), + ); + + let next_validators = + next_validator_keys.iter().map(|pair| pair.public()).collect::>(); + let next_validators_mmr_root = + get_authorities_mmr_root::(next_validators.iter()); + let leaf = beefy_primitives::mmr::MmrLeaf { + version: MmrLeafVersion::new(1, 0), + parent_number_and_hash: (header.number().saturating_sub(1), *header.parent_hash()), + beefy_next_authority_set: BeefyNextAuthoritySet { + id: next_validator_set_id, + len: next_validators.len() as u32, + root: next_validators_mmr_root, + }, + leaf_extra: (), + }; + + HeaderBuilder { + chain, + header, + leaf, + leaf_proof: None, + next_validator_keys, + next_validator_set_id, + } + } + + /// Customize generated proof of header MMR leaf. + /// + /// Can only be called once. + pub fn customize_proof( + mut self, + f: impl FnOnce(TestBridgedMmrProof) -> TestBridgedMmrProof, + ) -> Self { + assert!(self.leaf_proof.is_none()); + + let leaf_hash = TestBridgedMmrHashing::hash(&self.leaf.encode()); + let node = TestBridgedMmrNode::Hash(leaf_hash); + let leaf_position = self.chain.mmr.push(node).unwrap(); + + let proof = self.chain.mmr.gen_proof(vec![leaf_position]).unwrap(); + // genesis has no leaf => leaf index is header number minus 1 + let leaf_index = *self.header.number() - 1; + let leaf_count = *self.header.number(); + self.leaf_proof = Some(f(TestBridgedMmrProof { + leaf_index, + leaf_count, + items: proof.proof_items().iter().map(|i| i.hash()).collect(), + })); + + self + } + + /// Build header without commitment. + pub fn build(mut self) -> ChainBuilder { + if self.leaf_proof.is_none() { + self = self.customize_proof(|proof| proof); + } + + let validators = + self.chain.validator_keys.iter().map(|pair| pair.public()).collect::>(); + self.chain.headers.push(HeaderAndCommitment { + header: self.header, + commitment: None, + validator_set: TestBridgedValidatorSet::new(validators, self.chain.validator_set_id) + .unwrap(), + leaf: self.leaf, + leaf_proof: self.leaf_proof.expect("guaranteed by the customize_proof call above; qed"), + mmr_root: self.chain.mmr.get_root().unwrap().hash(), + }); + + self.chain.validator_set_id = self.next_validator_set_id; + self.chain.validator_keys = self.next_validator_keys; + + self.chain + } + + /// Build header with commitment. + pub fn finalize(self) -> ChainBuilder { + let validator_count = self.chain.validator_keys.len(); + let current_validator_set_id = self.chain.validator_set_id; + let current_validator_set_keys = self.chain.validator_keys.clone(); + let mut chain = self.build(); + + let last_header = chain.headers.last_mut().expect("added by append_header; qed"); + last_header.commitment = Some(sign_commitment( + Commitment { + payload: BeefyPayload::from_single_entry( + MMR_ROOT_PAYLOAD_ID, + chain.mmr.get_root().unwrap().hash().encode(), + ), + block_number: *last_header.header.number(), + validator_set_id: current_validator_set_id, + }, + ¤t_validator_set_keys, + validator_count * 2 / 3 + 1, + )); + + chain + } +} + +/// Default Merging & Hashing behavior for MMR. +pub struct BridgedMmrHashMerge; + +impl mmr_lib::Merge for BridgedMmrHashMerge { + type Item = TestBridgedMmrNode; + + fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item { + let mut concat = left.hash().as_ref().to_vec(); + concat.extend_from_slice(right.hash().as_ref()); + + TestBridgedMmrNode::Hash(TestBridgedMmrHashing::hash(&concat)) + } +} diff --git a/modules/beefy/src/utils.rs b/modules/beefy/src/utils.rs new file mode 100644 index 00000000000..c8a7d2cee14 --- /dev/null +++ b/modules/beefy/src/utils.rs @@ -0,0 +1,363 @@ +use crate::{ + BridgedBeefyAuthorityId, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo, + BridgedBeefyCommitmentHasher, BridgedBeefyMmrLeaf, BridgedBeefySignedCommitment, BridgedChain, + BridgedMmrHash, BridgedMmrHashing, BridgedMmrProof, Config, Error, LOG_TARGET, +}; +use bp_beefy::{merkle_root, verify_mmr_leaves_proof, BeefyVerify, MmrDataOrHash, MmrProof}; +use codec::Encode; +use frame_support::ensure; +use sp_runtime::traits::{Convert, Hash}; +use sp_std::{vec, vec::Vec}; + +type BridgedMmrDataOrHash = MmrDataOrHash, BridgedBeefyMmrLeaf>; +/// A way to encode validator id to the BEEFY merkle tree leaf. +type BridgedBeefyAuthorityIdToMerkleLeaf = + bp_beefy::BeefyAuthorityIdToMerkleLeafOf>; + +/// Get the MMR root for a collection of validators. +pub(crate) fn get_authorities_mmr_root< + 'a, + T: Config, + I: 'static, + V: Iterator>, +>( + authorities: V, +) -> BridgedMmrHash { + let merkle_leafs = authorities + .cloned() + .map(BridgedBeefyAuthorityIdToMerkleLeaf::::convert) + .collect::>(); + merkle_root::, _>(merkle_leafs) +} + +fn verify_authority_set, I: 'static>( + authority_set_info: &BridgedBeefyAuthoritySetInfo, + authority_set: &BridgedBeefyAuthoritySet, +) -> Result<(), Error> { + ensure!(authority_set.id() == authority_set_info.id, Error::::InvalidValidatorSetId); + ensure!( + authority_set.len() == authority_set_info.len as usize, + Error::::InvalidValidatorSetLen + ); + + // Ensure that the authority set that signed the commitment is the expected one. + let root = get_authorities_mmr_root::(authority_set.validators().iter()); + ensure!(root == authority_set_info.root, Error::::InvalidValidatorSetRoot); + + Ok(()) +} + +/// Number of correct signatures, required from given validators set to accept signed +/// commitment. +/// +/// We're using 'conservative' approach here, where signatures of `2/3+1` validators are +/// required.. +pub(crate) fn signatures_required, I: 'static>(validators_len: usize) -> usize { + validators_len - validators_len.saturating_sub(1) / 3 +} + +fn verify_signatures, I: 'static>( + commitment: &BridgedBeefySignedCommitment, + authority_set: &BridgedBeefyAuthoritySet, +) -> Result<(), Error> { + ensure!( + commitment.signatures.len() == authority_set.len(), + Error::::InvalidCommitmentSignaturesLen + ); + + // Ensure that the commitment was signed by enough authorities. + let msg = commitment.commitment.encode(); + let mut missing_signatures = signatures_required::(authority_set.len()); + for (idx, (authority, maybe_sig)) in + authority_set.validators().iter().zip(commitment.signatures.iter()).enumerate() + { + if let Some(sig) = maybe_sig { + if BeefyVerify::>::verify(sig, &msg, authority) { + missing_signatures = missing_signatures.saturating_sub(1); + if missing_signatures == 0 { + break + } + } else { + log::debug!( + target: LOG_TARGET, + "Signed commitment contains incorrect signature of validator {} ({:?}): {:?}", + idx, + authority, + sig, + ); + } + } + } + ensure!(missing_signatures == 0, Error::::NotEnoughCorrectSignatures); + + Ok(()) +} + +/// Extract MMR root from commitment payload. +fn extract_mmr_root, I: 'static>( + commitment: &BridgedBeefySignedCommitment, +) -> Result, Error> { + commitment + .commitment + .payload + .get_decoded(&bp_beefy::MMR_ROOT_PAYLOAD_ID) + .ok_or(Error::MmrRootMissingFromCommitment) +} + +pub(crate) fn verify_commitment, I: 'static>( + commitment: &BridgedBeefySignedCommitment, + authority_set_info: &BridgedBeefyAuthoritySetInfo, + authority_set: &BridgedBeefyAuthoritySet, +) -> Result, Error> { + // Ensure that the commitment is signed by the best known BEEFY validator set. + ensure!( + commitment.commitment.validator_set_id == authority_set_info.id, + Error::::InvalidCommitmentValidatorSetId + ); + ensure!( + commitment.signatures.len() == authority_set_info.len as usize, + Error::::InvalidCommitmentSignaturesLen + ); + + verify_authority_set(authority_set_info, authority_set)?; + verify_signatures(commitment, authority_set)?; + + extract_mmr_root(commitment) +} + +/// Verify MMR proof of given leaf. +pub(crate) fn verify_beefy_mmr_leaf, I: 'static>( + mmr_leaf: &BridgedBeefyMmrLeaf, + mmr_proof: BridgedMmrProof, + mmr_root: BridgedMmrHash, +) -> Result<(), Error> { + let mmr_proof_leaf_index = mmr_proof.leaf_index; + let mmr_proof_leaf_count = mmr_proof.leaf_count; + let mmr_proof_length = mmr_proof.items.len(); + + // Verify the mmr proof for the provided leaf. + let mmr_leaf_hash = BridgedMmrHashing::::hash(&mmr_leaf.encode()); + verify_mmr_leaves_proof( + mmr_root, + vec![BridgedMmrDataOrHash::::Hash(mmr_leaf_hash)], + MmrProof::into_batch_proof(mmr_proof), + ) + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "MMR proof of leaf {:?} (root: {:?}, leaf: {}, leaf count: {}, len: {}) \ + verification has failed with error: {:?}", + mmr_leaf_hash, + mmr_root, + mmr_proof_leaf_index, + mmr_proof_leaf_count, + mmr_proof_length, + e, + ); + + Error::::MmrProofVerificationFailed + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{mock::*, mock_chain::*, *}; + use beefy_primitives::ValidatorSet; + use bp_beefy::{BeefyPayload, MMR_ROOT_PAYLOAD_ID}; + use frame_support::{assert_noop, assert_ok}; + + #[test] + fn submit_commitment_checks_metadata() { + run_test_with_initialize(8, || { + // Fails if `commitment.commitment.validator_set_id` differs. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.customize_commitment( + |commitment| { + commitment.validator_set_id += 1; + }, + &validator_pairs(0, 8), + 6, + ); + assert_noop!( + import_commitment(header), + Error::::InvalidCommitmentValidatorSetId, + ); + + // Fails if `commitment.signatures.len()` differs. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.customize_signatures(|signatures| { + signatures.pop(); + }); + assert_noop!( + import_commitment(header), + Error::::InvalidCommitmentSignaturesLen, + ); + }); + } + + #[test] + fn submit_commitment_checks_validator_set() { + run_test_with_initialize(8, || { + // Fails if `ValidatorSet::id` differs. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.validator_set = ValidatorSet::new(validator_ids(0, 8), 1).unwrap(); + assert_noop!( + import_commitment(header), + Error::::InvalidValidatorSetId, + ); + + // Fails if `ValidatorSet::len()` differs. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.validator_set = ValidatorSet::new(validator_ids(0, 5), 0).unwrap(); + assert_noop!( + import_commitment(header), + Error::::InvalidValidatorSetLen, + ); + + // Fails if the validators differ. + let mut header = ChainBuilder::new(8).append_finalized_header().to_header(); + header.validator_set = ValidatorSet::new(validator_ids(3, 8), 0).unwrap(); + assert_noop!( + import_commitment(header), + Error::::InvalidValidatorSetRoot, + ); + }); + } + + #[test] + fn submit_commitment_checks_signatures() { + run_test_with_initialize(20, || { + // Fails when there aren't enough signatures. + let mut header = ChainBuilder::new(20).append_finalized_header().to_header(); + header.customize_signatures(|signatures| { + let first_signature_idx = signatures.iter().position(Option::is_some).unwrap(); + signatures[first_signature_idx] = None; + }); + assert_noop!( + import_commitment(header), + Error::::NotEnoughCorrectSignatures, + ); + + // Fails when there aren't enough correct signatures. + let mut header = ChainBuilder::new(20).append_finalized_header().to_header(); + header.customize_signatures(|signatures| { + let first_signature_idx = signatures.iter().position(Option::is_some).unwrap(); + let last_signature_idx = signatures.len() - + signatures.iter().rev().position(Option::is_some).unwrap() - + 1; + signatures[first_signature_idx] = signatures[last_signature_idx].clone(); + }); + assert_noop!( + import_commitment(header), + Error::::NotEnoughCorrectSignatures, + ); + + // Returns Ok(()) when there are enough signatures, even if some are incorrect. + let mut header = ChainBuilder::new(20).append_finalized_header().to_header(); + header.customize_signatures(|signatures| { + let first_signature_idx = signatures.iter().position(Option::is_some).unwrap(); + let first_missing_signature_idx = + signatures.iter().position(Option::is_none).unwrap(); + signatures[first_missing_signature_idx] = signatures[first_signature_idx].clone(); + }); + assert_ok!(import_commitment(header)); + }); + } + + #[test] + fn submit_commitment_checks_mmr_proof() { + run_test_with_initialize(1, || { + let validators = validator_pairs(0, 1); + + // Fails if leaf is not for parent. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + header.leaf.parent_number_and_hash.0 += 1; + assert_noop!( + import_commitment(header), + Error::::MmrProofVerificationFailed, + ); + + // Fails if mmr proof is incorrect. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + header.leaf_proof.leaf_index += 1; + assert_noop!( + import_commitment(header), + Error::::MmrProofVerificationFailed, + ); + + // Fails if mmr root is incorrect. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + // Replace MMR root with zeroes. + header.customize_commitment( + |commitment| { + commitment.payload = + BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, [0u8; 32].encode()); + }, + &validators, + 1, + ); + assert_noop!( + import_commitment(header), + Error::::MmrProofVerificationFailed, + ); + }); + } + + #[test] + fn submit_commitment_extracts_mmr_root() { + run_test_with_initialize(1, || { + let validators = validator_pairs(0, 1); + + // Fails if there is no mmr root in the payload. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + // Remove MMR root from the payload. + header.customize_commitment( + |commitment| { + commitment.payload = BeefyPayload::from_single_entry(*b"xy", vec![]); + }, + &validators, + 1, + ); + assert_noop!( + import_commitment(header), + Error::::MmrRootMissingFromCommitment, + ); + + // Fails if mmr root can't be decoded. + let mut header = ChainBuilder::new(1).append_finalized_header().to_header(); + // MMR root is a 32-byte array and we have replaced it with single byte + header.customize_commitment( + |commitment| { + commitment.payload = + BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, vec![42]); + }, + &validators, + 1, + ); + assert_noop!( + import_commitment(header), + Error::::MmrRootMissingFromCommitment, + ); + }); + } + + #[test] + fn submit_commitment_stores_valid_data() { + run_test_with_initialize(20, || { + let header = ChainBuilder::new(20).append_handoff_header(30).to_header(); + assert_ok!(import_commitment(header.clone())); + + assert_eq!(ImportedCommitmentsInfo::::get().unwrap().best_block_number, 1); + assert_eq!(CurrentAuthoritySetInfo::::get().id, 1); + assert_eq!(CurrentAuthoritySetInfo::::get().len, 30); + assert_eq!( + ImportedCommitments::::get(1).unwrap(), + bp_beefy::ImportedCommitment { + parent_number_and_hash: (0, [0; 32].into()), + mmr_root: header.mmr_root, + }, + ); + }); + } +} diff --git a/modules/grandpa/Cargo.toml b/modules/grandpa/Cargo.toml new file mode 100644 index 00000000000..45f314a0bbd --- /dev/null +++ b/modules/grandpa/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "pallet-bridge-grandpa" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +finality-grandpa = { version = "0.16.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Bridge Dependencies + +bp-runtime = { path = "../../primitives/runtime", default-features = false } +bp-header-chain = { path = "../../primitives/header-chain", default-features = false } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Optional Benchmarking Dependencies +bp-test-utils = { path = "../../primitives/test-utils", default-features = false, optional = true } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } + +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = ["std"] +std = [ + "bp-header-chain/std", + "bp-runtime/std", + "bp-test-utils/std", + "codec/std", + "finality-grandpa/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "log/std", + "scale-info/std", + "sp-finality-grandpa/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", +] +runtime-benchmarks = [ + "bp-test-utils", + "frame-benchmarking/runtime-benchmarks", +] diff --git a/modules/grandpa/src/benchmarking.rs b/modules/grandpa/src/benchmarking.rs new file mode 100644 index 00000000000..e937f7a0bf4 --- /dev/null +++ b/modules/grandpa/src/benchmarking.rs @@ -0,0 +1,132 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Benchmarks for the GRANDPA Pallet. +//! +//! The main dispatchable for the GRANDPA pallet is `submit_finality_proof`, so these benchmarks are +//! based around that. There are to main factors which affect finality proof verification: +//! +//! 1. The number of `votes-ancestries` in the justification +//! 2. The number of `pre-commits` in the justification +//! +//! Vote ancestries are the headers between (`finality_target`, `head_of_chain`], where +//! `header_of_chain` is a descendant of `finality_target`. +//! +//! Pre-commits are messages which are signed by validators at the head of the chain they think is +//! the best. +//! +//! Consider the following: +//! +//! / [B'] <- [C'] +//! [A] <- [B] <- [C] +//! +//! The common ancestor of both forks is block A, so this is what GRANDPA will finalize. In order to +//! verify this we will have vote ancestries of `[B, C, B', C']` and pre-commits `[C, C']`. +//! +//! Note that the worst case scenario here would be a justification where each validator has it's +//! own fork which is `SESSION_LENGTH` blocks long. + +use crate::*; + +use bp_runtime::BasicOperatingMode; +use bp_test_utils::{ + accounts, make_justification_for_header, JustificationGeneratorParams, TEST_GRANDPA_ROUND, + TEST_GRANDPA_SET_ID, +}; +use frame_benchmarking::{benchmarks_instance_pallet, whitelisted_caller}; +use frame_support::traits::Get; +use frame_system::RawOrigin; +use sp_finality_grandpa::AuthorityId; +use sp_runtime::traits::Zero; +use sp_std::vec::Vec; + +// The maximum number of vote ancestries to include in a justification. +// +// In practice this would be limited by the session length (number of blocks a single authority set +// can produce) of a given chain. +const MAX_VOTE_ANCESTRIES: u32 = 1000; + +// The maximum number of pre-commits to include in a justification. In practice this scales with the +// number of validators. +pub const MAX_VALIDATOR_SET_SIZE: u32 = 1024; + +// `1..MAX_VALIDATOR_SET_SIZE` and `1..MAX_VOTE_ANCESTRIES` are too large && benchmarks are +// running for almost 40m (steps=50, repeat=20) on a decent laptop, which is too much. Since +// we're building linear function here, let's just select some limited subrange for benchmarking. +const VALIDATOR_SET_SIZE_RANGE_BEGIN: u32 = MAX_VALIDATOR_SET_SIZE / 20; +const VALIDATOR_SET_SIZE_RANGE_END: u32 = + VALIDATOR_SET_SIZE_RANGE_BEGIN + VALIDATOR_SET_SIZE_RANGE_BEGIN; +const MAX_VOTE_ANCESTRIES_RANGE_BEGIN: u32 = MAX_VOTE_ANCESTRIES / 20; +const MAX_VOTE_ANCESTRIES_RANGE_END: u32 = + MAX_VOTE_ANCESTRIES_RANGE_BEGIN + MAX_VOTE_ANCESTRIES_RANGE_BEGIN; + +/// Returns number of first header to be imported. +/// +/// Since we bootstrap the pallet with `HeadersToKeep` already imported headers, +/// this function computes the next expected header number to import. +fn header_number, I: 'static, N: From>() -> N { + (T::HeadersToKeep::get() + 1).into() +} + +/// Prepare header and its justification to submit using `submit_finality_proof`. +fn prepare_benchmark_data, I: 'static>( + precommits: u32, + ancestors: u32, +) -> (BridgedHeader, GrandpaJustification>) { + let authority_list = accounts(precommits as u16) + .iter() + .map(|id| (AuthorityId::from(*id), 1)) + .collect::>(); + + let init_data = InitializationData { + header: Box::new(bp_test_utils::test_header(Zero::zero())), + authority_list, + set_id: TEST_GRANDPA_SET_ID, + operating_mode: BasicOperatingMode::Normal, + }; + + bootstrap_bridge::(init_data); + + let header: BridgedHeader = bp_test_utils::test_header(header_number::()); + let params = JustificationGeneratorParams { + header: header.clone(), + round: TEST_GRANDPA_ROUND, + set_id: TEST_GRANDPA_SET_ID, + authorities: accounts(precommits as u16).iter().map(|k| (*k, 1)).collect::>(), + ancestors, + forks: 1, + }; + let justification = make_justification_for_header(params); + (header, justification) +} + +benchmarks_instance_pallet! { + // This is the "gold standard" benchmark for this extrinsic, and it's what should be used to + // annotate the weight in the pallet. + submit_finality_proof { + let p in VALIDATOR_SET_SIZE_RANGE_BEGIN..VALIDATOR_SET_SIZE_RANGE_END; + let v in MAX_VOTE_ANCESTRIES_RANGE_BEGIN..MAX_VOTE_ANCESTRIES_RANGE_END; + let caller: T::AccountId = whitelisted_caller(); + let (header, justification) = prepare_benchmark_data::(p, v); + }: submit_finality_proof(RawOrigin::Signed(caller), Box::new(header), justification) + verify { + let header: BridgedHeader = bp_test_utils::test_header(header_number::()); + let expected_hash = header.hash(); + + assert_eq!(>::get().unwrap().1, expected_hash); + assert!(>::contains_key(expected_hash)); + } +} diff --git a/modules/grandpa/src/extension.rs b/modules/grandpa/src/extension.rs new file mode 100644 index 00000000000..c0f02da751e --- /dev/null +++ b/modules/grandpa/src/extension.rs @@ -0,0 +1,116 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{Config, Pallet}; +use bp_runtime::FilterCall; +use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; +use sp_runtime::{ + traits::Header, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, +}; + +/// Validate Grandpa headers in order to avoid "mining" transactions that provide outdated +/// bridged chain headers. Without this validation, even honest relayers may lose their funds +/// if there are multiple relays running and submitting the same information. +impl< + Call: IsSubType, T>>, + T: frame_system::Config + Config, + I: 'static, + > FilterCall for Pallet +{ + fn validate(call: &::RuntimeCall) -> TransactionValidity { + let bundled_block_number = match call.is_sub_type() { + Some(crate::Call::::submit_finality_proof { ref finality_target, .. }) => + *finality_target.number(), + _ => return Ok(ValidTransaction::default()), + }; + + let best_finalized = crate::BestFinalized::::get(); + let best_finalized_number = match best_finalized { + Some((best_finalized_number, _)) => best_finalized_number, + None => return InvalidTransaction::Call.into(), + }; + + if best_finalized_number >= bundled_block_number { + log::trace!( + target: crate::LOG_TARGET, + "Rejecting obsolete bridged header: bundled {:?}, best {:?}", + bundled_block_number, + best_finalized_number, + ); + + return InvalidTransaction::Stale.into() + } + + Ok(ValidTransaction::default()) + } +} + +#[cfg(test)] +mod tests { + use super::FilterCall; + use crate::{ + mock::{run_test, test_header, RuntimeCall, TestNumber, TestRuntime}, + BestFinalized, + }; + use bp_test_utils::make_default_justification; + + fn validate_block_submit(num: TestNumber) -> bool { + crate::Pallet::::validate(&RuntimeCall::Grandpa(crate::Call::< + TestRuntime, + (), + >::submit_finality_proof { + finality_target: Box::new(test_header(num)), + justification: make_default_justification(&test_header(num)), + })) + .is_ok() + } + + fn sync_to_header_10() { + let header10_hash = sp_core::H256::default(); + BestFinalized::::put((10, header10_hash)); + } + + #[test] + fn extension_rejects_obsolete_header() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#5 => tx is + // rejected + sync_to_header_10(); + assert!(!validate_block_submit(5)); + }); + } + + #[test] + fn extension_rejects_same_header() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#10 => tx is + // rejected + sync_to_header_10(); + assert!(!validate_block_submit(10)); + }); + } + + #[test] + fn extension_accepts_new_header() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#15 => tx is + // accepted + sync_to_header_10(); + assert!(validate_block_submit(15)); + }); + } +} diff --git a/modules/grandpa/src/lib.rs b/modules/grandpa/src/lib.rs new file mode 100644 index 00000000000..2402b2512e2 --- /dev/null +++ b/modules/grandpa/src/lib.rs @@ -0,0 +1,1258 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate GRANDPA Pallet +//! +//! This pallet is an on-chain GRANDPA light client for Substrate based chains. +//! +//! This pallet achieves this by trustlessly verifying GRANDPA finality proofs on-chain. Once +//! verified, finalized headers are stored in the pallet, thereby creating a sparse header chain. +//! This sparse header chain can be used as a source of truth for other higher-level applications. +//! +//! The pallet is responsible for tracking GRANDPA validator set hand-offs. We only import headers +//! with justifications signed by the current validator set we know of. The header is inspected for +//! a `ScheduledChanges` digest item, which is then used to update to next validator set. +//! +//! Since this pallet only tracks finalized headers it does not deal with forks. Forks can only +//! occur if the GRANDPA validator set on the bridged chain is either colluding or there is a severe +//! bug causing resulting in an equivocation. Such events are outside the scope of this pallet. +//! Shall the fork occur on the bridged chain governance intervention will be required to +//! re-initialize the bridge and track the right fork. + +#![cfg_attr(not(feature = "std"), no_std)] +// Runtime-generated enums +#![allow(clippy::large_enum_variant)] + +use storage_types::StoredAuthoritySet; + +use bp_header_chain::{justification::GrandpaJustification, HeaderChain, InitializationData}; +use bp_runtime::{ + BlockNumberOf, BoundedStorageValue, Chain, HashOf, HasherOf, HeaderOf, OwnedBridgeModule, +}; +use finality_grandpa::voter_set::VoterSet; +use frame_support::{ensure, fail}; +use frame_system::ensure_signed; +use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID}; +use sp_runtime::traits::{Header as HeaderT, Zero}; +use sp_std::{boxed::Box, convert::TryInto}; + +mod extension; +#[cfg(test)] +mod mock; +mod storage_types; + +/// Module, containing weights for this pallet. +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +// Re-export in crate namespace for `construct_runtime!` +pub use pallet::*; +pub use weights::WeightInfo; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-grandpa"; + +/// Bridged chain from the pallet configuration. +pub type BridgedChain = >::BridgedChain; +/// Block number of the bridged chain. +pub type BridgedBlockNumber = BlockNumberOf<>::BridgedChain>; +/// Block hash of the bridged chain. +pub type BridgedBlockHash = HashOf<>::BridgedChain>; +/// Hasher of the bridged chain. +pub type BridgedBlockHasher = HasherOf<>::BridgedChain>; +/// Header of the bridged chain. +pub type BridgedHeader = HeaderOf<>::BridgedChain>; +/// Stored header of the bridged chain. +pub type StoredBridgedHeader = + BoundedStorageValue<>::MaxBridgedHeaderSize, BridgedHeader>; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use bp_runtime::BasicOperatingMode; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The chain we are bridging to here. + type BridgedChain: Chain; + + /// The upper bound on the number of requests allowed by the pallet. + /// + /// A request refers to an action which writes a header to storage. + /// + /// Once this bound is reached the pallet will not allow any dispatchables to be called + /// until the request count has decreased. + #[pallet::constant] + type MaxRequests: Get; + + /// Maximal number of finalized headers to keep in the storage. + /// + /// The setting is there to prevent growing the on-chain state indefinitely. Note + /// the setting does not relate to block numbers - we will simply keep as much items + /// in the storage, so it doesn't guarantee any fixed timeframe for finality headers. + /// + /// Incautious change of this constant may lead to orphan entries in the runtime storage. + #[pallet::constant] + type HeadersToKeep: Get; + + /// Max number of authorities at the bridged chain. + #[pallet::constant] + type MaxBridgedAuthorities: Get; + /// Maximal size (in bytes) of the SCALE-encoded bridged chain header. + /// + /// This constant must be selected with care. The pallet requires mandatory headers to be + /// submitted to be able to proceed. Mandatory headers contain public keys of all GRANDPA + /// authorities. E.g. for 1024 authorities, the size of encoded keys will be at least 32 KB. + /// The same header may also contain other digest items as well, so some reserve here + /// is required. + #[pallet::constant] + type MaxBridgedHeaderSize: Get; + + /// Weights gathered through benchmarking. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn on_initialize(_n: T::BlockNumber) -> frame_support::weights::Weight { + >::mutate(|count| *count = count.saturating_sub(1)); + + T::DbWeight::get().reads_writes(1, 1) + } + } + + impl, I: 'static> OwnedBridgeModule for Pallet { + const LOG_TARGET: &'static str = LOG_TARGET; + type OwnerStorage = PalletOwner; + type OperatingMode = BasicOperatingMode; + type OperatingModeStorage = PalletOperatingMode; + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Verify a target header is finalized according to the given finality proof. + /// + /// It will use the underlying storage pallet to fetch information about the current + /// authorities and best finalized header in order to verify that the header is finalized. + /// + /// If successful in verification, it will write the target header to the underlying storage + /// pallet. + #[pallet::weight(T::WeightInfo::submit_finality_proof( + justification.commit.precommits.len().try_into().unwrap_or(u32::MAX), + justification.votes_ancestries.len().try_into().unwrap_or(u32::MAX), + ))] + pub fn submit_finality_proof( + origin: OriginFor, + finality_target: Box>, + justification: GrandpaJustification>, + ) -> DispatchResultWithPostInfo { + Self::ensure_not_halted().map_err(Error::::BridgeModule)?; + let _ = ensure_signed(origin)?; + + ensure!(Self::request_count() < T::MaxRequests::get(), >::TooManyRequests); + + let (hash, number) = (finality_target.hash(), finality_target.number()); + log::trace!( + target: LOG_TARGET, + "Going to try and finalize header {:?}", + finality_target + ); + + let best_finalized = BestFinalized::::get(); + let best_finalized = + best_finalized.and_then(|(_, hash)| ImportedHeaders::::get(hash)); + let best_finalized = match best_finalized { + Some(best_finalized) => best_finalized, + None => { + log::error!( + target: LOG_TARGET, + "Cannot finalize header {:?} because pallet is not yet initialized", + finality_target, + ); + fail!(>::NotInitialized); + }, + }; + + // We do a quick check here to ensure that our header chain is making progress and isn't + // "travelling back in time" (which could be indicative of something bad, e.g a + // hard-fork). + ensure!(best_finalized.number() < number, >::OldHeader); + + let authority_set = >::get(); + let set_id = authority_set.set_id; + verify_justification::(&justification, hash, *number, authority_set.into())?; + + let is_authorities_change_enacted = + try_enact_authority_change::(&finality_target, set_id)?; + let finality_target = StoredBridgedHeader::::try_from_inner(*finality_target) + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "Size of header {:?} ({}) is larger that the configured value {}", + hash, + e.value_size, + e.maximal_size, + ); + + Error::::TooLargeHeader + })?; + >::mutate(|count| *count += 1); + insert_header::(finality_target, hash); + log::info!( + target: LOG_TARGET, + "Successfully imported finalized header with hash {:?}!", + hash + ); + + // mandatory header is a header that changes authorities set. The pallet can't go + // further without importing this header. So every bridge MUST import mandatory headers. + // + // We don't want to charge extra costs for mandatory operations. So relayer is not + // paying fee for mandatory headers import transactions. + let is_mandatory_header = is_authorities_change_enacted; + let pays_fee = if is_mandatory_header { Pays::No } else { Pays::Yes }; + + Ok(pays_fee.into()) + } + + /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. + /// + /// The initial configuration provided does not need to be the genesis header of the bridged + /// chain, it can be any arbitrary header. You can also provide the next scheduled set + /// change if it is already know. + /// + /// This function is only allowed to be called from a trusted origin and writes to storage + /// with practically no checks in terms of the validity of the data. It is important that + /// you ensure that valid data is being passed in. + #[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))] + pub fn initialize( + origin: OriginFor, + init_data: super::InitializationData>, + ) -> DispatchResultWithPostInfo { + Self::ensure_owner_or_root(origin)?; + + let init_allowed = !>::exists(); + ensure!(init_allowed, >::AlreadyInitialized); + initialize_bridge::(init_data.clone())?; + + log::info!( + target: LOG_TARGET, + "Pallet has been initialized with the following parameters: {:?}", + init_data + ); + + Ok(().into()) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + } + + /// The current number of requests which have written to storage. + /// + /// If the `RequestCount` hits `MaxRequests`, no more calls will be allowed to the pallet until + /// the request capacity is increased. + /// + /// The `RequestCount` is decreased by one at the beginning of every block. This is to ensure + /// that the pallet can always make progress. + #[pallet::storage] + #[pallet::getter(fn request_count)] + pub(super) type RequestCount, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + + /// Hash of the header used to bootstrap the pallet. + #[pallet::storage] + pub(super) type InitialHash, I: 'static = ()> = + StorageValue<_, BridgedBlockHash, ValueQuery>; + + /// Hash of the best finalized header. + #[pallet::storage] + pub type BestFinalized, I: 'static = ()> = + StorageValue<_, (BridgedBlockNumber, BridgedBlockHash), OptionQuery>; + + /// A ring buffer of imported hashes. Ordered by the insertion time. + #[pallet::storage] + pub(super) type ImportedHashes, I: 'static = ()> = + StorageMap<_, Identity, u32, BridgedBlockHash>; + + /// Current ring buffer position. + #[pallet::storage] + pub(super) type ImportedHashesPointer, I: 'static = ()> = + StorageValue<_, u32, ValueQuery>; + + /// Headers which have been imported into the pallet. + #[pallet::storage] + pub type ImportedHeaders, I: 'static = ()> = + StorageMap<_, Identity, BridgedBlockHash, StoredBridgedHeader>; + + /// The current GRANDPA Authority set. + #[pallet::storage] + pub(super) type CurrentAuthoritySet, I: 'static = ()> = + StorageValue<_, StoredAuthoritySet, ValueQuery>; + + /// Optional pallet owner. + /// + /// Pallet owner has a right to halt all pallet operations and then resume it. If it is + /// `None`, then there are no direct ways to halt/resume pallet operations, but other + /// runtime methods may still be used to do that (i.e. democracy::referendum to update halt + /// flag directly or call the `halt_operations`). + #[pallet::storage] + pub type PalletOwner, I: 'static = ()> = + StorageValue<_, T::AccountId, OptionQuery>; + + /// The current operating mode of the pallet. + /// + /// Depending on the mode either all, or no transactions will be allowed. + #[pallet::storage] + pub type PalletOperatingMode, I: 'static = ()> = + StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + /// Optional module owner account. + pub owner: Option, + /// Optional module initialization data. + pub init_data: Option>>, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { owner: None, init_data: None } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + if let Some(ref owner) = self.owner { + >::put(owner); + } + + if let Some(init_data) = self.init_data.clone() { + initialize_bridge::(init_data).expect("genesis config is correct; qed"); + } else { + // Since the bridge hasn't been initialized we shouldn't allow anyone to perform + // transactions. + >::put(BasicOperatingMode::Halted); + } + } + } + + #[pallet::error] + pub enum Error { + /// The given justification is invalid for the given header. + InvalidJustification, + /// The authority set from the underlying header chain is invalid. + InvalidAuthoritySet, + /// There are too many requests for the current window to handle. + TooManyRequests, + /// The header being imported is older than the best finalized header known to the pallet. + OldHeader, + /// The scheduled authority set change found in the header is unsupported by the pallet. + /// + /// This is the case for non-standard (e.g forced) authority set changes. + UnsupportedScheduledChange, + /// The pallet is not yet initialized. + NotInitialized, + /// The pallet has already been initialized. + AlreadyInitialized, + /// Too many authorities in the set. + TooManyAuthoritiesInSet, + /// Too large header. + TooLargeHeader, + /// Error generated by the `OwnedBridgeModule` trait. + BridgeModule(bp_runtime::OwnedBridgeModuleError), + } + + /// Check the given header for a GRANDPA scheduled authority set change. If a change + /// is found it will be enacted immediately. + /// + /// This function does not support forced changes, or scheduled changes with delays + /// since these types of changes are indicative of abnormal behavior from GRANDPA. + /// + /// Returned value will indicate if a change was enacted or not. + pub(crate) fn try_enact_authority_change, I: 'static>( + header: &BridgedHeader, + current_set_id: sp_finality_grandpa::SetId, + ) -> Result { + let mut change_enacted = false; + + // We don't support forced changes - at that point governance intervention is required. + ensure!( + super::find_forced_change(header).is_none(), + >::UnsupportedScheduledChange + ); + + if let Some(change) = super::find_scheduled_change(header) { + // GRANDPA only includes a `delay` for forced changes, so this isn't valid. + ensure!(change.delay == Zero::zero(), >::UnsupportedScheduledChange); + + // TODO [#788]: Stop manually increasing the `set_id` here. + let next_authorities = StoredAuthoritySet:: { + authorities: change + .next_authorities + .try_into() + .map_err(|_| Error::::TooManyAuthoritiesInSet)?, + set_id: current_set_id + 1, + }; + + // Since our header schedules a change and we know the delay is 0, it must also enact + // the change. + >::put(&next_authorities); + change_enacted = true; + + log::info!( + target: LOG_TARGET, + "Transitioned from authority set {} to {}! New authorities are: {:?}", + current_set_id, + current_set_id + 1, + next_authorities, + ); + }; + + Ok(change_enacted) + } + + /// Verify a GRANDPA justification (finality proof) for a given header. + /// + /// Will use the GRANDPA current authorities known to the pallet. + /// + /// If successful it returns the decoded GRANDPA justification so we can refund any weight which + /// was overcharged in the initial call. + pub(crate) fn verify_justification, I: 'static>( + justification: &GrandpaJustification>, + hash: BridgedBlockHash, + number: BridgedBlockNumber, + authority_set: bp_header_chain::AuthoritySet, + ) -> Result<(), sp_runtime::DispatchError> { + use bp_header_chain::justification::verify_justification; + + let voter_set = + VoterSet::new(authority_set.authorities).ok_or(>::InvalidAuthoritySet)?; + let set_id = authority_set.set_id; + + Ok(verify_justification::>( + (hash, number), + set_id, + &voter_set, + justification, + ) + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "Received invalid justification for {:?}: {:?}", + hash, + e, + ); + >::InvalidJustification + })?) + } + + /// Import a previously verified header to the storage. + /// + /// Note this function solely takes care of updating the storage and pruning old entries, + /// but does not verify the validity of such import. + pub(crate) fn insert_header, I: 'static>( + header: StoredBridgedHeader, + hash: BridgedBlockHash, + ) { + let index = >::get(); + let pruning = >::try_get(index); + >::put((*header.number(), hash)); + >::insert(hash, header); + >::insert(index, hash); + + // Update ring buffer pointer and remove old header. + >::put((index + 1) % T::HeadersToKeep::get()); + if let Ok(hash) = pruning { + log::debug!(target: LOG_TARGET, "Pruning old header: {:?}.", hash); + >::remove(hash); + } + } + + /// Since this writes to storage with no real checks this should only be used in functions that + /// were called by a trusted origin. + pub(crate) fn initialize_bridge, I: 'static>( + init_params: super::InitializationData>, + ) -> Result<(), Error> { + let super::InitializationData { header, authority_list, set_id, operating_mode } = + init_params; + let authority_set_length = authority_list.len(); + let authority_set = StoredAuthoritySet::::try_new(authority_list, set_id) + .map_err(|_| { + log::error!( + target: LOG_TARGET, + "Failed to initialize bridge. Number of authorities in the set {} is larger than the configured value {}", + authority_set_length, + T::MaxBridgedAuthorities::get(), + ); + + Error::TooManyAuthoritiesInSet + })?; + let initial_hash = header.hash(); + let header = StoredBridgedHeader::::try_from_inner(*header).map_err(|e| { + log::error!( + target: LOG_TARGET, + "Failed to initialize bridge. Size of header {:?} ({}) is larger that the configured value {}", + initial_hash, + e.value_size, + e.maximal_size, + ); + + Error::::TooLargeHeader + })?; + + >::put(initial_hash); + >::put(0); + insert_header::(header, initial_hash); + + >::put(authority_set); + + >::put(operating_mode); + + Ok(()) + } + + #[cfg(feature = "runtime-benchmarks")] + pub(crate) fn bootstrap_bridge, I: 'static>( + init_params: super::InitializationData>, + ) { + let start_number = *init_params.header.number(); + let end_number = start_number + T::HeadersToKeep::get().into(); + initialize_bridge::(init_params).expect("benchmarks are correct"); + + let mut number = start_number; + while number < end_number { + number = number + sp_runtime::traits::One::one(); + let header = >::new( + number, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); + let hash = header.hash(); + insert_header::( + StoredBridgedHeader::::try_from_inner(header) + .expect("only used from benchmarks; benchmarks are correct; qed"), + hash, + ); + } + } +} + +impl, I: 'static> Pallet { + /// Get the best finalized header the pallet knows of. + pub fn best_finalized() -> Option> { + let (_, hash) = >::get()?; + >::get(hash).map(|h| h.into_inner()) + } + + /// Check if a particular header is known to the bridge pallet. + pub fn is_known_header(hash: BridgedBlockHash) -> bool { + >::contains_key(hash) + } +} + +/// Bridge GRANDPA pallet as header chain. +pub type GrandpaChainHeaders = Pallet; + +impl, I: 'static> HeaderChain> for GrandpaChainHeaders { + fn finalized_header(hash: HashOf>) -> Option>> { + ImportedHeaders::::get(hash).map(|h| h.into_inner()) + } +} + +pub(crate) fn find_scheduled_change( + header: &H, +) -> Option> { + use sp_runtime::generic::OpaqueDigestItemId; + + let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID); + + let filter_log = |log: ConsensusLog| match log { + ConsensusLog::ScheduledChange(change) => Some(change), + _ => None, + }; + + // find the first consensus digest with the right ID which converts to + // the right kind of consensus log. + header.digest().convert_first(|l| l.try_to(id).and_then(filter_log)) +} + +/// Checks the given header for a consensus digest signaling a **forced** scheduled change and +/// extracts it. +pub(crate) fn find_forced_change( + header: &H, +) -> Option<(H::Number, sp_finality_grandpa::ScheduledChange)> { + use sp_runtime::generic::OpaqueDigestItemId; + + let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID); + + let filter_log = |log: ConsensusLog| match log { + ConsensusLog::ForcedChange(delay, change) => Some((delay, change)), + _ => None, + }; + + // find the first consensus digest with the right ID which converts to + // the right kind of consensus log. + header.digest().convert_first(|l| l.try_to(id).and_then(filter_log)) +} + +/// (Re)initialize bridge with given header for using it in `pallet-bridge-messages` benchmarks. +#[cfg(feature = "runtime-benchmarks")] +pub fn initialize_for_benchmarks, I: 'static>(header: BridgedHeader) { + initialize_bridge::(InitializationData { + header: Box::new(header), + authority_list: sp_std::vec::Vec::new(), /* we don't verify any proofs in external + * benchmarks */ + set_id: 0, + operating_mode: bp_runtime::BasicOperatingMode::Normal, + }) + .expect("only used from benchmarks; benchmarks are correct; qed"); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{ + run_test, test_header, RuntimeOrigin, TestHeader, TestNumber, TestRuntime, + MAX_BRIDGED_AUTHORITIES, MAX_HEADER_SIZE, + }; + use bp_runtime::BasicOperatingMode; + use bp_test_utils::{ + authority_list, generate_owned_bridge_module_tests, make_default_justification, + make_justification_for_header, JustificationGeneratorParams, ALICE, BOB, + }; + use codec::Encode; + use frame_support::{ + assert_err, assert_noop, assert_ok, dispatch::PostDispatchInfo, + storage::generator::StorageValue, + }; + use sp_runtime::{Digest, DigestItem, DispatchError}; + + fn initialize_substrate_bridge() { + assert_ok!(init_with_origin(RuntimeOrigin::root())); + } + + fn init_with_origin( + origin: RuntimeOrigin, + ) -> Result< + InitializationData, + sp_runtime::DispatchErrorWithPostInfo, + > { + let genesis = test_header(0); + + let init_data = InitializationData { + header: Box::new(genesis), + authority_list: authority_list(), + set_id: 1, + operating_mode: BasicOperatingMode::Normal, + }; + + Pallet::::initialize(origin, init_data.clone()).map(|_| init_data) + } + + fn submit_finality_proof(header: u8) -> frame_support::dispatch::DispatchResultWithPostInfo { + let header = test_header(header.into()); + let justification = make_default_justification(&header); + + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification, + ) + } + + fn next_block() { + use frame_support::traits::OnInitialize; + + let current_number = frame_system::Pallet::::block_number(); + frame_system::Pallet::::set_block_number(current_number + 1); + let _ = Pallet::::on_initialize(current_number); + } + + fn change_log(delay: u64) -> Digest { + let consensus_log = + ConsensusLog::::ScheduledChange(sp_finality_grandpa::ScheduledChange { + next_authorities: vec![(ALICE.into(), 1), (BOB.into(), 1)], + delay, + }); + + Digest { logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())] } + } + + fn forced_change_log(delay: u64) -> Digest { + let consensus_log = ConsensusLog::::ForcedChange( + delay, + sp_finality_grandpa::ScheduledChange { + next_authorities: vec![(ALICE.into(), 1), (BOB.into(), 1)], + delay, + }, + ); + + Digest { logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())] } + } + + fn many_authorities_log() -> Digest { + let consensus_log = + ConsensusLog::::ScheduledChange(sp_finality_grandpa::ScheduledChange { + next_authorities: std::iter::repeat((ALICE.into(), 1)) + .take(MAX_BRIDGED_AUTHORITIES as usize + 1) + .collect(), + delay: 0, + }); + + Digest { logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())] } + } + + fn large_digest() -> Digest { + Digest { logs: vec![DigestItem::Other(vec![42; MAX_HEADER_SIZE as _])] } + } + + #[test] + fn init_root_or_owner_origin_can_initialize_pallet() { + run_test(|| { + assert_noop!(init_with_origin(RuntimeOrigin::signed(1)), DispatchError::BadOrigin); + assert_ok!(init_with_origin(RuntimeOrigin::root())); + + // Reset storage so we can initialize the pallet again + BestFinalized::::kill(); + PalletOwner::::put(2); + assert_ok!(init_with_origin(RuntimeOrigin::signed(2))); + }) + } + + #[test] + fn init_storage_entries_are_correctly_initialized() { + run_test(|| { + assert_eq!(BestFinalized::::get(), None,); + assert_eq!(Pallet::::best_finalized(), None); + + let init_data = init_with_origin(RuntimeOrigin::root()).unwrap(); + + assert!(>::contains_key(init_data.header.hash())); + assert_eq!(BestFinalized::::get().unwrap().1, init_data.header.hash()); + assert_eq!( + CurrentAuthoritySet::::get().authorities, + init_data.authority_list + ); + assert_eq!(PalletOperatingMode::::get(), BasicOperatingMode::Normal); + }) + } + + #[test] + fn init_can_only_initialize_pallet_once() { + run_test(|| { + initialize_substrate_bridge(); + assert_noop!( + init_with_origin(RuntimeOrigin::root()), + >::AlreadyInitialized + ); + }) + } + + #[test] + fn init_fails_if_there_are_too_many_authorities_in_the_set() { + run_test(|| { + let genesis = test_header(0); + let init_data = InitializationData { + header: Box::new(genesis), + authority_list: std::iter::repeat(authority_list().remove(0)) + .take(MAX_BRIDGED_AUTHORITIES as usize + 1) + .collect(), + set_id: 1, + operating_mode: BasicOperatingMode::Normal, + }; + + assert_noop!( + Pallet::::initialize(RuntimeOrigin::root(), init_data), + Error::::TooManyAuthoritiesInSet, + ); + }); + } + + #[test] + fn init_fails_if_header_is_too_large() { + run_test(|| { + let mut genesis = test_header(0); + genesis.digest = large_digest(); + let init_data = InitializationData { + header: Box::new(genesis), + authority_list: authority_list(), + set_id: 1, + operating_mode: BasicOperatingMode::Normal, + }; + + assert_noop!( + Pallet::::initialize(RuntimeOrigin::root(), init_data), + Error::::TooLargeHeader, + ); + }); + } + + #[test] + fn pallet_rejects_transactions_if_halted() { + run_test(|| { + initialize_substrate_bridge(); + + assert_ok!(Pallet::::set_operating_mode( + RuntimeOrigin::root(), + BasicOperatingMode::Halted + )); + assert_noop!( + submit_finality_proof(1), + Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted) + ); + + assert_ok!(Pallet::::set_operating_mode( + RuntimeOrigin::root(), + BasicOperatingMode::Normal + )); + assert_ok!(submit_finality_proof(1)); + }) + } + + #[test] + fn pallet_rejects_header_if_not_initialized_yet() { + run_test(|| { + assert_noop!(submit_finality_proof(1), Error::::NotInitialized); + }); + } + + #[test] + fn succesfully_imports_header_with_valid_finality() { + run_test(|| { + initialize_substrate_bridge(); + assert_ok!( + submit_finality_proof(1), + PostDispatchInfo { + actual_weight: None, + pays_fee: frame_support::dispatch::Pays::Yes, + }, + ); + + let header = test_header(1); + assert_eq!(>::get().unwrap().1, header.hash()); + assert!(>::contains_key(header.hash())); + }) + } + + #[test] + fn rejects_justification_that_skips_authority_set_transition() { + run_test(|| { + initialize_substrate_bridge(); + + let header = test_header(1); + + let params = + JustificationGeneratorParams:: { set_id: 2, ..Default::default() }; + let justification = make_justification_for_header(params); + + assert_err!( + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification, + ), + >::InvalidJustification + ); + }) + } + + #[test] + fn does_not_import_header_with_invalid_finality_proof() { + run_test(|| { + initialize_substrate_bridge(); + + let header = test_header(1); + let mut justification = make_default_justification(&header); + justification.round = 42; + + assert_err!( + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification, + ), + >::InvalidJustification + ); + }) + } + + #[test] + fn disallows_invalid_authority_set() { + run_test(|| { + let genesis = test_header(0); + + let invalid_authority_list = vec![(ALICE.into(), u64::MAX), (BOB.into(), u64::MAX)]; + let init_data = InitializationData { + header: Box::new(genesis), + authority_list: invalid_authority_list, + set_id: 1, + operating_mode: BasicOperatingMode::Normal, + }; + + assert_ok!(Pallet::::initialize(RuntimeOrigin::root(), init_data)); + + let header = test_header(1); + let justification = make_default_justification(&header); + + assert_err!( + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification, + ), + >::InvalidAuthoritySet + ); + }) + } + + #[test] + fn importing_header_ensures_that_chain_is_extended() { + run_test(|| { + initialize_substrate_bridge(); + + assert_ok!(submit_finality_proof(4)); + assert_err!(submit_finality_proof(3), Error::::OldHeader); + assert_ok!(submit_finality_proof(5)); + }) + } + + #[test] + fn importing_header_enacts_new_authority_set() { + run_test(|| { + initialize_substrate_bridge(); + + let next_set_id = 2; + let next_authorities = vec![(ALICE.into(), 1), (BOB.into(), 1)]; + + // Need to update the header digest to indicate that our header signals an authority set + // change. The change will be enacted when we import our header. + let mut header = test_header(2); + header.digest = change_log(0); + + // Create a valid justification for the header + let justification = make_default_justification(&header); + + // Let's import our test header + assert_ok!( + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header.clone()), + justification + ), + PostDispatchInfo { + actual_weight: None, + pays_fee: frame_support::dispatch::Pays::No, + }, + ); + + // Make sure that our header is the best finalized + assert_eq!(>::get().unwrap().1, header.hash()); + assert!(>::contains_key(header.hash())); + + // Make sure that the authority set actually changed upon importing our header + assert_eq!( + >::get(), + StoredAuthoritySet::::try_new(next_authorities, next_set_id) + .unwrap(), + ); + }) + } + + #[test] + fn importing_header_rejects_header_with_scheduled_change_delay() { + run_test(|| { + initialize_substrate_bridge(); + + // Need to update the header digest to indicate that our header signals an authority set + // change. However, the change doesn't happen until the next block. + let mut header = test_header(2); + header.digest = change_log(1); + + // Create a valid justification for the header + let justification = make_default_justification(&header); + + // Should not be allowed to import this header + assert_err!( + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification + ), + >::UnsupportedScheduledChange + ); + }) + } + + #[test] + fn importing_header_rejects_header_with_forced_changes() { + run_test(|| { + initialize_substrate_bridge(); + + // Need to update the header digest to indicate that it signals a forced authority set + // change. + let mut header = test_header(2); + header.digest = forced_change_log(0); + + // Create a valid justification for the header + let justification = make_default_justification(&header); + + // Should not be allowed to import this header + assert_err!( + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification + ), + >::UnsupportedScheduledChange + ); + }) + } + + #[test] + fn importing_header_rejects_header_with_too_many_authorities() { + run_test(|| { + initialize_substrate_bridge(); + + // Need to update the header digest to indicate that our header signals an authority set + // change. However, the change doesn't happen until the next block. + let mut header = test_header(2); + header.digest = many_authorities_log(); + + // Create a valid justification for the header + let justification = make_default_justification(&header); + + // Should not be allowed to import this header + assert_err!( + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification + ), + >::TooManyAuthoritiesInSet + ); + }); + } + + #[test] + fn importing_header_rejects_header_if_it_is_too_large() { + run_test(|| { + initialize_substrate_bridge(); + + // Need to update the header digest to indicate that our header signals an authority set + // change. However, the change doesn't happen until the next block. + let mut header = test_header(2); + header.digest = large_digest(); + + // Create a valid justification for the header + let justification = make_default_justification(&header); + + // Should not be allowed to import this header + assert_err!( + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification + ), + >::TooLargeHeader + ); + }); + } + + #[test] + fn parse_finalized_storage_proof_rejects_proof_on_unknown_header() { + run_test(|| { + assert_noop!( + Pallet::::parse_finalized_storage_proof( + Default::default(), + sp_trie::StorageProof::new(vec![]), + |_| (), + ), + bp_header_chain::HeaderChainError::UnknownHeader, + ); + }); + } + + #[test] + fn parse_finalized_storage_accepts_valid_proof() { + run_test(|| { + let (state_root, storage_proof) = bp_runtime::craft_valid_storage_proof(); + + let mut header = test_header(2); + header.set_state_root(state_root); + + let hash = header.hash(); + >::put((2, hash)); + >::insert( + hash, + StoredBridgedHeader::::try_from_inner(header).unwrap(), + ); + + assert_ok!( + Pallet::::parse_finalized_storage_proof(hash, storage_proof, |_| (),), + (), + ); + }); + } + + #[test] + fn rate_limiter_disallows_imports_once_limit_is_hit_in_single_block() { + run_test(|| { + initialize_substrate_bridge(); + + assert_ok!(submit_finality_proof(1)); + assert_ok!(submit_finality_proof(2)); + assert_err!(submit_finality_proof(3), >::TooManyRequests); + }) + } + + #[test] + fn rate_limiter_invalid_requests_do_not_count_towards_request_count() { + run_test(|| { + let submit_invalid_request = || { + let header = test_header(1); + let mut invalid_justification = make_default_justification(&header); + invalid_justification.round = 42; + + Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + invalid_justification, + ) + }; + + initialize_substrate_bridge(); + + for _ in 0..::MaxRequests::get() + 1 { + // Notice that the error here *isn't* `TooManyRequests` + assert_err!(submit_invalid_request(), >::InvalidJustification); + } + + // Can still submit `MaxRequests` requests afterwards + assert_ok!(submit_finality_proof(1)); + assert_ok!(submit_finality_proof(2)); + assert_err!(submit_finality_proof(3), >::TooManyRequests); + }) + } + + #[test] + fn rate_limiter_allows_request_after_new_block_has_started() { + run_test(|| { + initialize_substrate_bridge(); + assert_ok!(submit_finality_proof(1)); + assert_ok!(submit_finality_proof(2)); + + next_block(); + assert_ok!(submit_finality_proof(3)); + }) + } + + #[test] + fn rate_limiter_disallows_imports_once_limit_is_hit_across_different_blocks() { + run_test(|| { + initialize_substrate_bridge(); + assert_ok!(submit_finality_proof(1)); + assert_ok!(submit_finality_proof(2)); + + next_block(); + assert_ok!(submit_finality_proof(3)); + assert_err!(submit_finality_proof(4), >::TooManyRequests); + }) + } + + #[test] + fn rate_limiter_allows_max_requests_after_long_time_with_no_activity() { + run_test(|| { + initialize_substrate_bridge(); + assert_ok!(submit_finality_proof(1)); + assert_ok!(submit_finality_proof(2)); + + next_block(); + next_block(); + + next_block(); + assert_ok!(submit_finality_proof(5)); + assert_ok!(submit_finality_proof(7)); + }) + } + + #[test] + fn should_prune_headers_over_headers_to_keep_parameter() { + run_test(|| { + initialize_substrate_bridge(); + assert_ok!(submit_finality_proof(1)); + let first_header = Pallet::::best_finalized().unwrap(); + next_block(); + + assert_ok!(submit_finality_proof(2)); + next_block(); + assert_ok!(submit_finality_proof(3)); + next_block(); + assert_ok!(submit_finality_proof(4)); + next_block(); + assert_ok!(submit_finality_proof(5)); + next_block(); + + assert_ok!(submit_finality_proof(6)); + + assert!( + !Pallet::::is_known_header(first_header.hash()), + "First header should be pruned." + ); + }) + } + + #[test] + fn storage_keys_computed_properly() { + assert_eq!( + PalletOperatingMode::::storage_value_final_key().to_vec(), + bp_header_chain::storage_keys::pallet_operating_mode_key("Grandpa").0, + ); + + assert_eq!( + BestFinalized::::storage_value_final_key().to_vec(), + bp_header_chain::storage_keys::best_finalized_key("Grandpa").0, + ); + } + + generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted); +} diff --git a/modules/grandpa/src/mock.rs b/modules/grandpa/src/mock.rs new file mode 100644 index 00000000000..0f83bd528d6 --- /dev/null +++ b/modules/grandpa/src/mock.rs @@ -0,0 +1,131 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +// From construct_runtime macro +#![allow(clippy::from_over_into)] + +use bp_runtime::Chain; +use frame_support::{construct_runtime, parameter_types, weights::Weight}; +use sp_core::sr25519::Signature; +use sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +pub type AccountId = u64; +pub type TestHeader = crate::BridgedHeader; +pub type TestNumber = crate::BridgedBlockNumber; + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +pub const MAX_BRIDGED_AUTHORITIES: u32 = 2048; +pub const MAX_HEADER_SIZE: u32 = 65536; + +use crate as grandpa; + +construct_runtime! { + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Grandpa: grandpa::{Pallet, Call}, + } +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = (); + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type DbWeight = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const MaxRequests: u32 = 2; + pub const HeadersToKeep: u32 = 5; + pub const SessionLength: u64 = 5; + pub const NumValidators: u32 = 5; +} + +impl grandpa::Config for TestRuntime { + type BridgedChain = TestBridgedChain; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type MaxBridgedAuthorities = frame_support::traits::ConstU32; + type MaxBridgedHeaderSize = frame_support::traits::ConstU32; + type WeightInfo = (); +} + +#[derive(Debug)] +pub struct TestBridgedChain; + +impl Chain for TestBridgedChain { + type BlockNumber = ::BlockNumber; + type Hash = ::Hash; + type Hasher = ::Hashing; + type Header = ::Header; + + type AccountId = AccountId; + type Balance = u64; + type Index = u64; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + unreachable!() + } + fn max_extrinsic_weight() -> Weight { + unreachable!() + } +} + +pub fn run_test(test: impl FnOnce() -> T) -> T { + sp_io::TestExternalities::new(Default::default()).execute_with(test) +} + +pub fn test_header(num: TestNumber) -> TestHeader { + // We wrap the call to avoid explicit type annotations in our tests + bp_test_utils::test_header(num) +} diff --git a/modules/grandpa/src/storage_types.rs b/modules/grandpa/src/storage_types.rs new file mode 100644 index 00000000000..d930dbadbc6 --- /dev/null +++ b/modules/grandpa/src/storage_types.rs @@ -0,0 +1,66 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Wrappers for public types that are implementing `MaxEncodedLen` + +use crate::Config; + +use bp_header_chain::AuthoritySet; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{BoundedVec, RuntimeDebugNoBound}; +use scale_info::TypeInfo; +use sp_finality_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId}; + +/// A bounded list of Grandpa authorities with associated weights. +pub type StoredAuthorityList = + BoundedVec<(AuthorityId, AuthorityWeight), MaxBridgedAuthorities>; + +/// A bounded GRANDPA Authority List and ID. +#[derive(Clone, Decode, Encode, Eq, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)] +#[scale_info(skip_type_params(T, I))] +pub struct StoredAuthoritySet, I: 'static> { + /// List of GRANDPA authorities for the current round. + pub authorities: StoredAuthorityList<>::MaxBridgedAuthorities>, + /// Monotonic identifier of the current GRANDPA authority set. + pub set_id: SetId, +} + +impl, I: 'static> StoredAuthoritySet { + /// Try to create a new bounded GRANDPA Authority Set from unbounded list. + /// + /// Returns error if number of authorities in the provided list is too large. + pub fn try_new(authorities: AuthorityList, set_id: SetId) -> Result { + Ok(Self { authorities: TryFrom::try_from(authorities).map_err(drop)?, set_id }) + } +} + +impl, I: 'static> PartialEq for StoredAuthoritySet { + fn eq(&self, other: &Self) -> bool { + self.set_id == other.set_id && self.authorities == other.authorities + } +} + +impl, I: 'static> Default for StoredAuthoritySet { + fn default() -> Self { + StoredAuthoritySet { authorities: BoundedVec::default(), set_id: 0 } + } +} + +impl, I: 'static> From> for AuthoritySet { + fn from(t: StoredAuthoritySet) -> Self { + AuthoritySet { authorities: t.authorities.into(), set_id: t.set_id } + } +} diff --git a/modules/grandpa/src/weights.rs b/modules/grandpa/src/weights.rs new file mode 100644 index 00000000000..6997713face --- /dev/null +++ b/modules/grandpa/src/weights.rs @@ -0,0 +1,79 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Autogenerated weights for `pallet_bridge_grandpa` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-17, STEPS: 50, REPEAT: 20 +//! LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled +//! CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/millau-bridge-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_bridge_grandpa +// --extrinsic=* +// --execution=wasm +// --wasm-execution=Compiled +// --heap-pages=4096 +// --output=./modules/grandpa/src/weights.rs +// --template=./.maintain/millau-weight-template.hbs + +#![allow(clippy::all)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for `pallet_bridge_grandpa`. +pub trait WeightInfo { + fn submit_finality_proof(p: u32, v: u32) -> Weight; +} + +/// Weights for `pallet_bridge_grandpa` that are generated using one of the Bridge testnets. +/// +/// Those weights are test only and must never be used in production. +pub struct BridgeWeight(PhantomData); +impl WeightInfo for BridgeWeight { + fn submit_finality_proof(p: u32, v: u32) -> Weight { + Weight::from_ref_time(198_274_668 as u64) + .saturating_add(Weight::from_ref_time(39_830_948 as u64).saturating_mul(p as u64)) + .saturating_add(Weight::from_ref_time(1_535_681 as u64).saturating_mul(v as u64)) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn submit_finality_proof(p: u32, v: u32) -> Weight { + Weight::from_ref_time(198_274_668 as u64) + .saturating_add(Weight::from_ref_time(39_830_948 as u64).saturating_mul(p as u64)) + .saturating_add(Weight::from_ref_time(1_535_681 as u64).saturating_mul(v as u64)) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } +} diff --git a/modules/messages/Cargo.toml b/modules/messages/Cargo.toml new file mode 100644 index 00000000000..0c2fecb0be3 --- /dev/null +++ b/modules/messages/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "pallet-bridge-messages" +description = "Module that allows bridged chains to exchange messages using lane concept." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +log = { version = "0.4.17", default-features = false } +num-traits = { version = "0.2", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Bridge dependencies + +bp-messages = { path = "../../primitives/messages", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } + +# Substrate Dependencies + +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } +bp-test-utils = { path = "../../primitives/test-utils" } + +[features] +default = ["std"] +std = [ + "bp-messages/std", + "bp-runtime/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "log/std", + "num-traits/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", +] diff --git a/modules/messages/README.md b/modules/messages/README.md new file mode 100644 index 00000000000..2dc56296842 --- /dev/null +++ b/modules/messages/README.md @@ -0,0 +1,424 @@ +# Messages Module + +The messages module is used to deliver messages from source chain to target chain. Message is +(almost) opaque to the module and the final goal is to hand message to the message dispatch +mechanism. + +## Contents +- [Overview](#overview) +- [Message Workflow](#message-workflow) +- [Integrating Message Lane Module into Runtime](#integrating-messages-module-into-runtime) +- [Non-Essential Functionality](#non-essential-functionality) +- [Weights of Module Extrinsics](#weights-of-module-extrinsics) + +## Overview + +Message lane is an unidirectional channel, where messages are sent from source chain to the target +chain. At the same time, a single instance of messages module supports both outbound lanes and +inbound lanes. So the chain where the module is deployed (this chain), may act as a source chain for +outbound messages (heading to a bridged chain) and as a target chain for inbound messages (coming +from a bridged chain). + +Messages module supports multiple message lanes. Every message lane is identified with a 4-byte +identifier. Messages sent through the lane are assigned unique (for this lane) increasing integer +value that is known as nonce ("number that can only be used once"). Messages that are sent over the +same lane are guaranteed to be delivered to the target chain in the same order they're sent from +the source chain. In other words, message with nonce `N` will be delivered right before delivering a +message with nonce `N+1`. + +Single message lane may be seen as a transport channel for single application (onchain, offchain or +mixed). At the same time the module itself never dictates any lane or message rules. In the end, it +is the runtime developer who defines what message lane and message mean for this runtime. + +## Message Workflow + +The message "appears" when its submitter calls the `send_message()` function of the module. The +submitter specifies the lane that he's willing to use, the message itself and the fee that he's +willing to pay for the message delivery and dispatch. If a message passes all checks, the nonce is +assigned and the message is stored in the module storage. The message is in an "undelivered" state +now. + +We assume that there are external, offchain actors, called relayers, that are submitting module +related transactions to both target and source chains. The pallet itself has no assumptions about +relayers incentivization scheme, but it has some callbacks for paying rewards. See +[Integrating Messages Module into runtime](#Integrating-Messages-Module-into-runtime) +for details. + +Eventually, some relayer would notice this message in the "undelivered" state and it would decide to +deliver this message. Relayer then crafts `receive_messages_proof()` transaction (aka delivery +transaction) for the messages module instance, deployed at the target chain. Relayer provides +his account id at the source chain, the proof of message (or several messages), the number of +messages in the transaction and their cumulative dispatch weight. Once a transaction is mined, the +message is considered "delivered". + +Once a message is delivered, the relayer may want to confirm delivery back to the source chain. +There are two reasons why he would want to do that. The first is that we intentionally limit number +of "delivered", but not yet "confirmed" messages at inbound lanes +(see [What about other Constants in the Messages Module Configuration Trait](#What-about-other-Constants-in-the-Messages-Module-Configuration-Trait) for explanation). +So at some point, the target chain may stop accepting new messages until relayers confirm some of +these. The second is that if the relayer wants to be rewarded for delivery, he must prove the fact +that he has actually delivered the message. And this proof may only be generated after the delivery +transaction is mined. So relayer crafts the `receive_messages_delivery_proof()` transaction (aka +confirmation transaction) for the messages module instance, deployed at the source chain. Once +this transaction is mined, the message is considered "confirmed". + +The "confirmed" state is the final state of the message. But there's one last thing related to the +message - the fact that it is now "confirmed" and reward has been paid to the relayer (or at least +callback for this has been called), must be confirmed to the target chain. Otherwise, we may reach +the limit of "unconfirmed" messages at the target chain and it will stop accepting new messages. So +relayer sometimes includes a nonce of the latest "confirmed" message in the next +`receive_messages_proof()` transaction, proving that some messages have been confirmed. + +## Integrating Messages Module into Runtime + +As it has been said above, the messages module supports both outbound and inbound message lanes. +So if we will integrate a module in some runtime, it may act as the source chain runtime for +outbound messages and as the target chain runtime for inbound messages. In this section, we'll +sometimes refer to the chain we're currently integrating with, as this chain and the other chain as +bridged chain. + +Messages module doesn't simply accept transactions that are claiming that the bridged chain has +some updated data for us. Instead of this, the module assumes that the bridged chain is able to +prove that updated data in some way. The proof is abstracted from the module and may be of any kind. +In our Substrate-to-Substrate bridge we're using runtime storage proofs. Other bridges may use +transaction proofs, Substrate header digests or anything else that may be proved. + +**IMPORTANT NOTE**: everything below in this chapter describes details of the messages module +configuration. But if you interested in well-probed and relatively easy integration of two +Substrate-based chains, you may want to look at the +[bridge-runtime-common](../../bin/runtime-common/README.md) crate. This crate is providing a lot of +helpers for integration, which may be directly used from within your runtime. Then if you'll decide +to change something in this scheme, get back here for detailed information. + +### General Information + +The messages module supports instances. Every module instance is supposed to bridge this chain +and some bridged chain. To bridge with another chain, using another instance is suggested (this +isn't forced anywhere in the code, though). + +Message submitters may track message progress by inspecting module events. When Message is accepted, +the `MessageAccepted` event is emitted in the `send_message()` transaction. The event contains both +message lane identifier and nonce that has been assigned to the message. When a message is delivered +to the target chain, the `MessagesDelivered` event is emitted from the +`receive_messages_delivery_proof()` transaction. The `MessagesDelivered` contains the message lane +identifier, inclusive range of delivered message nonces and their single-bit dispatch results. + +Please note that the meaning of the 'dispatch result' is determined by the message dispatcher at +the target chain. For example, in case of immediate call dispatcher it will be the `true` if call +has been successfully dispatched and `false` if it has only been delivered. This simple mechanism +built into the messages module allows building basic bridge applications, which only care whether +their messages have been successfully dispatched or not. More sophisticated applications may use +their own dispatch result delivery mechanism to deliver something larger than single bit. + +### How to plug-in Messages Module to Send Messages to the Bridged Chain? + +The `pallet_bridge_messages::Config` trait has 3 main associated types that are used to work with +outbound messages. The `pallet_bridge_messages::Config::TargetHeaderChain` defines how we see the +bridged chain as the target for our outbound messages. It must be able to check that the bridged +chain may accept our message - like that the message has size below maximal possible transaction +size of the chain and so on. And when the relayer sends us a confirmation transaction, this +implementation must be able to parse and verify the proof of messages delivery. Normally, you would +reuse the same (configurable) type on all chains that are sending messages to the same bridged +chain. + +The `pallet_bridge_messages::Config::LaneMessageVerifier` defines a single callback to verify outbound +messages. The simplest callback may just accept all messages. But in this case you'll need to answer +many questions first. Who will pay for the delivery and confirmation transaction? Are we sure that +someone will ever deliver this message to the bridged chain? Are we sure that we don't bloat our +runtime storage by accepting this message? What if the message is improperly encoded or has some +fields set to invalid values? Answering all those (and similar) questions would lead to correct +implementation. + +There's another thing to consider when implementing type for use in +`pallet_bridge_messages::Config::LaneMessageVerifier`. It is whether we treat all message lanes +identically, or they'll have different sets of verification rules? For example, you may reserve +lane#1 for messages coming from some 'wrapped-token' pallet - then you may verify in your +implementation that the origin is associated with this pallet. Lane#2 may be reserved for 'system' +messages and you may charge zero fee for such messages. You may have some rate limiting for messages +sent over the lane#3. Or you may just verify the same rules set for all outbound messages - it is +all up to the `pallet_bridge_messages::Config::LaneMessageVerifier` implementation. + +The last type is the `pallet_bridge_messages::Config::MessageDeliveryAndDispatchPayment`. When all +checks are made and we have decided to accept the message, we're calling the +`pay_delivery_and_dispatch_fee()` callback, passing the corresponding argument of the `send_message` +function. Later, when message delivery is confirmed, we're calling `pay_relayers_rewards()` +callback, passing accounts of relayers and messages that they have delivered. The simplest +implementation of this trait is in the [`instant_payments.rs`](./src/instant_payments.rs) module and +simply calls `Currency::transfer()` when those callbacks are called. So `Currency` units are +transferred between submitter, 'relayers fund' and relayers accounts. Other implementations may use +more or less sophisticated techniques - the whole relayers incentivization scheme is not a part of +the messages module. + +### I have a Messages Module in my Runtime, but I Want to Reject all Outbound Messages. What shall I do? + +You should be looking at the `bp_messages::source_chain::ForbidOutboundMessages` structure +[`bp_messages::source_chain`](../../primitives/messages/src/source_chain.rs). It implements +all required traits and will simply reject all transactions, related to outbound messages. + +### How to plug-in Messages Module to Receive Messages from the Bridged Chain? + +The `pallet_bridge_messages::Config` trait has 2 main associated types that are used to work with +inbound messages. The `pallet_bridge_messages::Config::SourceHeaderChain` defines how we see the +bridged chain as the source or our inbound messages. When relayer sends us a delivery transaction, +this implementation must be able to parse and verify the proof of messages wrapped in this +transaction. Normally, you would reuse the same (configurable) type on all chains that are sending +messages to the same bridged chain. + +The `pallet_bridge_messages::Config::MessageDispatch` defines a way on how to dispatch delivered +messages. Apart from actually dispatching the message, the implementation must return the correct +dispatch weight of the message before dispatch is called. + +### I have a Messages Module in my Runtime, but I Want to Reject all Inbound Messages. What +shall I do? + +You should be looking at the `bp_messages::target_chain::ForbidInboundMessages` structure from +the [`bp_messages::target_chain`](../../primitives/messages/src/target_chain.rs) module. It +implements all required traits and will simply reject all transactions, related to inbound messages. + +### What about other Constants in the Messages Module Configuration Trait? + +Message is being stored in the source chain storage until its delivery will be confirmed. After +that, we may safely remove the message from the storage. Lane messages are removed (pruned) when +someone sends a new message using the same lane. So the message submitter pays for that pruning. To +avoid pruning too many messages in a single transaction, there's +`pallet_bridge_messages::Config::MaxMessagesToPruneAtOnce` configuration parameter. We will never prune +more than this number of messages in the single transaction. That said, the value should not be too +big to avoid waste of resources when there are no messages to prune. + +To be able to reward the relayer for delivering messages, we store a map of message nonces range => +identifier of the relayer that has delivered this range at the target chain runtime storage. If a +relayer delivers multiple consequent ranges, they're merged into single entry. So there may be more +than one entry for the same relayer. Eventually, this whole map must be delivered back to the source +chain to confirm delivery and pay rewards. So to make sure we are able to craft this confirmation +transaction, we need to: (1) keep the size of this map below a certain limit and (2) make sure that +the weight of processing this map is below a certain limit. Both size and processing weight mostly +depend on the number of entries. The number of entries is limited with the +`pallet_bridge_messages::ConfigMaxUnrewardedRelayerEntriesAtInboundLane` parameter. Processing weight +also depends on the total number of messages that are being confirmed, because every confirmed +message needs to be read. So there's another +`pallet_bridge_messages::Config::MaxUnconfirmedMessagesAtInboundLane` parameter for that. + +When choosing values for these parameters, you must also keep in mind that if proof in your scheme +is based on finality of headers (and it is the most obvious option for Substrate-based chains with +finality notion), then choosing too small values for these parameters may cause significant delays +in message delivery. That's because there are too many actors involved in this scheme: 1) authorities +that are finalizing headers of the target chain need to finalize header with non-empty map; 2) the +headers relayer then needs to submit this header and its finality proof to the source chain; 3) the +messages relayer must then send confirmation transaction (storage proof of this map) to the source +chain; 4) when the confirmation transaction will be mined at some header, source chain authorities +must finalize this header; 5) the headers relay then needs to submit this header and its finality +proof to the target chain; 6) only now the messages relayer may submit new messages from the source +to target chain and prune the entry from the map. + +Delivery transaction requires the relayer to provide both number of entries and total number of +messages in the map. This means that the module never charges an extra cost for delivering a map - +the relayer would need to pay exactly for the number of entries+messages it has delivered. So the +best guess for values of these parameters would be the pair that would occupy `N` percent of the +maximal transaction size and weight of the source chain. The `N` should be large enough to process +large maps, at the same time keeping reserve for future source chain upgrades. + +## Non-Essential Functionality + +Apart from the message related calls, the module exposes a set of auxiliary calls. They fall in two +groups, described in the next two paragraphs. + +There may be a special account in every runtime where the messages module is deployed. This +account, named 'module owner', is like a module-level sudo account - he's able to halt all and +result all module operations without requiring runtime upgrade. The module may have no message +owner, but we suggest to use it at least for initial deployment. To calls that are related to this +account are: +- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account; +- `fn halt_operations()`: the module owner (or sudo account) may call this function to stop all + module operations. After this call, all message-related transactions will be rejected until + further `resume_operations` call'. This call may be used when something extraordinary happens with + the bridge; +- `fn resume_operations()`: module owner may call this function to resume bridge operations. The + module will resume its regular operations after this call. + +Apart from halting and resuming the bridge, the module owner may also tune module configuration +parameters without runtime upgrades. The set of parameters needs to be designed in advance, though. +The module configuration trait has associated `Parameter` type, which may be e.g. enum and represent +a set of parameters that may be updated by the module owner. For example, if your bridge needs to +convert sums between different tokens, you may define a 'conversion rate' parameter and let the +module owner update this parameter when there are significant changes in the rate. The corresponding +module call is `fn update_pallet_parameter()`. + +## Weights of Module Extrinsics + +The main assumptions behind weight formulas is: +- all possible costs are paid in advance by the message submitter; +- whenever possible, relayer tries to minimize cost of its transactions. So e.g. even though sender + always pays for delivering outbound lane state proof, relayer may not include it in the delivery + transaction (unless messages module on target chain requires that); +- weight formula should incentivize relayer to not to submit any redundant data in the extrinsics + arguments; +- the extrinsic shall never be executing slower (i.e. has larger actual weight) than defined by the + formula. + +### Weight of `send_message` call + +#### Related benchmarks + +| Benchmark | Description | +|-----------------------------------|-----------------------------------------------------| +`send_minimal_message_worst_case` | Sends 0-size message with worst possible conditions | +`send_1_kb_message_worst_case` | Sends 1KB-size message with worst possible conditions | +`send_16_kb_message_worst_case` | Sends 16KB-size message with worst possible conditions | + +#### Weight formula + +The weight formula is: +``` +Weight = BaseWeight + MessageSizeInKilobytes * MessageKiloByteSendWeight +``` + +Where: + +| Component | How it is computed? | Description | +|-----------------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| +| `SendMessageOverhead` | `send_minimal_message_worst_case` | Weight of sending minimal (0 bytes) message | +| `MessageKiloByteSendWeight` | `(send_16_kb_message_worst_case - send_1_kb_message_worst_case)/15` | Weight of sending every additional kilobyte of the message | + +### Weight of `receive_messages_proof` call + +#### Related benchmarks + +| Benchmark | Description* | +|---------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| +| `receive_single_message_proof` | Receives proof of single `EXPECTED_DEFAULT_MESSAGE_LENGTH` message | +| `receive_two_messages_proof` | Receives proof of two identical `EXPECTED_DEFAULT_MESSAGE_LENGTH` messages | +| `receive_single_message_proof_with_outbound_lane_state` | Receives proof of single `EXPECTED_DEFAULT_MESSAGE_LENGTH` message and proof of outbound lane state at the source chain | +| `receive_single_message_proof_1_kb` | Receives proof of single message. The proof has size of approximately 1KB** | +| `receive_single_message_proof_16_kb` | Receives proof of single message. The proof has size of approximately 16KB** | + +*\* - In all benchmarks all received messages are dispatched and their dispatch cost is near to zero* + +*\*\* - Trie leafs are assumed to have minimal values. The proof is derived from the minimal proof +by including more trie nodes. That's because according to our additioal benchmarks, increasing proof +by including more nodes has slightly larger impact on performance than increasing values stored in leafs*. + +#### Weight formula + +The weight formula is: +``` +Weight = BaseWeight + OutboundStateDeliveryWeight + + MessagesCount * MessageDeliveryWeight + + MessagesDispatchWeight + + Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight +``` + +Where: + +| Component | How it is computed? | Description | +|-------------------------------|------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `BaseWeight` | `2*receive_single_message_proof - receive_two_messages_proof` | Weight of receiving and parsing minimal proof | +| `OutboundStateDeliveryWeight` | `receive_single_message_proof_with_outbound_lane_state - receive_single_message_proof` | Additional weight when proof includes outbound lane state | +| `MessageDeliveryWeight` | `receive_two_messages_proof - receive_single_message_proof` | Weight of of parsing and dispatching (without actual dispatch cost) of every message | +| `MessagesCount` | | Provided by relayer | +| `MessagesDispatchWeight` | | Provided by relayer | +| `ActualProofSize` | | Provided by relayer | +| `ExpectedProofSize` | `EXPECTED_DEFAULT_MESSAGE_LENGTH * MessagesCount + EXTRA_STORAGE_PROOF_SIZE` | Size of proof that we are expecting. This only includes `EXTRA_STORAGE_PROOF_SIZE` once, because we assume that intermediate nodes likely to be included in the proof only once. This may be wrong, but since weight of processing proof with many nodes is almost equal to processing proof with large leafs, additional cost will be covered because we're charging for extra proof bytes anyway | +| `ProofByteDeliveryWeight` | `(receive_single_message_proof_16_kb - receive_single_message_proof_1_kb) / (15 * 1024)` | Weight of processing every additional proof byte over `ExpectedProofSize` limit | + +#### Why for every message sent using `send_message` we will be able to craft `receive_messages_proof` transaction? + +We have following checks in `send_message` transaction on the source chain: +- message size should be less than or equal to `2/3` of maximal extrinsic size on the target chain; +- message dispatch weight should be less than or equal to the `1/2` of maximal extrinsic dispatch + weight on the target chain. + +Delivery transaction is an encoded delivery call and signed extensions. So we have `1/3` of maximal +extrinsic size reserved for: +- storage proof, excluding the message itself. Currently, on our test chains, the overhead is always + within `EXTRA_STORAGE_PROOF_SIZE` limits (1024 bytes); +- signed extras and other call arguments (`relayer_id: SourceChain::AccountId`, `messages_count: + u32`, `dispatch_weight: u64`). + +On Millau chain, maximal extrinsic size is `0.75 * 2MB`, so `1/3` is `512KB` (`524_288` bytes). This +should be enough to cover these extra arguments and signed extensions. + +Let's exclude message dispatch cost from single message delivery transaction weight formula: +``` +Weight = BaseWeight + OutboundStateDeliveryWeight + MessageDeliveryWeight + + Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight +``` + +So we have `1/2` of maximal extrinsic weight to cover these components. `BaseWeight`, +`OutboundStateDeliveryWeight` and `MessageDeliveryWeight` are determined using benchmarks and are +hardcoded into runtime. Adequate relayer would only include required trie nodes into the proof. So +if message size would be maximal (`2/3` of `MaximalExtrinsicSize`), then the extra proof size would +be `MaximalExtrinsicSize / 3 * 2 - EXPECTED_DEFAULT_MESSAGE_LENGTH`. + +Both conditions are verified by `pallet_bridge_messages::ensure_weights_are_correct` and +`pallet_bridge_messages::ensure_able_to_receive_messages` functions, which must be called from every +runtime's tests. + +#### Post-dispatch weight refunds of the `receive_messages_proof` call + +Weight formula of the `receive_messages_proof` call assumes that the dispatch fee of every message is +paid at the target chain (where call is executed), that every message will be dispatched and that +dispatch weight of the message will be exactly the weight that is returned from the +`MessageDispatch::dispatch_weight` method call. This isn't true for all messages, so the call returns +actual weight used to dispatch messages. + +This actual weight is the weight, returned by the weight formula, minus: +- the weight of undispatched messages, if we have failed to dispatch because of different issues; +- the unspent dispatch weight if the declared weight of some messages is less than their actual post-dispatch weight; +- the pay-dispatch-fee weight for every message that had dispatch fee paid at the source chain. + +The last component is computed as a difference between two benchmarks results - the `receive_single_message_proof` +benchmark (that assumes that the fee is paid during dispatch) and the `receive_single_prepaid_message_proof` +(that assumes that the dispatch fee is already paid). + +### Weight of `receive_messages_delivery_proof` call + +#### Related benchmarks + +| Benchmark | Description | +|-------------------------------------------------------------|------------------------------------------------------------------------------------------| +| `receive_delivery_proof_for_single_message` | Receives proof of single message delivery | +| `receive_delivery_proof_for_two_messages_by_single_relayer` | Receives proof of two messages delivery. Both messages are delivered by the same relayer | +| `receive_delivery_proof_for_two_messages_by_two_relayers` | Receives proof of two messages delivery. Messages are delivered by different relayers | + +#### Weight formula + +The weight formula is: +``` +Weight = BaseWeight + MessagesCount * MessageConfirmationWeight + + RelayersCount * RelayerRewardWeight + + Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight + + MessagesCount * (DbReadWeight + DbWriteWeight) +``` + +Where: + +| Component | How it is computed? | Description | +|---------------------------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `BaseWeight` | `2*receive_delivery_proof_for_single_message - receive_delivery_proof_for_two_messages_by_single_relayer` | Weight of receiving and parsing minimal delivery proof | +| `MessageDeliveryWeight` | `receive_delivery_proof_for_two_messages_by_single_relayer - receive_delivery_proof_for_single_message` | Weight of confirming every additional message | +| `MessagesCount` | | Provided by relayer | +| `RelayerRewardWeight` | `receive_delivery_proof_for_two_messages_by_two_relayers - receive_delivery_proof_for_two_messages_by_single_relayer` | Weight of rewarding every additional relayer | +| `RelayersCount` | | Provided by relayer | +| `ActualProofSize` | | Provided by relayer | +| `ExpectedProofSize` | `EXTRA_STORAGE_PROOF_SIZE` | Size of proof that we are expecting | +| `ProofByteDeliveryWeight` | `(receive_single_message_proof_16_kb - receive_single_message_proof_1_kb) / (15 * 1024)` | Weight of processing every additional proof byte over `ExpectedProofSize` limit. We're using the same formula, as for message delivery, because proof mechanism is assumed to be the same in both cases | + +#### Post-dispatch weight refunds of the `receive_messages_delivery_proof` call + +Weight formula of the `receive_messages_delivery_proof` call assumes that all messages in the proof +are actually delivered (so there are no already confirmed messages) and every messages is processed +by the `OnDeliveryConfirmed` callback. This means that for every message, we're adding single db read +weight and single db write weight. If, by some reason, messages are not processed by the +`OnDeliveryConfirmed` callback, or their processing is faster than that additional weight, the +difference is refunded to the submitter. + +#### Why we're always able to craft `receive_messages_delivery_proof` transaction? + +There can be at most `::MaxUnconfirmedMessagesAtInboundLane` +messages and at most +`::MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded +relayers in the single delivery confirmation transaction. + +We're checking that this transaction may be crafted in the +`pallet_bridge_messages::ensure_able_to_receive_confirmation` function, which must be called from every +runtime' tests. diff --git a/modules/messages/src/benchmarking.rs b/modules/messages/src/benchmarking.rs new file mode 100644 index 00000000000..b8360facacb --- /dev/null +++ b/modules/messages/src/benchmarking.rs @@ -0,0 +1,423 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Messages pallet benchmarking. + +use crate::{ + inbound_lane::InboundLaneStorage, inbound_lane_storage, outbound_lane, + weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH, Call, OutboundLanes, +}; + +use bp_messages::{ + source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, DeliveredMessages, + InboundLaneData, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer, + UnrewardedRelayersState, +}; +use bp_runtime::StorageProofSize; +use frame_benchmarking::{account, benchmarks_instance_pallet}; +use frame_support::weights::Weight; +use frame_system::RawOrigin; +use sp_std::{ops::RangeInclusive, prelude::*}; + +const SEED: u32 = 0; + +/// Pallet we're benchmarking here. +pub struct Pallet, I: 'static>(crate::Pallet); + +/// Benchmark-specific message proof parameters. +#[derive(Debug)] +pub struct MessageProofParams { + /// Id of the lane. + pub lane: LaneId, + /// Range of messages to include in the proof. + pub message_nonces: RangeInclusive, + /// If `Some`, the proof needs to include this outbound lane data. + pub outbound_lane_data: Option, + /// Proof size requirements. + pub size: StorageProofSize, +} + +/// Benchmark-specific message delivery proof parameters. +#[derive(Debug)] +pub struct MessageDeliveryProofParams { + /// Id of the lane. + pub lane: LaneId, + /// The proof needs to include this inbound lane data. + pub inbound_lane_data: InboundLaneData, + /// Proof size requirements. + pub size: StorageProofSize, +} + +/// Trait that must be implemented by runtime. +pub trait Config: crate::Config { + /// Lane id to use in benchmarks. + fn bench_lane_id() -> LaneId { + Default::default() + } + /// Return id of relayer account at the bridged chain. + fn bridged_relayer_id() -> Self::InboundRelayer; + /// Create given account and give it enough balance for test purposes. + fn endow_account(account: &Self::AccountId); + /// Prepare messages proof to receive by the module. + fn prepare_message_proof( + params: MessageProofParams, + ) -> (::MessagesProof, Weight); + /// Prepare messages delivery proof to receive by the module. + fn prepare_message_delivery_proof( + params: MessageDeliveryProofParams, + ) -> >::MessagesDeliveryProof; + /// Returns true if message has been dispatched (either successfully or not). + fn is_message_dispatched(nonce: MessageNonce) -> bool; +} + +benchmarks_instance_pallet! { + // + // Benchmarks that are used directly by the runtime. + // + + // Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions: + // * proof does not include outbound lane state proof; + // * inbound lane already has state, so it needs to be read and decoded; + // * message is successfully dispatched; + // * message requires all heavy checks done by dispatcher; + // * message dispatch fee is paid at target (this) chain. + // + // This is base benchmark for all other message delivery benchmarks. + receive_single_message_proof { + let relayer_id_on_source = T::bridged_relayer_id(); + let relayer_id_on_target = account("relayer", 0, SEED); + T::endow_account(&relayer_id_on_target); + + // mark messages 1..=20 as delivered + receive_messages::(20); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: T::bench_lane_id(), + message_nonces: 21..=21, + outbound_lane_data: None, + size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH), + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) + verify { + assert_eq!( + crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), + 21, + ); + assert!(T::is_message_dispatched(21)); + } + + // Benchmark `receive_messages_proof` extrinsic with two minimal-weight messages and following conditions: + // * proof does not include outbound lane state proof; + // * inbound lane already has state, so it needs to be read and decoded; + // * message is successfully dispatched; + // * message requires all heavy checks done by dispatcher; + // * message dispatch fee is paid at target (this) chain. + // + // The weight of single message delivery could be approximated as + // `weight(receive_two_messages_proof) - weight(receive_single_message_proof)`. + // This won't be super-accurate if message has non-zero dispatch weight, but estimation should + // be close enough to real weight. + receive_two_messages_proof { + let relayer_id_on_source = T::bridged_relayer_id(); + let relayer_id_on_target = account("relayer", 0, SEED); + T::endow_account(&relayer_id_on_target); + + // mark messages 1..=20 as delivered + receive_messages::(20); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: T::bench_lane_id(), + message_nonces: 21..=22, + outbound_lane_data: None, + size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH), + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 2, dispatch_weight) + verify { + assert_eq!( + crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), + 22, + ); + assert!(T::is_message_dispatched(22)); + } + + // Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions: + // * proof includes outbound lane state proof; + // * inbound lane already has state, so it needs to be read and decoded; + // * message is successfully dispatched; + // * message requires all heavy checks done by dispatcher; + // * message dispatch fee is paid at target (this) chain. + // + // The weight of outbound lane state delivery would be + // `weight(receive_single_message_proof_with_outbound_lane_state) - weight(receive_single_message_proof)`. + // This won't be super-accurate if message has non-zero dispatch weight, but estimation should + // be close enough to real weight. + receive_single_message_proof_with_outbound_lane_state { + let relayer_id_on_source = T::bridged_relayer_id(); + let relayer_id_on_target = account("relayer", 0, SEED); + T::endow_account(&relayer_id_on_target); + + // mark messages 1..=20 as delivered + receive_messages::(20); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: T::bench_lane_id(), + message_nonces: 21..=21, + outbound_lane_data: Some(OutboundLaneData { + oldest_unpruned_nonce: 21, + latest_received_nonce: 20, + latest_generated_nonce: 21, + }), + size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH), + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) + verify { + let lane_state = crate::InboundLanes::::get(&T::bench_lane_id()); + assert_eq!(lane_state.last_delivered_nonce(), 21); + assert_eq!(lane_state.last_confirmed_nonce, 20); + assert!(T::is_message_dispatched(21)); + } + + // Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions: + // * the proof has many redundand trie nodes with total size of approximately 1KB; + // * proof does not include outbound lane state proof; + // * inbound lane already has state, so it needs to be read and decoded; + // * message is successfully dispatched; + // * message requires all heavy checks done by dispatcher. + // + // With single KB of messages proof, the weight of the call is increased (roughly) by + // `(receive_single_message_proof_16KB - receive_single_message_proof_1_kb) / 15`. + receive_single_message_proof_1_kb { + let relayer_id_on_source = T::bridged_relayer_id(); + let relayer_id_on_target = account("relayer", 0, SEED); + T::endow_account(&relayer_id_on_target); + + // mark messages 1..=20 as delivered + receive_messages::(20); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: T::bench_lane_id(), + message_nonces: 21..=21, + outbound_lane_data: None, + size: StorageProofSize::HasExtraNodes(1024), + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) + verify { + assert_eq!( + crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), + 21, + ); + assert!(T::is_message_dispatched(21)); + } + + // Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions: + // * the proof has many redundand trie nodes with total size of approximately 16KB; + // * proof does not include outbound lane state proof; + // * inbound lane already has state, so it needs to be read and decoded; + // * message is successfully dispatched; + // * message requires all heavy checks done by dispatcher. + // + // Size of proof grows because it contains extra trie nodes in it. + // + // With single KB of messages proof, the weight of the call is increased (roughly) by + // `(receive_single_message_proof_16KB - receive_single_message_proof) / 15`. + receive_single_message_proof_16_kb { + let relayer_id_on_source = T::bridged_relayer_id(); + let relayer_id_on_target = account("relayer", 0, SEED); + T::endow_account(&relayer_id_on_target); + + // mark messages 1..=20 as delivered + receive_messages::(20); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: T::bench_lane_id(), + message_nonces: 21..=21, + outbound_lane_data: None, + size: StorageProofSize::HasExtraNodes(16 * 1024), + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) + verify { + assert_eq!( + crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), + 21, + ); + assert!(T::is_message_dispatched(21)); + } + + // Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions: + // * proof does not include outbound lane state proof; + // * inbound lane already has state, so it needs to be read and decoded; + // * message is successfully dispatched; + // * message requires all heavy checks done by dispatcher; + // * message dispatch fee is paid at source (bridged) chain. + // + // This benchmark is used to compute extra weight spent at target chain when fee is paid there. Then we use + // this information in two places: (1) to reduce weight of delivery tx if sender pays fee at the source chain + // and (2) to refund relayer with this weight if fee has been paid at the source chain. + receive_single_prepaid_message_proof { + let relayer_id_on_source = T::bridged_relayer_id(); + let relayer_id_on_target = account("relayer", 0, SEED); + T::endow_account(&relayer_id_on_target); + + // mark messages 1..=20 as delivered + receive_messages::(20); + + let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams { + lane: T::bench_lane_id(), + message_nonces: 21..=21, + outbound_lane_data: None, + size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH), + }); + }: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight) + verify { + assert_eq!( + crate::InboundLanes::::get(&T::bench_lane_id()).last_delivered_nonce(), + 21, + ); + assert!(T::is_message_dispatched(21)); + } + + // Benchmark `receive_messages_delivery_proof` extrinsic with following conditions: + // * single relayer is rewarded for relaying single message; + // * relayer account does not exist (in practice it needs to exist in production environment). + // + // This is base benchmark for all other confirmations delivery benchmarks. + receive_delivery_proof_for_single_message { + let relayer_id: T::AccountId = account("relayer", 0, SEED); + + // send message that we're going to confirm + send_regular_message::(); + + let relayers_state = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }; + let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { + lane: T::bench_lane_id(), + inbound_lane_data: InboundLaneData { + relayers: vec![UnrewardedRelayer { + relayer: relayer_id.clone(), + messages: DeliveredMessages::new(1), + }].into_iter().collect(), + last_confirmed_nonce: 0, + }, + size: StorageProofSize::Minimal(0), + }); + }: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state) + verify { + assert_eq!(OutboundLanes::::get(T::bench_lane_id()).latest_received_nonce, 1); + } + + // Benchmark `receive_messages_delivery_proof` extrinsic with following conditions: + // * single relayer is rewarded for relaying two messages; + // * relayer account does not exist (in practice it needs to exist in production environment). + // + // Additional weight for paying single-message reward to the same relayer could be computed + // as `weight(receive_delivery_proof_for_two_messages_by_single_relayer) + // - weight(receive_delivery_proof_for_single_message)`. + receive_delivery_proof_for_two_messages_by_single_relayer { + let relayer_id: T::AccountId = account("relayer", 0, SEED); + + // send message that we're going to confirm + send_regular_message::(); + send_regular_message::(); + + let relayers_state = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 2, + total_messages: 2, + last_delivered_nonce: 2, + }; + let mut delivered_messages = DeliveredMessages::new(1); + delivered_messages.note_dispatched_message(); + let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { + lane: T::bench_lane_id(), + inbound_lane_data: InboundLaneData { + relayers: vec![UnrewardedRelayer { + relayer: relayer_id.clone(), + messages: delivered_messages, + }].into_iter().collect(), + last_confirmed_nonce: 0, + }, + size: StorageProofSize::Minimal(0), + }); + }: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state) + verify { + assert_eq!(OutboundLanes::::get(T::bench_lane_id()).latest_received_nonce, 2); + } + + // Benchmark `receive_messages_delivery_proof` extrinsic with following conditions: + // * two relayers are rewarded for relaying single message each; + // * relayer account does not exist (in practice it needs to exist in production environment). + // + // Additional weight for paying reward to the next relayer could be computed + // as `weight(receive_delivery_proof_for_two_messages_by_two_relayers) + // - weight(receive_delivery_proof_for_two_messages_by_single_relayer)`. + receive_delivery_proof_for_two_messages_by_two_relayers { + let relayer1_id: T::AccountId = account("relayer1", 1, SEED); + let relayer2_id: T::AccountId = account("relayer2", 2, SEED); + + // send message that we're going to confirm + send_regular_message::(); + send_regular_message::(); + + let relayers_state = UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + messages_in_oldest_entry: 1, + total_messages: 2, + last_delivered_nonce: 2, + }; + let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams { + lane: T::bench_lane_id(), + inbound_lane_data: InboundLaneData { + relayers: vec![ + UnrewardedRelayer { + relayer: relayer1_id.clone(), + messages: DeliveredMessages::new(1), + }, + UnrewardedRelayer { + relayer: relayer2_id.clone(), + messages: DeliveredMessages::new(2), + }, + ].into_iter().collect(), + last_confirmed_nonce: 0, + }, + size: StorageProofSize::Minimal(0), + }); + }: receive_messages_delivery_proof(RawOrigin::Signed(relayer1_id.clone()), proof, relayers_state) + verify { + assert_eq!(OutboundLanes::::get(T::bench_lane_id()).latest_received_nonce, 2); + } +} + +fn send_regular_message, I: 'static>() { + let mut outbound_lane = outbound_lane::(T::bench_lane_id()); + outbound_lane.send_message(vec![]); +} + +fn receive_messages, I: 'static>(nonce: MessageNonce) { + let mut inbound_lane_storage = inbound_lane_storage::(T::bench_lane_id()); + inbound_lane_storage.set_data(InboundLaneData { + relayers: vec![UnrewardedRelayer { + relayer: T::bridged_relayer_id(), + messages: DeliveredMessages::new(nonce), + }] + .into_iter() + .collect(), + last_confirmed_nonce: 0, + }); +} diff --git a/modules/messages/src/inbound_lane.rs b/modules/messages/src/inbound_lane.rs new file mode 100644 index 00000000000..0797cbfb946 --- /dev/null +++ b/modules/messages/src/inbound_lane.rs @@ -0,0 +1,545 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Everything about incoming messages receival. + +use crate::Config; + +use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, + DeliveredMessages, InboundLaneData, LaneId, MessageKey, MessageNonce, OutboundLaneData, + ReceivalResult, UnrewardedRelayer, +}; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use frame_support::{traits::Get, RuntimeDebug}; +use scale_info::{Type, TypeInfo}; +use sp_std::prelude::PartialEq; + +/// Inbound lane storage. +pub trait InboundLaneStorage { + /// Id of relayer on source chain. + type Relayer: Clone + PartialEq; + + /// Lane id. + fn id(&self) -> LaneId; + /// Return maximal number of unrewarded relayer entries in inbound lane. + fn max_unrewarded_relayer_entries(&self) -> MessageNonce; + /// Return maximal number of unconfirmed messages in inbound lane. + fn max_unconfirmed_messages(&self) -> MessageNonce; + /// Get lane data from the storage. + fn data(&self) -> InboundLaneData; + /// Update lane data in the storage. + fn set_data(&mut self, data: InboundLaneData); +} + +/// Inbound lane data wrapper that implements `MaxEncodedLen`. +/// +/// We have already had `MaxEncodedLen`-like functionality before, but its usage has +/// been localized and we haven't been passing bounds (maximal count of unrewarded relayer entries, +/// maximal count of unconfirmed messages) everywhere. This wrapper allows us to avoid passing +/// these generic bounds all over the code. +/// +/// The encoding of this type matches encoding of the corresponding `MessageData`. +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)] +pub struct StoredInboundLaneData, I: 'static>(pub InboundLaneData); + +impl, I: 'static> sp_std::ops::Deref for StoredInboundLaneData { + type Target = InboundLaneData; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl, I: 'static> sp_std::ops::DerefMut for StoredInboundLaneData { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl, I: 'static> Default for StoredInboundLaneData { + fn default() -> Self { + StoredInboundLaneData(Default::default()) + } +} + +impl, I: 'static> From> + for InboundLaneData +{ + fn from(data: StoredInboundLaneData) -> Self { + data.0 + } +} + +impl, I: 'static> EncodeLike> + for InboundLaneData +{ +} + +impl, I: 'static> TypeInfo for StoredInboundLaneData { + type Identity = Self; + + fn type_info() -> Type { + InboundLaneData::::type_info() + } +} + +impl, I: 'static> MaxEncodedLen for StoredInboundLaneData { + fn max_encoded_len() -> usize { + InboundLaneData::::encoded_size_hint( + T::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize, + ) + .unwrap_or(usize::MAX) + } +} + +/// Inbound messages lane. +pub struct InboundLane { + storage: S, +} + +impl InboundLane { + /// Create new inbound lane backed by given storage. + pub fn new(storage: S) -> Self { + InboundLane { storage } + } + + /// Receive state of the corresponding outbound lane. + pub fn receive_state_update( + &mut self, + outbound_lane_data: OutboundLaneData, + ) -> Option { + let mut data = self.storage.data(); + let last_delivered_nonce = data.last_delivered_nonce(); + + if outbound_lane_data.latest_received_nonce > last_delivered_nonce { + // this is something that should never happen if proofs are correct + return None + } + if outbound_lane_data.latest_received_nonce <= data.last_confirmed_nonce { + return None + } + + let new_confirmed_nonce = outbound_lane_data.latest_received_nonce; + data.last_confirmed_nonce = new_confirmed_nonce; + // Firstly, remove all of the records where higher nonce <= new confirmed nonce + while data + .relayers + .front() + .map(|entry| entry.messages.end <= new_confirmed_nonce) + .unwrap_or(false) + { + data.relayers.pop_front(); + } + // Secondly, update the next record with lower nonce equal to new confirmed nonce if needed. + // Note: There will be max. 1 record to update as we don't allow messages from relayers to + // overlap. + match data.relayers.front_mut() { + Some(entry) if entry.messages.begin < new_confirmed_nonce => { + entry.messages.begin = new_confirmed_nonce + 1; + }, + _ => {}, + } + + self.storage.set_data(data); + Some(outbound_lane_data.latest_received_nonce) + } + + /// Receive new message. + pub fn receive_message, AccountId>( + &mut self, + relayer_at_bridged_chain: &S::Relayer, + relayer_at_this_chain: &AccountId, + nonce: MessageNonce, + message_data: DispatchMessageData, + ) -> ReceivalResult { + let mut data = self.storage.data(); + let is_correct_message = nonce == data.last_delivered_nonce() + 1; + if !is_correct_message { + return ReceivalResult::InvalidNonce + } + + // if there are more unrewarded relayer entries than we may accept, reject this message + if data.relayers.len() as MessageNonce >= self.storage.max_unrewarded_relayer_entries() { + return ReceivalResult::TooManyUnrewardedRelayers + } + + // if there are more unconfirmed messages than we may accept, reject this message + let unconfirmed_messages_count = nonce.saturating_sub(data.last_confirmed_nonce); + if unconfirmed_messages_count > self.storage.max_unconfirmed_messages() { + return ReceivalResult::TooManyUnconfirmedMessages + } + + // then, dispatch message + let dispatch_result = Dispatch::dispatch( + relayer_at_this_chain, + DispatchMessage { + key: MessageKey { lane_id: self.storage.id(), nonce }, + data: message_data, + }, + ); + + // now let's update inbound lane storage + let push_new = match data.relayers.back_mut() { + Some(entry) if entry.relayer == *relayer_at_bridged_chain => { + entry.messages.note_dispatched_message(); + false + }, + _ => true, + }; + if push_new { + data.relayers.push_back(UnrewardedRelayer { + relayer: (*relayer_at_bridged_chain).clone(), + messages: DeliveredMessages::new(nonce), + }); + } + self.storage.set_data(data); + + ReceivalResult::Dispatched(dispatch_result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + inbound_lane, + mock::{ + dispatch_result, inbound_message_data, run_test, unrewarded_relayer, + TestMessageDispatch, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_RELAYER_A, + TEST_RELAYER_B, TEST_RELAYER_C, + }, + RuntimeInboundLaneStorage, + }; + + fn receive_regular_message( + lane: &mut InboundLane>, + nonce: MessageNonce, + ) { + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_A, + &TEST_RELAYER_A, + nonce, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::Dispatched(dispatch_result(0)) + ); + } + + #[test] + fn receive_status_update_ignores_status_from_the_future() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + receive_regular_message(&mut lane, 1); + assert_eq!( + lane.receive_state_update(OutboundLaneData { + latest_received_nonce: 10, + ..Default::default() + }), + None, + ); + + assert_eq!(lane.storage.data().last_confirmed_nonce, 0); + }); + } + + #[test] + fn receive_status_update_ignores_obsolete_status() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + receive_regular_message(&mut lane, 1); + receive_regular_message(&mut lane, 2); + receive_regular_message(&mut lane, 3); + assert_eq!( + lane.receive_state_update(OutboundLaneData { + latest_received_nonce: 3, + ..Default::default() + }), + Some(3), + ); + assert_eq!(lane.storage.data().last_confirmed_nonce, 3); + + assert_eq!( + lane.receive_state_update(OutboundLaneData { + latest_received_nonce: 3, + ..Default::default() + }), + None, + ); + assert_eq!(lane.storage.data().last_confirmed_nonce, 3); + }); + } + + #[test] + fn receive_status_update_works() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + receive_regular_message(&mut lane, 1); + receive_regular_message(&mut lane, 2); + receive_regular_message(&mut lane, 3); + assert_eq!(lane.storage.data().last_confirmed_nonce, 0); + assert_eq!( + lane.storage.data().relayers, + vec![unrewarded_relayer(1, 3, TEST_RELAYER_A)] + ); + + assert_eq!( + lane.receive_state_update(OutboundLaneData { + latest_received_nonce: 2, + ..Default::default() + }), + Some(2), + ); + assert_eq!(lane.storage.data().last_confirmed_nonce, 2); + assert_eq!( + lane.storage.data().relayers, + vec![unrewarded_relayer(3, 3, TEST_RELAYER_A)] + ); + + assert_eq!( + lane.receive_state_update(OutboundLaneData { + latest_received_nonce: 3, + ..Default::default() + }), + Some(3), + ); + assert_eq!(lane.storage.data().last_confirmed_nonce, 3); + assert_eq!(lane.storage.data().relayers, vec![]); + }); + } + + #[test] + fn receive_status_update_works_with_batches_from_relayers() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + let mut seed_storage_data = lane.storage.data(); + // Prepare data + seed_storage_data.last_confirmed_nonce = 0; + seed_storage_data.relayers.push_back(unrewarded_relayer(1, 1, TEST_RELAYER_A)); + // Simulate messages batch (2, 3, 4) from relayer #2 + seed_storage_data.relayers.push_back(unrewarded_relayer(2, 4, TEST_RELAYER_B)); + seed_storage_data.relayers.push_back(unrewarded_relayer(5, 5, TEST_RELAYER_C)); + lane.storage.set_data(seed_storage_data); + // Check + assert_eq!( + lane.receive_state_update(OutboundLaneData { + latest_received_nonce: 3, + ..Default::default() + }), + Some(3), + ); + assert_eq!(lane.storage.data().last_confirmed_nonce, 3); + assert_eq!( + lane.storage.data().relayers, + vec![ + unrewarded_relayer(4, 4, TEST_RELAYER_B), + unrewarded_relayer(5, 5, TEST_RELAYER_C) + ] + ); + }); + } + + #[test] + fn fails_to_receive_message_with_incorrect_nonce() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_A, + &TEST_RELAYER_A, + 10, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::InvalidNonce + ); + assert_eq!(lane.storage.data().last_delivered_nonce(), 0); + }); + } + + #[test] + fn fails_to_receive_messages_above_unrewarded_relayer_entries_limit_per_lane() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + let max_nonce = + ::MaxUnrewardedRelayerEntriesAtInboundLane::get(); + for current_nonce in 1..max_nonce + 1 { + assert_eq!( + lane.receive_message::( + &(TEST_RELAYER_A + current_nonce), + &(TEST_RELAYER_A + current_nonce), + current_nonce, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::Dispatched(dispatch_result(0)) + ); + } + // Fails to dispatch new message from different than latest relayer. + assert_eq!( + lane.receive_message::( + &(TEST_RELAYER_A + max_nonce + 1), + &(TEST_RELAYER_A + max_nonce + 1), + max_nonce + 1, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::TooManyUnrewardedRelayers, + ); + // Fails to dispatch new messages from latest relayer. Prevents griefing attacks. + assert_eq!( + lane.receive_message::( + &(TEST_RELAYER_A + max_nonce), + &(TEST_RELAYER_A + max_nonce), + max_nonce + 1, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::TooManyUnrewardedRelayers, + ); + }); + } + + #[test] + fn fails_to_receive_messages_above_unconfirmed_messages_limit_per_lane() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + let max_nonce = ::MaxUnconfirmedMessagesAtInboundLane::get(); + for current_nonce in 1..=max_nonce { + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_A, + &TEST_RELAYER_A, + current_nonce, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::Dispatched(dispatch_result(0)) + ); + } + // Fails to dispatch new message from different than latest relayer. + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_B, + &TEST_RELAYER_B, + max_nonce + 1, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::TooManyUnconfirmedMessages, + ); + // Fails to dispatch new messages from latest relayer. + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_A, + &TEST_RELAYER_A, + max_nonce + 1, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::TooManyUnconfirmedMessages, + ); + }); + } + + #[test] + fn correctly_receives_following_messages_from_two_relayers_alternately() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_A, + &TEST_RELAYER_A, + 1, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::Dispatched(dispatch_result(0)) + ); + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_B, + &TEST_RELAYER_B, + 2, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::Dispatched(dispatch_result(0)) + ); + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_A, + &TEST_RELAYER_A, + 3, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::Dispatched(dispatch_result(0)) + ); + assert_eq!( + lane.storage.data().relayers, + vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B), + unrewarded_relayer(3, 3, TEST_RELAYER_A) + ] + ); + }); + } + + #[test] + fn rejects_same_message_from_two_different_relayers() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_A, + &TEST_RELAYER_A, + 1, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::Dispatched(dispatch_result(0)) + ); + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_B, + &TEST_RELAYER_B, + 1, + inbound_message_data(REGULAR_PAYLOAD) + ), + ReceivalResult::InvalidNonce, + ); + }); + } + + #[test] + fn correct_message_is_processed_instantly() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + receive_regular_message(&mut lane, 1); + assert_eq!(lane.storage.data().last_delivered_nonce(), 1); + }); + } + + #[test] + fn unspent_weight_is_returned_by_receive_message() { + run_test(|| { + let mut lane = inbound_lane::(TEST_LANE_ID); + let mut payload = REGULAR_PAYLOAD; + *payload.dispatch_result.unspent_weight.ref_time_mut() = 1; + assert_eq!( + lane.receive_message::( + &TEST_RELAYER_A, + &TEST_RELAYER_A, + 1, + inbound_message_data(payload) + ), + ReceivalResult::Dispatched(dispatch_result(1)) + ); + }); + } +} diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs new file mode 100644 index 00000000000..abe6ec67983 --- /dev/null +++ b/modules/messages/src/lib.rs @@ -0,0 +1,1928 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Runtime module that allows sending and receiving messages using lane concept: +//! +//! 1) the message is sent using `send_message()` call; +//! 2) every outbound message is assigned nonce; +//! 3) the messages are stored in the storage; +//! 4) external component (relay) delivers messages to bridged chain; +//! 5) messages are processed in order (ordered by assigned nonce); +//! 6) relay may send proof-of-delivery back to this chain. +//! +//! Once message is sent, its progress can be tracked by looking at module events. +//! The assigned nonce is reported using `MessageAccepted` event. When message is +//! delivered to the the bridged chain, it is reported using `MessagesDelivered` event. +//! +//! **IMPORTANT NOTE**: after generating weights (custom `WeighInfo` implementation) for +//! your runtime (where this module is plugged to), please add test for these weights. +//! The test should call the `ensure_weights_are_correct` function from this module. +//! If this test fails with your weights, then either weights are computed incorrectly, +//! or some benchmarks assumptions are broken for your runtime. + +#![cfg_attr(not(feature = "std"), no_std)] +// Generated by `decl_event!` +#![allow(clippy::unused_unit)] + +pub use inbound_lane::StoredInboundLaneData; +pub use outbound_lane::StoredMessagePayload; +pub use weights::WeightInfo; +pub use weights_ext::{ + ensure_able_to_receive_confirmation, ensure_able_to_receive_message, + ensure_weights_are_correct, WeightInfoExt, EXPECTED_DEFAULT_MESSAGE_LENGTH, +}; + +use crate::{ + inbound_lane::{InboundLane, InboundLaneStorage}, + outbound_lane::{OutboundLane, OutboundLaneStorage, ReceivalConfirmationResult}, +}; + +use bp_messages::{ + source_chain::{ + LaneMessageVerifier, MessageDeliveryAndDispatchPayment, RelayersRewards, + SendMessageArtifacts, TargetHeaderChain, + }, + target_chain::{ + DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, + }, + total_unrewarded_messages, DeliveredMessages, InboundLaneData, InboundMessageDetails, LaneId, + MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, + OutboundMessageDetails, UnrewardedRelayer, UnrewardedRelayersState, +}; +use bp_runtime::{BasicOperatingMode, ChainId, OwnedBridgeModule, Size}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get}; +use sp_runtime::traits::UniqueSaturatedFrom; +use sp_std::{ + cell::RefCell, collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive, + prelude::*, +}; + +mod inbound_lane; +mod outbound_lane; +mod weights_ext; + +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +#[cfg(test)] +mod mock; + +pub use pallet::*; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-messages"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use bp_messages::{ReceivalResult, ReceivedMessages}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + // General types + + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + /// Benchmarks results from runtime we're plugged into. + type WeightInfo: WeightInfoExt; + + /// Gets the chain id value from the instance. + #[pallet::constant] + type BridgedChainId: Get; + + /// Get all active outbound lanes that the message pallet is serving. + type ActiveOutboundLanes: Get<&'static [LaneId]>; + /// Maximal number of unrewarded relayer entries at inbound lane. Unrewarded means that the + /// relayer has delivered messages, but either confirmations haven't been delivered back to + /// the source chain, or we haven't received reward confirmations yet. + /// + /// This constant limits maximal number of entries in the `InboundLaneData::relayers`. Keep + /// in mind that the same relayer account may take several (non-consecutive) entries in this + /// set. + type MaxUnrewardedRelayerEntriesAtInboundLane: Get; + /// Maximal number of unconfirmed messages at inbound lane. Unconfirmed means that the + /// message has been delivered, but either confirmations haven't been delivered back to the + /// source chain, or we haven't received reward confirmations for these messages yet. + /// + /// This constant limits difference between last message from last entry of the + /// `InboundLaneData::relayers` and first message at the first entry. + /// + /// There is no point of making this parameter lesser than + /// MaxUnrewardedRelayerEntriesAtInboundLane, because then maximal number of relayer entries + /// will be limited by maximal number of messages. + /// + /// This value also represents maximal number of messages in single delivery transaction. + /// Transaction that is declaring more messages than this value, will be rejected. Even if + /// these messages are from different lanes. + type MaxUnconfirmedMessagesAtInboundLane: Get; + + /// Maximal encoded size of the outbound payload. + #[pallet::constant] + type MaximalOutboundPayloadSize: Get; + /// Payload type of outbound messages. This payload is dispatched on the bridged chain. + type OutboundPayload: Parameter + Size; + + /// Payload type of inbound messages. This payload is dispatched on this chain. + type InboundPayload: Decode; + /// Identifier of relayer that deliver messages to this chain. Relayer reward is paid on the + /// bridged chain. + type InboundRelayer: Parameter + MaxEncodedLen; + + // Types that are used by outbound_lane (on source chain). + + /// Target header chain. + type TargetHeaderChain: TargetHeaderChain; + /// Message payload verifier. + type LaneMessageVerifier: LaneMessageVerifier; + /// Message delivery payment. + type MessageDeliveryAndDispatchPayment: MessageDeliveryAndDispatchPayment< + Self::RuntimeOrigin, + Self::AccountId, + >; + + // Types that are used by inbound_lane (on target chain). + + /// Source header chain, as it is represented on target chain. + type SourceHeaderChain: SourceHeaderChain; + /// Message dispatch. + type MessageDispatch: MessageDispatch< + Self::AccountId, + DispatchPayload = Self::InboundPayload, + >; + } + + /// Shortcut to messages proof type for Config. + type MessagesProofOf = + <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof; + /// Shortcut to messages delivery proof type for Config. + type MessagesDeliveryProofOf = + <>::TargetHeaderChain as TargetHeaderChain< + >::OutboundPayload, + ::AccountId, + >>::MessagesDeliveryProof; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); + + impl, I: 'static> OwnedBridgeModule for Pallet { + const LOG_TARGET: &'static str = LOG_TARGET; + type OwnerStorage = PalletOwner; + type OperatingMode = MessagesOperatingMode; + type OperatingModeStorage = PalletOperatingMode; + } + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet + where + u32: TryFrom<::BlockNumber>, + { + fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { + // we'll need at least to read outbound lane state, kill a message and update lane state + let db_weight = T::DbWeight::get(); + if !remaining_weight.all_gte(db_weight.reads_writes(1, 2)) { + return Weight::zero() + } + + // messages from lane with index `i` in `ActiveOutboundLanes` are pruned when + // `System::block_number() % lanes.len() == i`. Otherwise we need to read lane states on + // every block, wasting the whole `remaining_weight` for nothing and causing starvation + // of the last lane pruning + let active_lanes = T::ActiveOutboundLanes::get(); + let active_lanes_len = (active_lanes.len() as u32).into(); + let active_lane_index = u32::unique_saturated_from( + frame_system::Pallet::::block_number() % active_lanes_len, + ); + let active_lane_id = active_lanes[active_lane_index as usize]; + + // first db read - outbound lane state + let mut active_lane = outbound_lane::(active_lane_id); + let mut used_weight = db_weight.reads(1); + // and here we'll have writes + used_weight += active_lane.prune_messages(db_weight, remaining_weight - used_weight); + + // we already checked we have enough `remaining_weight` to cover this `used_weight` + used_weight + } + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all/some pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: MessagesOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + + /// Receive messages proof from bridged chain. + /// + /// The weight of the call assumes that the transaction always brings outbound lane + /// state update. Because of that, the submitter (relayer) has no benefit of not including + /// this data in the transaction, so reward confirmations lags should be minimal. + #[pallet::weight(T::WeightInfo::receive_messages_proof_weight(proof, *messages_count, *dispatch_weight))] + pub fn receive_messages_proof( + origin: OriginFor, + relayer_id_at_bridged_chain: T::InboundRelayer, + proof: MessagesProofOf, + messages_count: u32, + dispatch_weight: Weight, + ) -> DispatchResultWithPostInfo { + Self::ensure_not_halted().map_err(Error::::BridgeModule)?; + let relayer_id_at_this_chain = ensure_signed(origin)?; + + // reject transactions that are declaring too many messages + ensure!( + MessageNonce::from(messages_count) <= T::MaxUnconfirmedMessagesAtInboundLane::get(), + Error::::TooManyMessagesInTheProof + ); + + // why do we need to know the weight of this (`receive_messages_proof`) call? Because + // we may want to return some funds for not-dispatching (or partially dispatching) some + // messages to the call origin (relayer). And this is done by returning actual weight + // from the call. But we only know dispatch weight of every messages. So to refund + // relayer because we have not dispatched Message, we need to: + // + // ActualWeight = DeclaredWeight - Message.DispatchWeight + // + // The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible + // to get pre-computed value (and it has been already computed by the executive). + let declared_weight = T::WeightInfo::receive_messages_proof_weight( + &proof, + messages_count, + dispatch_weight, + ); + let mut actual_weight = declared_weight; + + // verify messages proof && convert proof into messages + let messages = verify_and_decode_messages_proof::< + T::SourceHeaderChain, + T::InboundPayload, + >(proof, messages_count) + .map_err(|err| { + log::trace!(target: LOG_TARGET, "Rejecting invalid messages proof: {:?}", err,); + + Error::::InvalidMessagesProof + })?; + + // dispatch messages and (optionally) update lane(s) state(s) + let mut total_messages = 0; + let mut valid_messages = 0; + let mut messages_received_status = Vec::with_capacity(messages.len()); + let mut dispatch_weight_left = dispatch_weight; + for (lane_id, lane_data) in messages { + let mut lane = inbound_lane::(lane_id); + + if let Some(lane_state) = lane_data.lane_state { + let updated_latest_confirmed_nonce = lane.receive_state_update(lane_state); + if let Some(updated_latest_confirmed_nonce) = updated_latest_confirmed_nonce { + log::trace!( + target: LOG_TARGET, + "Received lane {:?} state update: latest_confirmed_nonce={}", + lane_id, + updated_latest_confirmed_nonce, + ); + } + } + + let mut lane_messages_received_status = + ReceivedMessages::new(lane_id, Vec::with_capacity(lane_data.messages.len())); + let mut is_lane_processing_stopped_no_weight_left = false; + + for mut message in lane_data.messages { + debug_assert_eq!(message.key.lane_id, lane_id); + total_messages += 1; + + if is_lane_processing_stopped_no_weight_left { + lane_messages_received_status + .push_skipped_for_not_enough_weight(message.key.nonce); + continue + } + + // ensure that relayer has declared enough weight for dispatching next message + // on this lane. We can't dispatch lane messages out-of-order, so if declared + // weight is not enough, let's move to next lane + let message_dispatch_weight = T::MessageDispatch::dispatch_weight(&mut message); + if message_dispatch_weight.any_gt(dispatch_weight_left) { + log::trace!( + target: LOG_TARGET, + "Cannot dispatch any more messages on lane {:?}. Weight: declared={}, left={}", + lane_id, + message_dispatch_weight, + dispatch_weight_left, + ); + lane_messages_received_status + .push_skipped_for_not_enough_weight(message.key.nonce); + is_lane_processing_stopped_no_weight_left = true; + continue + } + + let receival_result = lane.receive_message::( + &relayer_id_at_bridged_chain, + &relayer_id_at_this_chain, + message.key.nonce, + message.data, + ); + + // note that we're returning unspent weight to relayer even if message has been + // rejected by the lane. This allows relayers to submit spam transactions with + // e.g. the same set of already delivered messages over and over again, without + // losing funds for messages dispatch. But keep in mind that relayer pays base + // delivery transaction cost anyway. And base cost covers everything except + // dispatch, so we have a balance here. + let (unspent_weight, refund_pay_dispatch_fee) = match &receival_result { + ReceivalResult::Dispatched(dispatch_result) => { + valid_messages += 1; + ( + dispatch_result.unspent_weight, + !dispatch_result.dispatch_fee_paid_during_dispatch, + ) + }, + ReceivalResult::InvalidNonce | + ReceivalResult::TooManyUnrewardedRelayers | + ReceivalResult::TooManyUnconfirmedMessages => (message_dispatch_weight, true), + }; + lane_messages_received_status.push(message.key.nonce, receival_result); + + let unspent_weight = unspent_weight.min(message_dispatch_weight); + dispatch_weight_left -= message_dispatch_weight - unspent_weight; + actual_weight = actual_weight.saturating_sub(unspent_weight).saturating_sub( + // delivery call weight formula assumes that the fee is paid at + // this (target) chain. If the message is prepaid at the source + // chain, let's refund relayer with this extra cost. + if refund_pay_dispatch_fee { + T::WeightInfo::pay_inbound_dispatch_fee_overhead() + } else { + Weight::zero() + }, + ); + } + + messages_received_status.push(lane_messages_received_status); + } + + log::debug!( + target: LOG_TARGET, + "Received messages: total={}, valid={}. Weight used: {}/{}", + total_messages, + valid_messages, + actual_weight, + declared_weight, + ); + + Self::deposit_event(Event::MessagesReceived(messages_received_status)); + + Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) + } + + /// Receive messages delivery proof from bridged chain. + #[pallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight( + proof, + relayers_state, + ))] + pub fn receive_messages_delivery_proof( + origin: OriginFor, + proof: MessagesDeliveryProofOf, + relayers_state: UnrewardedRelayersState, + ) -> DispatchResult { + Self::ensure_not_halted().map_err(Error::::BridgeModule)?; + + let confirmation_relayer = ensure_signed(origin)?; + let (lane_id, lane_data) = T::TargetHeaderChain::verify_messages_delivery_proof(proof) + .map_err(|err| { + log::trace!( + target: LOG_TARGET, + "Rejecting invalid messages delivery proof: {:?}", + err, + ); + + Error::::InvalidMessagesDeliveryProof + })?; + + // verify that the relayer has declared correct `lane_data::relayers` state + // (we only care about total number of entries and messages, because this affects call + // weight) + ensure!( + total_unrewarded_messages(&lane_data.relayers).unwrap_or(MessageNonce::MAX) == + relayers_state.total_messages && + lane_data.relayers.len() as MessageNonce == + relayers_state.unrewarded_relayer_entries, + Error::::InvalidUnrewardedRelayersState + ); + // the `last_delivered_nonce` field may also be used by the signed extension. Even + // though providing wrong value isn't critical, let's also check it here. + ensure!( + lane_data.last_delivered_nonce() == relayers_state.last_delivered_nonce, + Error::::InvalidUnrewardedRelayersState + ); + + // mark messages as delivered + let mut lane = outbound_lane::(lane_id); + let last_delivered_nonce = lane_data.last_delivered_nonce(); + let confirmed_messages = match lane.confirm_delivery( + relayers_state.total_messages, + last_delivered_nonce, + &lane_data.relayers, + ) { + ReceivalConfirmationResult::ConfirmedMessages(confirmed_messages) => + Some(confirmed_messages), + ReceivalConfirmationResult::NoNewConfirmations => None, + ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected( + to_confirm_messages_count, + ) => { + log::trace!( + target: LOG_TARGET, + "Messages delivery proof contains too many messages to confirm: {} vs declared {}", + to_confirm_messages_count, + relayers_state.total_messages, + ); + + fail!(Error::::TryingToConfirmMoreMessagesThanExpected); + }, + error => { + log::trace!( + target: LOG_TARGET, + "Messages delivery proof contains invalid unrewarded relayers vec: {:?}", + error, + ); + + fail!(Error::::InvalidUnrewardedRelayers); + }, + }; + + if let Some(confirmed_messages) = confirmed_messages { + // emit 'delivered' event + let received_range = confirmed_messages.begin..=confirmed_messages.end; + Self::deposit_event(Event::MessagesDelivered { + lane_id, + messages: confirmed_messages, + }); + + // if some new messages have been confirmed, reward relayers + >::MessageDeliveryAndDispatchPayment::pay_relayers_rewards( + lane_id, + lane_data.relayers, + &confirmation_relayer, + &received_range, + ); + } + + log::trace!( + target: LOG_TARGET, + "Received messages delivery proof up to (and including) {} at lane {:?}", + last_delivered_nonce, + lane_id, + ); + + Ok(()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// Message has been accepted and is waiting to be delivered. + MessageAccepted { lane_id: LaneId, nonce: MessageNonce }, + /// Messages have been received from the bridged chain. + MessagesReceived(Vec>), + /// Messages in the inclusive range have been delivered to the bridged chain. + MessagesDelivered { lane_id: LaneId, messages: DeliveredMessages }, + } + + #[pallet::error] + pub enum Error { + /// Pallet is not in Normal operating mode. + NotOperatingNormally, + /// The outbound lane is inactive. + InactiveOutboundLane, + /// The message is too large to be sent over the bridge. + MessageIsTooLarge, + /// Message has been treated as invalid by chain verifier. + MessageRejectedByChainVerifier, + /// Message has been treated as invalid by lane verifier. + MessageRejectedByLaneVerifier, + /// Submitter has failed to pay fee for delivering and dispatching messages. + FailedToWithdrawMessageFee, + /// The transaction brings too many messages. + TooManyMessagesInTheProof, + /// Invalid messages has been submitted. + InvalidMessagesProof, + /// Invalid messages delivery proof has been submitted. + InvalidMessagesDeliveryProof, + /// The bridged chain has invalid `UnrewardedRelayers` in its storage (fatal for the lane). + InvalidUnrewardedRelayers, + /// The relayer has declared invalid unrewarded relayers state in the + /// `receive_messages_delivery_proof` call. + InvalidUnrewardedRelayersState, + /// The message someone is trying to work with (i.e. increase fee) is already-delivered. + MessageIsAlreadyDelivered, + /// The message someone is trying to work with (i.e. increase fee) is not yet sent. + MessageIsNotYetSent, + /// The number of actually confirmed messages is going to be larger than the number of + /// messages in the proof. This may mean that this or bridged chain storage is corrupted. + TryingToConfirmMoreMessagesThanExpected, + /// Error generated by the `OwnedBridgeModule` trait. + BridgeModule(bp_runtime::OwnedBridgeModuleError), + } + + /// Optional pallet owner. + /// + /// Pallet owner has a right to halt all pallet operations and then resume it. If it is + /// `None`, then there are no direct ways to halt/resume pallet operations, but other + /// runtime methods may still be used to do that (i.e. democracy::referendum to update halt + /// flag directly or call the `halt_operations`). + #[pallet::storage] + #[pallet::getter(fn module_owner)] + pub type PalletOwner, I: 'static = ()> = StorageValue<_, T::AccountId>; + + /// The current operating mode of the pallet. + /// + /// Depending on the mode either all, some, or no transactions will be allowed. + #[pallet::storage] + #[pallet::getter(fn operating_mode)] + pub type PalletOperatingMode, I: 'static = ()> = + StorageValue<_, MessagesOperatingMode, ValueQuery>; + + /// Map of lane id => inbound lane data. + #[pallet::storage] + pub type InboundLanes, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, LaneId, StoredInboundLaneData, ValueQuery>; + + /// Map of lane id => outbound lane data. + #[pallet::storage] + pub type OutboundLanes, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, LaneId, OutboundLaneData, ValueQuery>; + + /// All queued outbound messages. + #[pallet::storage] + pub type OutboundMessages, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, MessageKey, StoredMessagePayload>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + /// Initial pallet operating mode. + pub operating_mode: MessagesOperatingMode, + /// Initial pallet owner. + pub owner: Option, + /// Dummy marker. + pub phantom: sp_std::marker::PhantomData, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + operating_mode: Default::default(), + owner: Default::default(), + phantom: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + PalletOperatingMode::::put(self.operating_mode); + if let Some(ref owner) = self.owner { + PalletOwner::::put(owner); + } + } + } + + impl, I: 'static> Pallet { + /// Get stored data of the outbound message with given nonce. + pub fn outbound_message_data(lane: LaneId, nonce: MessageNonce) -> Option { + OutboundMessages::::get(MessageKey { lane_id: lane, nonce }).map(Into::into) + } + + /// Prepare data, related to given inbound message. + pub fn inbound_message_data( + lane: LaneId, + payload: MessagePayload, + outbound_details: OutboundMessageDetails, + ) -> InboundMessageDetails { + let mut dispatch_message = DispatchMessage { + key: MessageKey { lane_id: lane, nonce: outbound_details.nonce }, + data: payload.into(), + }; + InboundMessageDetails { + dispatch_weight: T::MessageDispatch::dispatch_weight(&mut dispatch_message), + } + } + } +} + +impl bp_messages::source_chain::MessagesBridge + for Pallet +where + T: Config, + I: 'static, +{ + type Error = sp_runtime::DispatchErrorWithPostInfo; + + fn send_message( + sender: T::RuntimeOrigin, + lane: LaneId, + message: T::OutboundPayload, + ) -> Result { + crate::send_message::(sender, lane, message) + } +} + +/// Function that actually sends message. +fn send_message, I: 'static>( + submitter: T::RuntimeOrigin, + lane_id: LaneId, + payload: T::OutboundPayload, +) -> sp_std::result::Result< + SendMessageArtifacts, + sp_runtime::DispatchErrorWithPostInfo, +> { + ensure_normal_operating_mode::()?; + + // let's check if outbound lane is active + ensure!(T::ActiveOutboundLanes::get().contains(&lane_id), Error::::InactiveOutboundLane,); + + // let's first check if message can be delivered to target chain + T::TargetHeaderChain::verify_message(&payload).map_err(|err| { + log::trace!( + target: LOG_TARGET, + "Message to lane {:?} is rejected by target chain: {:?}", + lane_id, + err, + ); + + Error::::MessageRejectedByChainVerifier + })?; + + // now let's enforce any additional lane rules + let mut lane = outbound_lane::(lane_id); + T::LaneMessageVerifier::verify_message(&submitter, &lane_id, &lane.data(), &payload).map_err( + |err| { + log::trace!( + target: LOG_TARGET, + "Message to lane {:?} is rejected by lane verifier: {:?}", + lane_id, + err, + ); + + Error::::MessageRejectedByLaneVerifier + }, + )?; + + // finally, save message in outbound storage and emit event + let encoded_payload = payload.encode(); + let encoded_payload_len = encoded_payload.len(); + ensure!( + encoded_payload_len <= T::MaximalOutboundPayloadSize::get() as usize, + Error::::MessageIsTooLarge + ); + let nonce = lane.send_message(encoded_payload); + + log::trace!( + target: LOG_TARGET, + "Accepted message {} to lane {:?}. Message size: {:?}", + nonce, + lane_id, + encoded_payload_len, + ); + + Pallet::::deposit_event(Event::MessageAccepted { lane_id, nonce }); + + // we may introduce benchmarks for that, but no heavy ops planned here apart from + // db reads and writes. There are currently 2 db reads and 2 db writes: + // - one db read for operation mode check (`ensure_normal_operating_mode`); + // - one db read for outbound lane state (`outbound_lane`); + // - one db write for outbound lane state (`send_message`); + // - one db write for the message (`send_message`); + let actual_weight = T::DbWeight::get().reads_writes(2, 2); + + Ok(SendMessageArtifacts { nonce, weight: actual_weight }) +} + +/// Calculate the number of messages that the relayers have delivered. +pub fn calc_relayers_rewards( + messages_relayers: VecDeque>, + received_range: &RangeInclusive, +) -> RelayersRewards +where + T: frame_system::Config + crate::Config, + I: 'static, +{ + // remember to reward relayers that have delivered messages + // this loop is bounded by `T::MaxUnrewardedRelayerEntriesAtInboundLane` on the bridged chain + let mut relayers_rewards = RelayersRewards::new(); + for entry in messages_relayers { + let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start()); + let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end()); + + // loop won't proceed if current entry is ahead of received range (begin > end). + // this loop is bound by `T::MaxUnconfirmedMessagesAtInboundLane` on the bridged chain + if nonce_end >= nonce_begin { + *relayers_rewards.entry(entry.relayer).or_default() += nonce_end - nonce_begin + 1; + } + } + relayers_rewards +} + +/// Ensure that the pallet is in normal operational mode. +fn ensure_normal_operating_mode, I: 'static>() -> Result<(), Error> { + if PalletOperatingMode::::get() == + MessagesOperatingMode::Basic(BasicOperatingMode::Normal) + { + return Ok(()) + } + + Err(Error::::NotOperatingNormally) +} + +/// Creates new inbound lane object, backed by runtime storage. +fn inbound_lane, I: 'static>( + lane_id: LaneId, +) -> InboundLane> { + InboundLane::new(inbound_lane_storage::(lane_id)) +} + +/// Creates new runtime inbound lane storage. +fn inbound_lane_storage, I: 'static>( + lane_id: LaneId, +) -> RuntimeInboundLaneStorage { + RuntimeInboundLaneStorage { + lane_id, + cached_data: RefCell::new(None), + _phantom: Default::default(), + } +} + +/// Creates new outbound lane object, backed by runtime storage. +fn outbound_lane, I: 'static>( + lane_id: LaneId, +) -> OutboundLane> { + OutboundLane::new(RuntimeOutboundLaneStorage { lane_id, _phantom: Default::default() }) +} + +/// Runtime inbound lane storage. +struct RuntimeInboundLaneStorage, I: 'static = ()> { + lane_id: LaneId, + cached_data: RefCell>>, + _phantom: PhantomData, +} + +impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage { + type Relayer = T::InboundRelayer; + + fn id(&self) -> LaneId { + self.lane_id + } + + fn max_unrewarded_relayer_entries(&self) -> MessageNonce { + T::MaxUnrewardedRelayerEntriesAtInboundLane::get() + } + + fn max_unconfirmed_messages(&self) -> MessageNonce { + T::MaxUnconfirmedMessagesAtInboundLane::get() + } + + fn data(&self) -> InboundLaneData { + match self.cached_data.clone().into_inner() { + Some(data) => data, + None => { + let data: InboundLaneData = + InboundLanes::::get(self.lane_id).into(); + *self.cached_data.try_borrow_mut().expect( + "we're in the single-threaded environment;\ + we have no recursive borrows; qed", + ) = Some(data.clone()); + data + }, + } + } + + fn set_data(&mut self, data: InboundLaneData) { + *self.cached_data.try_borrow_mut().expect( + "we're in the single-threaded environment;\ + we have no recursive borrows; qed", + ) = Some(data.clone()); + InboundLanes::::insert(self.lane_id, StoredInboundLaneData::(data)) + } +} + +/// Runtime outbound lane storage. +struct RuntimeOutboundLaneStorage { + lane_id: LaneId, + _phantom: PhantomData<(T, I)>, +} + +impl, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage { + fn id(&self) -> LaneId { + self.lane_id + } + + fn data(&self) -> OutboundLaneData { + OutboundLanes::::get(self.lane_id) + } + + fn set_data(&mut self, data: OutboundLaneData) { + OutboundLanes::::insert(self.lane_id, data) + } + + #[cfg(test)] + fn message(&self, nonce: &MessageNonce) -> Option { + OutboundMessages::::get(MessageKey { lane_id: self.lane_id, nonce: *nonce }) + .map(Into::into) + } + + fn save_message(&mut self, nonce: MessageNonce, message_payload: MessagePayload) { + OutboundMessages::::insert( + MessageKey { lane_id: self.lane_id, nonce }, + StoredMessagePayload::::try_from(message_payload).expect( + "save_message is called after all checks in send_message; \ + send_message checks message size; \ + qed", + ), + ); + } + + fn remove_message(&mut self, nonce: &MessageNonce) { + OutboundMessages::::remove(MessageKey { lane_id: self.lane_id, nonce: *nonce }); + } +} + +/// Verify messages proof and return proved messages with decoded payload. +fn verify_and_decode_messages_proof( + proof: Chain::MessagesProof, + messages_count: u32, +) -> Result>, Chain::Error> { + // `receive_messages_proof` weight formula and `MaxUnconfirmedMessagesAtInboundLane` check + // guarantees that the `message_count` is sane and Vec may be allocated. + // (tx with too many messages will either be rejected from the pool, or will fail earlier) + Chain::verify_messages_proof(proof, messages_count).map(|messages_by_lane| { + messages_by_lane + .into_iter() + .map(|(lane, lane_data)| { + ( + lane, + ProvedLaneMessages { + lane_state: lane_data.lane_state, + messages: lane_data.messages.into_iter().map(Into::into).collect(), + }, + ) + }) + .collect() + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{ + message, message_payload, run_test, unrewarded_relayer, DbWeight, + RuntimeEvent as TestEvent, RuntimeOrigin, TestMessageDeliveryAndDispatchPayment, + TestMessagesDeliveryProof, TestMessagesProof, TestRuntime, MAX_OUTBOUND_PAYLOAD_SIZE, + PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_LANE_ID_2, + TEST_LANE_ID_3, TEST_RELAYER_A, TEST_RELAYER_B, + }; + use bp_messages::{UnrewardedRelayer, UnrewardedRelayersState}; + use bp_test_utils::generate_owned_bridge_module_tests; + use frame_support::{ + assert_noop, assert_ok, + storage::generator::{StorageMap, StorageValue}, + traits::Hooks, + weights::Weight, + }; + use frame_system::{EventRecord, Pallet as System, Phase}; + use sp_runtime::DispatchError; + + fn get_ready_for_events() { + System::::set_block_number(1); + System::::reset_events(); + } + + fn inbound_unrewarded_relayers_state( + lane: bp_messages::LaneId, + ) -> bp_messages::UnrewardedRelayersState { + let inbound_lane_data = InboundLanes::::get(lane).0; + let last_delivered_nonce = inbound_lane_data.last_delivered_nonce(); + let relayers = inbound_lane_data.relayers; + bp_messages::UnrewardedRelayersState { + unrewarded_relayer_entries: relayers.len() as _, + messages_in_oldest_entry: relayers + .front() + .map(|entry| 1 + entry.messages.end - entry.messages.begin) + .unwrap_or(0), + total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX), + last_delivered_nonce, + } + } + + fn send_regular_message() -> Weight { + get_ready_for_events(); + + let message_nonce = + outbound_lane::(TEST_LANE_ID).data().latest_generated_nonce + 1; + let weight = send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + REGULAR_PAYLOAD, + ) + .expect("send_message has failed") + .weight; + + // check event with assigned nonce + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Messages(Event::MessageAccepted { + lane_id: TEST_LANE_ID, + nonce: message_nonce + }), + topics: vec![], + }], + ); + + weight + } + + fn receive_messages_delivery_proof() { + System::::set_block_number(1); + System::::reset_events(); + + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![UnrewardedRelayer { + relayer: 0, + messages: DeliveredMessages::new(1), + }] + .into_iter() + .collect(), + }, + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + last_delivered_nonce: 1, + ..Default::default() + }, + )); + + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Messages(Event::MessagesDelivered { + lane_id: TEST_LANE_ID, + messages: DeliveredMessages::new(1), + }), + topics: vec![], + }], + ); + } + + #[test] + fn pallet_rejects_transactions_if_halted() { + run_test(|| { + // send message first to be able to check that delivery_proof fails later + send_regular_message(); + + PalletOperatingMode::::put(MessagesOperatingMode::Basic( + BasicOperatingMode::Halted, + )); + + assert_noop!( + send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + REGULAR_PAYLOAD, + ), + Error::::NotOperatingNormally, + ); + + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Ok(vec![message(2, REGULAR_PAYLOAD)]).into(), + 1, + REGULAR_PAYLOAD.declared_weight, + ), + Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), + ); + + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] + .into_iter() + .collect(), + }, + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }, + ), + Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), + ); + }); + } + + #[test] + fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() { + run_test(|| { + // send message first to be able to check that delivery_proof fails later + send_regular_message(); + + PalletOperatingMode::::put( + MessagesOperatingMode::RejectingOutboundMessages, + ); + + assert_noop!( + send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + REGULAR_PAYLOAD, + ), + Error::::NotOperatingNormally, + ); + + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), + 1, + REGULAR_PAYLOAD.declared_weight, + ),); + + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] + .into_iter() + .collect(), + }, + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }, + )); + }); + } + + #[test] + fn send_message_works() { + run_test(|| { + send_regular_message(); + }); + } + + #[test] + fn send_message_rejects_too_large_message() { + run_test(|| { + let mut message_payload = message_payload(1, 0); + // the payload isn't simply extra, so it'll definitely overflow + // `MAX_OUTBOUND_PAYLOAD_SIZE` if we add `MAX_OUTBOUND_PAYLOAD_SIZE` bytes to extra + message_payload + .extra + .extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]); + assert_noop!( + send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + message_payload.clone(), + ), + Error::::MessageIsTooLarge, + ); + + // let's check that we're able to send `MAX_OUTBOUND_PAYLOAD_SIZE` messages + while message_payload.encoded_size() as u32 > MAX_OUTBOUND_PAYLOAD_SIZE { + message_payload.extra.pop(); + } + assert_eq!(message_payload.encoded_size() as u32, MAX_OUTBOUND_PAYLOAD_SIZE); + assert_ok!(send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + message_payload, + ),); + }) + } + + #[test] + fn chain_verifier_rejects_invalid_message_in_send_message() { + run_test(|| { + // messages with this payload are rejected by target chain verifier + assert_noop!( + send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + PAYLOAD_REJECTED_BY_TARGET_CHAIN, + ), + Error::::MessageRejectedByChainVerifier, + ); + }); + } + + #[test] + fn lane_verifier_rejects_invalid_message_in_send_message() { + run_test(|| { + // messages with zero fee are rejected by lane verifier + let mut message = REGULAR_PAYLOAD; + message.reject_by_lane_verifier = true; + assert_noop!( + send_message::(RuntimeOrigin::signed(1), TEST_LANE_ID, message,), + Error::::MessageRejectedByLaneVerifier, + ); + }); + } + + #[test] + fn receive_messages_proof_works() { + run_test(|| { + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), + 1, + REGULAR_PAYLOAD.declared_weight, + )); + + assert_eq!(InboundLanes::::get(TEST_LANE_ID).0.last_delivered_nonce(), 1); + }); + } + + #[test] + fn receive_messages_proof_updates_confirmed_message_nonce() { + run_test(|| { + // say we have received 10 messages && last confirmed message is 8 + InboundLanes::::insert( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 8, + relayers: vec![ + unrewarded_relayer(9, 9, TEST_RELAYER_A), + unrewarded_relayer(10, 10, TEST_RELAYER_B), + ] + .into_iter() + .collect(), + }, + ); + assert_eq!( + inbound_unrewarded_relayers_state(TEST_LANE_ID), + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + messages_in_oldest_entry: 1, + total_messages: 2, + last_delivered_nonce: 10, + }, + ); + + // message proof includes outbound lane state with latest confirmed message updated to 9 + let mut message_proof: TestMessagesProof = + Ok(vec![message(11, REGULAR_PAYLOAD)]).into(); + message_proof.result.as_mut().unwrap()[0].1.lane_state = + Some(OutboundLaneData { latest_received_nonce: 9, ..Default::default() }); + + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + message_proof, + 1, + REGULAR_PAYLOAD.declared_weight, + )); + + assert_eq!( + InboundLanes::::get(TEST_LANE_ID).0, + InboundLaneData { + last_confirmed_nonce: 9, + relayers: vec![ + unrewarded_relayer(10, 10, TEST_RELAYER_B), + unrewarded_relayer(11, 11, TEST_RELAYER_A) + ] + .into_iter() + .collect(), + }, + ); + assert_eq!( + inbound_unrewarded_relayers_state(TEST_LANE_ID), + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + messages_in_oldest_entry: 1, + total_messages: 2, + last_delivered_nonce: 11, + }, + ); + }); + } + + #[test] + fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() { + run_test(|| { + let mut declared_weight = REGULAR_PAYLOAD.declared_weight; + *declared_weight.ref_time_mut() -= 1; + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), + 1, + declared_weight, + )); + assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 0); + }); + } + + #[test] + fn receive_messages_proof_rejects_invalid_proof() { + run_test(|| { + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Err(()).into(), + 1, + Weight::from_ref_time(0), + ), + Error::::InvalidMessagesProof, + ); + }); + } + + #[test] + fn receive_messages_proof_rejects_proof_with_too_many_messages() { + run_test(|| { + assert_noop!( + Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Ok(vec![message(1, REGULAR_PAYLOAD)]).into(), + u32::MAX, + Weight::from_ref_time(0), + ), + Error::::TooManyMessagesInTheProof, + ); + }); + } + + #[test] + fn receive_messages_delivery_proof_works() { + run_test(|| { + send_regular_message(); + receive_messages_delivery_proof(); + + assert_eq!( + OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, + 1, + ); + }); + } + + #[test] + fn receive_messages_delivery_proof_rewards_relayers() { + run_test(|| { + assert_ok!(send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + REGULAR_PAYLOAD, + )); + assert_ok!(send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID, + REGULAR_PAYLOAD, + )); + + // this reports delivery of message 1 => reward is paid to TEST_RELAYER_A + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] + .into_iter() + .collect(), + ..Default::default() + } + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + last_delivered_nonce: 1, + ..Default::default() + }, + )); + assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_A, 1)); + assert!(!TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_B, 1)); + + // this reports delivery of both message 1 and message 2 => reward is paid only to + // TEST_RELAYER_B + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B) + ] + .into_iter() + .collect(), + ..Default::default() + } + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + total_messages: 2, + last_delivered_nonce: 2, + ..Default::default() + }, + )); + assert!(!TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_A, 1)); + assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_B, 1)); + }); + } + + #[test] + fn receive_messages_delivery_proof_rejects_invalid_proof() { + run_test(|| { + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Err(())), + Default::default(), + ), + Error::::InvalidMessagesDeliveryProof, + ); + }); + } + + #[test] + fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_invalid() { + run_test(|| { + // when number of relayers entries is invalid + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B) + ] + .into_iter() + .collect(), + ..Default::default() + } + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 2, + last_delivered_nonce: 2, + ..Default::default() + }, + ), + Error::::InvalidUnrewardedRelayersState, + ); + + // when number of messages is invalid + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B) + ] + .into_iter() + .collect(), + ..Default::default() + } + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + total_messages: 1, + last_delivered_nonce: 2, + ..Default::default() + }, + ), + Error::::InvalidUnrewardedRelayersState, + ); + + // when last delivered nonce is invalid + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + relayers: vec![ + unrewarded_relayer(1, 1, TEST_RELAYER_A), + unrewarded_relayer(2, 2, TEST_RELAYER_B) + ] + .into_iter() + .collect(), + ..Default::default() + } + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + total_messages: 2, + last_delivered_nonce: 8, + ..Default::default() + }, + ), + Error::::InvalidUnrewardedRelayersState, + ); + }); + } + + #[test] + fn receive_messages_accepts_single_message_with_invalid_payload() { + run_test(|| { + let mut invalid_message = message(1, REGULAR_PAYLOAD); + invalid_message.payload = Vec::new(); + + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Ok(vec![invalid_message]).into(), + 1, + Weight::from_ref_time(0), /* weight may be zero in this case (all messages are + * improperly encoded) */ + ),); + + assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 1,); + }); + } + + #[test] + fn receive_messages_accepts_batch_with_message_with_invalid_payload() { + run_test(|| { + let mut invalid_message = message(2, REGULAR_PAYLOAD); + invalid_message.payload = Vec::new(); + + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + Ok( + vec![message(1, REGULAR_PAYLOAD), invalid_message, message(3, REGULAR_PAYLOAD),] + ) + .into(), + 3, + REGULAR_PAYLOAD.declared_weight + REGULAR_PAYLOAD.declared_weight, + ),); + + assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 3,); + }); + } + + #[test] + fn actual_dispatch_weight_does_not_overlow() { + run_test(|| { + let message1 = message(1, message_payload(0, u64::MAX / 2)); + let message2 = message(2, message_payload(0, u64::MAX / 2)); + let message3 = message(3, message_payload(0, u64::MAX / 2)); + + assert_ok!(Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + // this may cause overflow if source chain storage is invalid + Ok(vec![message1, message2, message3]).into(), + 3, + Weight::MAX, + )); + assert_eq!(InboundLanes::::get(TEST_LANE_ID).last_delivered_nonce(), 2); + }); + } + + #[test] + fn weight_refund_from_receive_messages_proof_works() { + run_test(|| { + fn submit_with_unspent_weight( + nonce: MessageNonce, + unspent_weight: u64, + is_prepaid: bool, + ) -> (Weight, Weight) { + let mut payload = REGULAR_PAYLOAD; + *payload.dispatch_result.unspent_weight.ref_time_mut() = unspent_weight; + payload.dispatch_result.dispatch_fee_paid_during_dispatch = !is_prepaid; + let proof = Ok(vec![message(nonce, payload)]).into(); + let messages_count = 1; + let pre_dispatch_weight = + ::WeightInfo::receive_messages_proof_weight( + &proof, + messages_count, + REGULAR_PAYLOAD.declared_weight, + ); + let post_dispatch_weight = Pallet::::receive_messages_proof( + RuntimeOrigin::signed(1), + TEST_RELAYER_A, + proof, + messages_count, + REGULAR_PAYLOAD.declared_weight, + ) + .expect("delivery has failed") + .actual_weight + .expect("receive_messages_proof always returns Some"); + + (pre_dispatch_weight, post_dispatch_weight) + } + + // when dispatch is returning `unspent_weight < declared_weight` + let (pre, post) = submit_with_unspent_weight(1, 1, false); + assert_eq!(post.ref_time(), pre.ref_time() - 1); + + // when dispatch is returning `unspent_weight = declared_weight` + let (pre, post) = + submit_with_unspent_weight(2, REGULAR_PAYLOAD.declared_weight.ref_time(), false); + assert_eq!( + post.ref_time(), + pre.ref_time() - REGULAR_PAYLOAD.declared_weight.ref_time() + ); + + // when dispatch is returning `unspent_weight > declared_weight` + let (pre, post) = submit_with_unspent_weight( + 3, + REGULAR_PAYLOAD.declared_weight.ref_time() + 1, + false, + ); + assert_eq!( + post.ref_time(), + pre.ref_time() - REGULAR_PAYLOAD.declared_weight.ref_time() + ); + + // when there's no unspent weight + let (pre, post) = submit_with_unspent_weight(4, 0, false); + assert_eq!(post, pre); + + // when dispatch is returning `unspent_weight < declared_weight` AND message is prepaid + let (pre, post) = submit_with_unspent_weight(5, 1, true); + assert_eq!( + post.ref_time(), + pre.ref_time() - + 1 - ::WeightInfo::pay_inbound_dispatch_fee_overhead() + .ref_time() + ); + }); + } + + #[test] + fn messages_delivered_callbacks_are_called() { + run_test(|| { + send_regular_message(); + send_regular_message(); + send_regular_message(); + + // messages 1+2 are confirmed in 1 tx, message 3 in a separate tx + // dispatch of message 2 has failed + let mut delivered_messages_1_and_2 = DeliveredMessages::new(1); + delivered_messages_1_and_2.note_dispatched_message(); + let messages_1_and_2_proof = Ok(( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 0, + relayers: vec![UnrewardedRelayer { + relayer: 0, + messages: delivered_messages_1_and_2.clone(), + }] + .into_iter() + .collect(), + }, + )); + let delivered_message_3 = DeliveredMessages::new(3); + let messages_3_proof = Ok(( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 0, + relayers: vec![UnrewardedRelayer { relayer: 0, messages: delivered_message_3 }] + .into_iter() + .collect(), + }, + )); + + // first tx with messages 1+2 + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(messages_1_and_2_proof), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 2, + last_delivered_nonce: 2, + ..Default::default() + }, + )); + // second tx with message 3 + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(messages_3_proof), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + last_delivered_nonce: 3, + ..Default::default() + }, + )); + }); + } + + #[test] + fn receive_messages_delivery_proof_rejects_proof_if_trying_to_confirm_more_messages_than_expected( + ) { + run_test(|| { + // send message first to be able to check that delivery_proof fails later + send_regular_message(); + + // 1) InboundLaneData declares that the `last_confirmed_nonce` is 1; + // 2) InboundLaneData has no entries => `InboundLaneData::last_delivered_nonce()` + // returns `last_confirmed_nonce`; + // 3) it means that we're going to confirm delivery of messages 1..=1; + // 4) so the number of declared messages (see `UnrewardedRelayersState`) is `0` and + // numer of actually confirmed messages is `1`. + assert_noop!( + Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { last_confirmed_nonce: 1, relayers: Default::default() }, + ))), + UnrewardedRelayersState { last_delivered_nonce: 1, ..Default::default() }, + ), + Error::::TryingToConfirmMoreMessagesThanExpected, + ); + }); + } + + #[test] + fn storage_keys_computed_properly() { + assert_eq!( + PalletOperatingMode::::storage_value_final_key().to_vec(), + bp_messages::storage_keys::operating_mode_key("Messages").0, + ); + + assert_eq!( + OutboundMessages::::storage_map_final_key(MessageKey { + lane_id: TEST_LANE_ID, + nonce: 42 + }), + bp_messages::storage_keys::message_key("Messages", &TEST_LANE_ID, 42).0, + ); + + assert_eq!( + OutboundLanes::::storage_map_final_key(TEST_LANE_ID), + bp_messages::storage_keys::outbound_lane_data_key("Messages", &TEST_LANE_ID).0, + ); + + assert_eq!( + InboundLanes::::storage_map_final_key(TEST_LANE_ID), + bp_messages::storage_keys::inbound_lane_data_key("Messages", &TEST_LANE_ID).0, + ); + } + + #[test] + fn inbound_message_details_works() { + run_test(|| { + assert_eq!( + Pallet::::inbound_message_data( + TEST_LANE_ID, + REGULAR_PAYLOAD.encode(), + OutboundMessageDetails { + nonce: 0, + dispatch_weight: Weight::from_ref_time(0), + size: 0, + }, + ), + InboundMessageDetails { dispatch_weight: REGULAR_PAYLOAD.declared_weight }, + ); + }); + } + + #[test] + fn on_idle_callback_respects_remaining_weight() { + run_test(|| { + send_regular_message(); + send_regular_message(); + send_regular_message(); + send_regular_message(); + + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID, + InboundLaneData { + last_confirmed_nonce: 4, + relayers: vec![unrewarded_relayer(1, 4, TEST_RELAYER_A)] + .into_iter() + .collect(), + }, + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 4, + total_messages: 4, + last_delivered_nonce: 4, + }, + )); + + // all 4 messages may be pruned now + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, + 4 + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, + 1 + ); + System::::set_block_number(2); + + // if passed wight is too low to do anything + let dbw = DbWeight::get(); + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(1, 1)), + Weight::zero(), + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, + 1 + ); + + // if passed wight is enough to prune single message + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(1, 2)), + dbw.reads_writes(1, 2), + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, + 2 + ); + + // if passed wight is enough to prune two more messages + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(1, 3)), + dbw.reads_writes(1, 3), + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, + 4 + ); + + // if passed wight is enough to prune many messages + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(100, 100)), + dbw.reads_writes(1, 2), + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, + 5 + ); + }); + } + + #[test] + fn on_idle_callback_is_rotating_lanes_to_prune() { + run_test(|| { + // send + receive confirmation for lane 1 + send_regular_message(); + receive_messages_delivery_proof(); + // send + receive confirmation for lane 2 + assert_ok!(send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID_2, + REGULAR_PAYLOAD, + )); + assert_ok!(Pallet::::receive_messages_delivery_proof( + RuntimeOrigin::signed(1), + TestMessagesDeliveryProof(Ok(( + TEST_LANE_ID_2, + InboundLaneData { + last_confirmed_nonce: 1, + relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)] + .into_iter() + .collect(), + }, + ))), + UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + messages_in_oldest_entry: 1, + total_messages: 1, + last_delivered_nonce: 1, + }, + )); + + // nothing is pruned yet + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, + 1 + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, + 1 + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID_2).data().latest_received_nonce, + 1 + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, + 1 + ); + + // in block#2.on_idle lane messages of lane 1 are pruned + let dbw = DbWeight::get(); + System::::set_block_number(2); + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(100, 100)), + dbw.reads_writes(1, 2), + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, + 2 + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, + 1 + ); + + // in block#3.on_idle lane messages of lane 2 are pruned + System::::set_block_number(3); + + assert_eq!( + Pallet::::on_idle(0, dbw.reads_writes(100, 100)), + dbw.reads_writes(1, 2), + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, + 2 + ); + assert_eq!( + outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, + 2 + ); + }); + } + + #[test] + fn outbound_message_from_unconfigured_lane_is_rejected() { + run_test(|| { + assert_noop!( + send_message::( + RuntimeOrigin::signed(1), + TEST_LANE_ID_3, + REGULAR_PAYLOAD, + ), + Error::::InactiveOutboundLane, + ); + }); + } + + generate_owned_bridge_module_tests!( + MessagesOperatingMode::Basic(BasicOperatingMode::Normal), + MessagesOperatingMode::Basic(BasicOperatingMode::Halted) + ); +} diff --git a/modules/messages/src/mock.rs b/modules/messages/src/mock.rs new file mode 100644 index 00000000000..7a59eb4fc3e --- /dev/null +++ b/modules/messages/src/mock.rs @@ -0,0 +1,418 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +// From construct_runtime macro +#![allow(clippy::from_over_into)] + +use crate::{calc_relayers_rewards, Config}; + +use bp_messages::{ + source_chain::{LaneMessageVerifier, MessageDeliveryAndDispatchPayment, TargetHeaderChain}, + target_chain::{ + DispatchMessage, DispatchMessageData, MessageDispatch, ProvedLaneMessages, ProvedMessages, + SourceHeaderChain, + }, + DeliveredMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, + OutboundLaneData, UnrewardedRelayer, +}; +use bp_runtime::{messages::MessageDispatchResult, Size}; +use codec::{Decode, Encode}; +use frame_support::{ + parameter_types, + weights::{RuntimeDbWeight, Weight}, +}; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_runtime::{ + testing::Header as SubstrateHeader, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; +use std::{ + collections::{BTreeMap, VecDeque}, + ops::RangeInclusive, +}; + +pub type AccountId = u64; +pub type Balance = u64; +#[derive(Decode, Encode, Clone, Debug, PartialEq, Eq, TypeInfo)] +pub struct TestPayload { + /// Field that may be used to identify messages. + pub id: u64, + /// Reject this message by lane verifier? + pub reject_by_lane_verifier: bool, + /// Dispatch weight that is declared by the message sender. + pub declared_weight: Weight, + /// Message dispatch result. + /// + /// Note: in correct code `dispatch_result.unspent_weight` will always be <= `declared_weight`, + /// but for test purposes we'll be making it larger than `declared_weight` sometimes. + pub dispatch_result: MessageDispatchResult, + /// Extra bytes that affect payload size. + pub extra: Vec, +} +pub type TestMessageFee = u64; +pub type TestRelayer = u64; + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +use crate as pallet_bridge_messages; + +frame_support::construct_runtime! { + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Event}, + Messages: pallet_bridge_messages::{Pallet, Call, Event}, + } +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 }; +} + +impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = SubstrateHeader; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = DbWeight; + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} + +impl pallet_balances::Config for TestRuntime { + type MaxLocks = (); + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = frame_system::Pallet; + type WeightInfo = (); + type MaxReserves = (); + type ReserveIdentifier = (); +} + +parameter_types! { + pub const MaxMessagesToPruneAtOnce: u64 = 10; + pub const MaxUnrewardedRelayerEntriesAtInboundLane: u64 = 16; + pub const MaxUnconfirmedMessagesAtInboundLane: u64 = 32; + pub const TestBridgedChainId: bp_runtime::ChainId = *b"test"; + pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID, TEST_LANE_ID_2]; +} + +impl Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type ActiveOutboundLanes = ActiveOutboundLanes; + type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = frame_support::traits::ConstU32; + type OutboundPayload = TestPayload; + + type InboundPayload = TestPayload; + type InboundRelayer = TestRelayer; + + type TargetHeaderChain = TestTargetHeaderChain; + type LaneMessageVerifier = TestLaneMessageVerifier; + type MessageDeliveryAndDispatchPayment = TestMessageDeliveryAndDispatchPayment; + + type SourceHeaderChain = TestSourceHeaderChain; + type MessageDispatch = TestMessageDispatch; + type BridgedChainId = TestBridgedChainId; +} + +impl Size for TestPayload { + fn size(&self) -> u32 { + 16 + self.extra.len() as u32 + } +} + +/// Maximal outbound payload size. +pub const MAX_OUTBOUND_PAYLOAD_SIZE: u32 = 4096; + +/// Account that has balance to use in tests. +pub const ENDOWED_ACCOUNT: AccountId = 0xDEAD; + +/// Account id of test relayer. +pub const TEST_RELAYER_A: AccountId = 100; + +/// Account id of additional test relayer - B. +pub const TEST_RELAYER_B: AccountId = 101; + +/// Account id of additional test relayer - C. +pub const TEST_RELAYER_C: AccountId = 102; + +/// Error that is returned by all test implementations. +pub const TEST_ERROR: &str = "Test error"; + +/// Lane that we're using in tests. +pub const TEST_LANE_ID: LaneId = [0, 0, 0, 1]; + +/// Secondary lane that we're using in tests. +pub const TEST_LANE_ID_2: LaneId = [0, 0, 0, 2]; + +/// Inactive outbound lane. +pub const TEST_LANE_ID_3: LaneId = [0, 0, 0, 3]; + +/// Regular message payload. +pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50); + +/// Payload that is rejected by `TestTargetHeaderChain`. +pub const PAYLOAD_REJECTED_BY_TARGET_CHAIN: TestPayload = message_payload(1, 50); + +/// Vec of proved messages, grouped by lane. +pub type MessagesByLaneVec = Vec<(LaneId, ProvedLaneMessages)>; + +/// Test messages proof. +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +pub struct TestMessagesProof { + pub result: Result, +} + +impl Size for TestMessagesProof { + fn size(&self) -> u32 { + 0 + } +} + +impl From, ()>> for TestMessagesProof { + fn from(result: Result, ()>) -> Self { + Self { + result: result.map(|messages| { + let mut messages_by_lane: BTreeMap> = + BTreeMap::new(); + for message in messages { + messages_by_lane.entry(message.key.lane_id).or_default().messages.push(message); + } + messages_by_lane.into_iter().collect() + }), + } + } +} + +/// Messages delivery proof used in tests. +#[derive(Debug, Encode, Decode, Eq, Clone, PartialEq, TypeInfo)] +pub struct TestMessagesDeliveryProof(pub Result<(LaneId, InboundLaneData), ()>); + +impl Size for TestMessagesDeliveryProof { + fn size(&self) -> u32 { + 0 + } +} + +/// Target header chain that is used in tests. +#[derive(Debug, Default)] +pub struct TestTargetHeaderChain; + +impl TargetHeaderChain for TestTargetHeaderChain { + type Error = &'static str; + + type MessagesDeliveryProof = TestMessagesDeliveryProof; + + fn verify_message(payload: &TestPayload) -> Result<(), Self::Error> { + if *payload == PAYLOAD_REJECTED_BY_TARGET_CHAIN { + Err(TEST_ERROR) + } else { + Ok(()) + } + } + + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error> { + proof.0.map_err(|_| TEST_ERROR) + } +} + +/// Lane message verifier that is used in tests. +#[derive(Debug, Default)] +pub struct TestLaneMessageVerifier; + +impl LaneMessageVerifier for TestLaneMessageVerifier { + type Error = &'static str; + + fn verify_message( + _submitter: &RuntimeOrigin, + _lane: &LaneId, + _lane_outbound_data: &OutboundLaneData, + payload: &TestPayload, + ) -> Result<(), Self::Error> { + if !payload.reject_by_lane_verifier { + Ok(()) + } else { + Err(TEST_ERROR) + } + } +} + +/// Message fee payment system that is used in tests. +#[derive(Debug, Default)] +pub struct TestMessageDeliveryAndDispatchPayment; + +impl TestMessageDeliveryAndDispatchPayment { + /// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is + /// cleared after the call. + pub fn is_reward_paid(relayer: AccountId, fee: TestMessageFee) -> bool { + let key = (b":relayer-reward:", relayer, fee).encode(); + frame_support::storage::unhashed::take::(&key).is_some() + } +} + +impl MessageDeliveryAndDispatchPayment + for TestMessageDeliveryAndDispatchPayment +{ + type Error = &'static str; + + fn pay_relayers_rewards( + _lane_id: LaneId, + message_relayers: VecDeque>, + _confirmation_relayer: &AccountId, + received_range: &RangeInclusive, + ) { + let relayers_rewards = + calc_relayers_rewards::(message_relayers, received_range); + for (relayer, reward) in &relayers_rewards { + let key = (b":relayer-reward:", relayer, reward).encode(); + frame_support::storage::unhashed::put(&key, &true); + } + } +} + +/// Source header chain that is used in tests. +#[derive(Debug)] +pub struct TestSourceHeaderChain; + +impl SourceHeaderChain for TestSourceHeaderChain { + type Error = &'static str; + + type MessagesProof = TestMessagesProof; + + fn verify_messages_proof( + proof: Self::MessagesProof, + _messages_count: u32, + ) -> Result, Self::Error> { + proof.result.map(|proof| proof.into_iter().collect()).map_err(|_| TEST_ERROR) + } +} + +/// Source header chain that is used in tests. +#[derive(Debug)] +pub struct TestMessageDispatch; + +impl MessageDispatch for TestMessageDispatch { + type DispatchPayload = TestPayload; + + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { + match message.data.payload.as_ref() { + Ok(payload) => payload.declared_weight, + Err(_) => Weight::from_ref_time(0), + } + } + + fn dispatch( + _relayer_account: &AccountId, + message: DispatchMessage, + ) -> MessageDispatchResult { + match message.data.payload.as_ref() { + Ok(payload) => payload.dispatch_result.clone(), + Err(_) => dispatch_result(0), + } + } +} + +/// Return test lane message with given nonce and payload. +pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { + Message { key: MessageKey { lane_id: TEST_LANE_ID, nonce }, payload: payload.encode() } +} + +/// Return valid outbound message data, constructed from given payload. +pub fn outbound_message_data(payload: TestPayload) -> MessagePayload { + payload.encode() +} + +/// Return valid inbound (dispatch) message data, constructed from given payload. +pub fn inbound_message_data(payload: TestPayload) -> DispatchMessageData { + DispatchMessageData { payload: Ok(payload) } +} + +/// Constructs message payload using given arguments and zero unspent weight. +pub const fn message_payload(id: u64, declared_weight: u64) -> TestPayload { + TestPayload { + id, + reject_by_lane_verifier: false, + declared_weight: Weight::from_ref_time(declared_weight), + dispatch_result: dispatch_result(0), + extra: Vec::new(), + } +} + +/// Returns message dispatch result with given unspent weight. +pub const fn dispatch_result(unspent_weight: u64) -> MessageDispatchResult { + MessageDispatchResult { + unspent_weight: Weight::from_ref_time(unspent_weight), + dispatch_fee_paid_during_dispatch: true, + } +} + +/// Constructs unrewarded relayer entry from nonces range and relayer id. +pub fn unrewarded_relayer( + begin: MessageNonce, + end: MessageNonce, + relayer: TestRelayer, +) -> UnrewardedRelayer { + UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end } } +} + +/// Run pallet test. +pub fn run_test(test: impl FnOnce() -> T) -> T { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { balances: vec![(ENDOWED_ACCOUNT, 1_000_000)] } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(test) +} diff --git a/modules/messages/src/outbound_lane.rs b/modules/messages/src/outbound_lane.rs new file mode 100644 index 00000000000..33a58a40400 --- /dev/null +++ b/modules/messages/src/outbound_lane.rs @@ -0,0 +1,436 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Everything about outgoing messages sending. + +use crate::Config; + +use bp_messages::{ + DeliveredMessages, LaneId, MessageNonce, MessagePayload, OutboundLaneData, UnrewardedRelayer, +}; +use frame_support::{ + weights::{RuntimeDbWeight, Weight}, + BoundedVec, RuntimeDebug, +}; +use num_traits::Zero; +use sp_std::collections::vec_deque::VecDeque; + +/// Outbound lane storage. +pub trait OutboundLaneStorage { + /// Lane id. + fn id(&self) -> LaneId; + /// Get lane data from the storage. + fn data(&self) -> OutboundLaneData; + /// Update lane data in the storage. + fn set_data(&mut self, data: OutboundLaneData); + /// Returns saved outbound message payload. + #[cfg(test)] + fn message(&self, nonce: &MessageNonce) -> Option; + /// Save outbound message in the storage. + fn save_message(&mut self, nonce: MessageNonce, message_payload: MessagePayload); + /// Remove outbound message from the storage. + fn remove_message(&mut self, nonce: &MessageNonce); +} + +/// Outbound message data wrapper that implements `MaxEncodedLen`. +pub type StoredMessagePayload = BoundedVec>::MaximalOutboundPayloadSize>; + +/// Result of messages receival confirmation. +#[derive(RuntimeDebug, PartialEq, Eq)] +pub enum ReceivalConfirmationResult { + /// New messages have been confirmed by the confirmation transaction. + ConfirmedMessages(DeliveredMessages), + /// Confirmation transaction brings no new confirmation. This may be a result of relayer + /// error or several relayers running. + NoNewConfirmations, + /// Bridged chain is trying to confirm more messages than we have generated. May be a result + /// of invalid bridged chain storage. + FailedToConfirmFutureMessages, + /// The unrewarded relayers vec contains an empty entry. May be a result of invalid bridged + /// chain storage. + EmptyUnrewardedRelayerEntry, + /// The unrewarded relayers vec contains non-consecutive entries. May be a result of invalid + /// bridged chain storage. + NonConsecutiveUnrewardedRelayerEntries, + /// The chain has more messages that need to be confirmed than there is in the proof. + TryingToConfirmMoreMessagesThanExpected(MessageNonce), +} + +/// Outbound messages lane. +pub struct OutboundLane { + storage: S, +} + +impl OutboundLane { + /// Create new outbound lane backed by given storage. + pub fn new(storage: S) -> Self { + OutboundLane { storage } + } + + /// Get this lane data. + pub fn data(&self) -> OutboundLaneData { + self.storage.data() + } + + /// Send message over lane. + /// + /// Returns new message nonce. + pub fn send_message(&mut self, message_payload: MessagePayload) -> MessageNonce { + let mut data = self.storage.data(); + let nonce = data.latest_generated_nonce + 1; + data.latest_generated_nonce = nonce; + + self.storage.save_message(nonce, message_payload); + self.storage.set_data(data); + + nonce + } + + /// Confirm messages delivery. + pub fn confirm_delivery( + &mut self, + max_allowed_messages: MessageNonce, + latest_delivered_nonce: MessageNonce, + relayers: &VecDeque>, + ) -> ReceivalConfirmationResult { + let mut data = self.storage.data(); + if latest_delivered_nonce <= data.latest_received_nonce { + return ReceivalConfirmationResult::NoNewConfirmations + } + if latest_delivered_nonce > data.latest_generated_nonce { + return ReceivalConfirmationResult::FailedToConfirmFutureMessages + } + if latest_delivered_nonce - data.latest_received_nonce > max_allowed_messages { + // that the relayer has declared correct number of messages that the proof contains (it + // is checked outside of the function). But it may happen (but only if this/bridged + // chain storage is corrupted, though) that the actual number of confirmed messages if + // larger than declared. This would mean that 'reward loop' will take more time than the + // weight formula accounts, so we can't allow that. + return ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected( + latest_delivered_nonce - data.latest_received_nonce, + ) + } + + if let Err(e) = ensure_unrewarded_relayers_are_correct(latest_delivered_nonce, relayers) { + return e + } + + let prev_latest_received_nonce = data.latest_received_nonce; + data.latest_received_nonce = latest_delivered_nonce; + self.storage.set_data(data); + + ReceivalConfirmationResult::ConfirmedMessages(DeliveredMessages { + begin: prev_latest_received_nonce + 1, + end: latest_delivered_nonce, + }) + } + + /// Prune at most `max_messages_to_prune` already received messages. + /// + /// Returns weight, consumed by messages pruning and lane state update. + pub fn prune_messages( + &mut self, + db_weight: RuntimeDbWeight, + mut remaining_weight: Weight, + ) -> Weight { + let write_weight = db_weight.writes(1); + let two_writes_weight = write_weight + write_weight; + let mut spent_weight = Weight::zero(); + let mut data = self.storage.data(); + while remaining_weight.all_gte(two_writes_weight) && + data.oldest_unpruned_nonce <= data.latest_received_nonce + { + self.storage.remove_message(&data.oldest_unpruned_nonce); + + spent_weight += write_weight; + remaining_weight -= write_weight; + data.oldest_unpruned_nonce += 1; + } + + if !spent_weight.is_zero() { + spent_weight += write_weight; + self.storage.set_data(data); + } + + spent_weight + } +} + +/// Verifies unrewarded relayers vec. +/// +/// Returns `Err(_)` if unrewarded relayers vec contains invalid data, meaning that the bridged +/// chain has invalid runtime storage. +fn ensure_unrewarded_relayers_are_correct( + latest_received_nonce: MessageNonce, + relayers: &VecDeque>, +) -> Result<(), ReceivalConfirmationResult> { + let mut last_entry_end: Option = None; + for entry in relayers { + // unrewarded relayer entry must have at least 1 unconfirmed message + // (guaranteed by the `InboundLane::receive_message()`) + if entry.messages.end < entry.messages.begin { + return Err(ReceivalConfirmationResult::EmptyUnrewardedRelayerEntry) + } + // every entry must confirm range of messages that follows previous entry range + // (guaranteed by the `InboundLane::receive_message()`) + if let Some(last_entry_end) = last_entry_end { + let expected_entry_begin = last_entry_end.checked_add(1); + if expected_entry_begin != Some(entry.messages.begin) { + return Err(ReceivalConfirmationResult::NonConsecutiveUnrewardedRelayerEntries) + } + } + last_entry_end = Some(entry.messages.end); + // entry can't confirm messages larger than `inbound_lane_data.latest_received_nonce()` + // (guaranteed by the `InboundLane::receive_message()`) + if entry.messages.end > latest_received_nonce { + // technically this will be detected in the next loop iteration as + // `InvalidNumberOfDispatchResults` but to guarantee safety of loop operations below + // this is detected now + return Err(ReceivalConfirmationResult::FailedToConfirmFutureMessages) + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + mock::{ + outbound_message_data, run_test, unrewarded_relayer, TestRelayer, TestRuntime, + REGULAR_PAYLOAD, TEST_LANE_ID, + }, + outbound_lane, + }; + use frame_support::weights::constants::RocksDbWeight; + use sp_std::ops::RangeInclusive; + + fn unrewarded_relayers( + nonces: RangeInclusive, + ) -> VecDeque> { + vec![unrewarded_relayer(*nonces.start(), *nonces.end(), 0)] + .into_iter() + .collect() + } + + fn delivered_messages(nonces: RangeInclusive) -> DeliveredMessages { + DeliveredMessages { begin: *nonces.start(), end: *nonces.end() } + } + + fn assert_3_messages_confirmation_fails( + latest_received_nonce: MessageNonce, + relayers: &VecDeque>, + ) -> ReceivalConfirmationResult { + run_test(|| { + let mut lane = outbound_lane::(TEST_LANE_ID); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + assert_eq!(lane.storage.data().latest_generated_nonce, 3); + assert_eq!(lane.storage.data().latest_received_nonce, 0); + let result = lane.confirm_delivery(3, latest_received_nonce, relayers); + assert_eq!(lane.storage.data().latest_generated_nonce, 3); + assert_eq!(lane.storage.data().latest_received_nonce, 0); + result + }) + } + + #[test] + fn send_message_works() { + run_test(|| { + let mut lane = outbound_lane::(TEST_LANE_ID); + assert_eq!(lane.storage.data().latest_generated_nonce, 0); + assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1); + assert!(lane.storage.message(&1).is_some()); + assert_eq!(lane.storage.data().latest_generated_nonce, 1); + }); + } + + #[test] + fn confirm_delivery_works() { + run_test(|| { + let mut lane = outbound_lane::(TEST_LANE_ID); + assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1); + assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 2); + assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 3); + assert_eq!(lane.storage.data().latest_generated_nonce, 3); + assert_eq!(lane.storage.data().latest_received_nonce, 0); + assert_eq!( + lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), + ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(1..=3)), + ); + assert_eq!(lane.storage.data().latest_generated_nonce, 3); + assert_eq!(lane.storage.data().latest_received_nonce, 3); + }); + } + + #[test] + fn confirm_delivery_rejects_nonce_lesser_than_latest_received() { + run_test(|| { + let mut lane = outbound_lane::(TEST_LANE_ID); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + assert_eq!(lane.storage.data().latest_generated_nonce, 3); + assert_eq!(lane.storage.data().latest_received_nonce, 0); + assert_eq!( + lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), + ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(1..=3)), + ); + assert_eq!( + lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), + ReceivalConfirmationResult::NoNewConfirmations, + ); + assert_eq!(lane.storage.data().latest_generated_nonce, 3); + assert_eq!(lane.storage.data().latest_received_nonce, 3); + + assert_eq!( + lane.confirm_delivery(1, 2, &unrewarded_relayers(1..=1)), + ReceivalConfirmationResult::NoNewConfirmations, + ); + assert_eq!(lane.storage.data().latest_generated_nonce, 3); + assert_eq!(lane.storage.data().latest_received_nonce, 3); + }); + } + + #[test] + fn confirm_delivery_rejects_nonce_larger_than_last_generated() { + assert_eq!( + assert_3_messages_confirmation_fails(10, &unrewarded_relayers(1..=10),), + ReceivalConfirmationResult::FailedToConfirmFutureMessages, + ); + } + + #[test] + fn confirm_delivery_fails_if_entry_confirms_future_messages() { + assert_eq!( + assert_3_messages_confirmation_fails( + 3, + &unrewarded_relayers(1..=1) + .into_iter() + .chain(unrewarded_relayers(2..=30).into_iter()) + .chain(unrewarded_relayers(3..=3).into_iter()) + .collect(), + ), + ReceivalConfirmationResult::FailedToConfirmFutureMessages, + ); + } + + #[test] + #[allow(clippy::reversed_empty_ranges)] + fn confirm_delivery_fails_if_entry_is_empty() { + assert_eq!( + assert_3_messages_confirmation_fails( + 3, + &unrewarded_relayers(1..=1) + .into_iter() + .chain(unrewarded_relayers(2..=1).into_iter()) + .chain(unrewarded_relayers(2..=3).into_iter()) + .collect(), + ), + ReceivalConfirmationResult::EmptyUnrewardedRelayerEntry, + ); + } + + #[test] + fn confirm_delivery_fails_if_entries_are_non_consecutive() { + assert_eq!( + assert_3_messages_confirmation_fails( + 3, + &unrewarded_relayers(1..=1) + .into_iter() + .chain(unrewarded_relayers(3..=3).into_iter()) + .chain(unrewarded_relayers(2..=2).into_iter()) + .collect(), + ), + ReceivalConfirmationResult::NonConsecutiveUnrewardedRelayerEntries, + ); + } + + #[test] + fn prune_messages_works() { + run_test(|| { + let mut lane = outbound_lane::(TEST_LANE_ID); + // when lane is empty, nothing is pruned + assert_eq!( + lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), + Weight::zero() + ); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + // when nothing is confirmed, nothing is pruned + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + assert!(lane.storage.message(&1).is_some()); + assert!(lane.storage.message(&2).is_some()); + assert!(lane.storage.message(&3).is_some()); + assert_eq!( + lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), + Weight::zero() + ); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + // after confirmation, some messages are received + assert_eq!( + lane.confirm_delivery(2, 2, &unrewarded_relayers(1..=2)), + ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(1..=2)), + ); + assert_eq!( + lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), + RocksDbWeight::get().writes(3), + ); + assert!(lane.storage.message(&1).is_none()); + assert!(lane.storage.message(&2).is_none()); + assert!(lane.storage.message(&3).is_some()); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3); + // after last message is confirmed, everything is pruned + assert_eq!( + lane.confirm_delivery(1, 3, &unrewarded_relayers(3..=3)), + ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(3..=3)), + ); + assert_eq!( + lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), + RocksDbWeight::get().writes(2), + ); + assert!(lane.storage.message(&1).is_none()); + assert!(lane.storage.message(&2).is_none()); + assert!(lane.storage.message(&3).is_none()); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); + }); + } + + #[test] + fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() { + run_test(|| { + let mut lane = outbound_lane::(TEST_LANE_ID); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + assert_eq!( + lane.confirm_delivery(0, 3, &unrewarded_relayers(1..=3)), + ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected(3), + ); + assert_eq!( + lane.confirm_delivery(2, 3, &unrewarded_relayers(1..=3)), + ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected(3), + ); + assert_eq!( + lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), + ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(1..=3)), + ); + }); + } +} diff --git a/modules/messages/src/weights.rs b/modules/messages/src/weights.rs new file mode 100644 index 00000000000..2ae60c17faf --- /dev/null +++ b/modules/messages/src/weights.rs @@ -0,0 +1,163 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Autogenerated weights for `pallet_bridge_messages` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-17, STEPS: 50, REPEAT: 20 +//! LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled +//! CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/millau-bridge-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_bridge_messages +// --extrinsic=* +// --execution=wasm +// --wasm-execution=Compiled +// --heap-pages=4096 +// --output=./modules/messages/src/weights.rs +// --template=./.maintain/millau-weight-template.hbs + +#![allow(clippy::all)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for `pallet_bridge_messages`. +pub trait WeightInfo { + fn receive_single_message_proof() -> Weight; + fn receive_two_messages_proof() -> Weight; + fn receive_single_message_proof_with_outbound_lane_state() -> Weight; + fn receive_single_message_proof_1_kb() -> Weight; + fn receive_single_message_proof_16_kb() -> Weight; + fn receive_single_prepaid_message_proof() -> Weight; + fn receive_delivery_proof_for_single_message() -> Weight; + fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight; + fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight; +} + +/// Weights for `pallet_bridge_messages` that are generated using one of the Bridge testnets. +/// +/// Those weights are test only and must never be used in production. +pub struct BridgeWeight(PhantomData); +impl WeightInfo for BridgeWeight { + fn receive_single_message_proof() -> Weight { + Weight::from_ref_time(50_596_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn receive_two_messages_proof() -> Weight { + Weight::from_ref_time(77_041_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn receive_single_message_proof_with_outbound_lane_state() -> Weight { + Weight::from_ref_time(58_331_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn receive_single_message_proof_1_kb() -> Weight { + Weight::from_ref_time(48_061_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn receive_single_message_proof_16_kb() -> Weight { + Weight::from_ref_time(101_601_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn receive_single_prepaid_message_proof() -> Weight { + Weight::from_ref_time(49_646_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn receive_delivery_proof_for_single_message() -> Weight { + Weight::from_ref_time(55_108_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { + Weight::from_ref_time(53_917_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { + Weight::from_ref_time(57_335_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn receive_single_message_proof() -> Weight { + Weight::from_ref_time(50_596_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn receive_two_messages_proof() -> Weight { + Weight::from_ref_time(77_041_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn receive_single_message_proof_with_outbound_lane_state() -> Weight { + Weight::from_ref_time(58_331_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn receive_single_message_proof_1_kb() -> Weight { + Weight::from_ref_time(48_061_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn receive_single_message_proof_16_kb() -> Weight { + Weight::from_ref_time(101_601_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn receive_single_prepaid_message_proof() -> Weight { + Weight::from_ref_time(49_646_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn receive_delivery_proof_for_single_message() -> Weight { + Weight::from_ref_time(55_108_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { + Weight::from_ref_time(53_917_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { + Weight::from_ref_time(57_335_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/modules/messages/src/weights_ext.rs b/modules/messages/src/weights_ext.rs new file mode 100644 index 00000000000..1604832c079 --- /dev/null +++ b/modules/messages/src/weights_ext.rs @@ -0,0 +1,289 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Weight-related utilities. + +use crate::weights::WeightInfo; + +use bp_messages::{MessageNonce, UnrewardedRelayersState}; +use bp_runtime::{PreComputedSize, Size}; +use frame_support::weights::Weight; + +/// Size of the message being delivered in benchmarks. +pub const EXPECTED_DEFAULT_MESSAGE_LENGTH: u32 = 128; + +/// We assume that size of signed extensions on all our chains and size of all 'small' arguments of +/// calls we're checking here would fit 1KB. +const SIGNED_EXTENSIONS_SIZE: u32 = 1024; + +/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at +/// Rialto chain. This mostly depends on number of entries (and their density) in the storage trie. +/// Some reserve is reserved to account future chain growth. +pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; + +/// Ensure that weights from `WeightInfoExt` implementation are looking correct. +pub fn ensure_weights_are_correct() { + // verify `receive_messages_proof` weight components + assert_ne!(W::receive_messages_proof_overhead(), Weight::zero()); + assert_ne!(W::receive_messages_proof_messages_overhead(1), Weight::zero()); + assert_ne!(W::receive_messages_proof_outbound_lane_state_overhead(), Weight::zero()); + assert_ne!(W::storage_proof_size_overhead(1), Weight::zero()); + + // verify `receive_messages_delivery_proof` weight components + assert_ne!(W::receive_messages_delivery_proof_overhead(), Weight::zero()); + assert_ne!(W::storage_proof_size_overhead(1), Weight::zero()); +} + +/// Ensure that we're able to receive maximal (by-size and by-weight) message from other chain. +pub fn ensure_able_to_receive_message( + max_extrinsic_size: u32, + max_extrinsic_weight: Weight, + max_incoming_message_proof_size: u32, + max_incoming_message_dispatch_weight: Weight, +) { + // verify that we're able to receive proof of maximal-size message + let max_delivery_transaction_size = + max_incoming_message_proof_size.saturating_add(SIGNED_EXTENSIONS_SIZE); + assert!( + max_delivery_transaction_size <= max_extrinsic_size, + "Size of maximal message delivery transaction {} + {} is larger than maximal possible transaction size {}", + max_incoming_message_proof_size, + SIGNED_EXTENSIONS_SIZE, + max_extrinsic_size, + ); + + // verify that we're able to receive proof of maximal-size message with maximal dispatch weight + let max_delivery_transaction_dispatch_weight = W::receive_messages_proof_weight( + &PreComputedSize( + (max_incoming_message_proof_size + W::expected_extra_storage_proof_size()) as usize, + ), + 1, + max_incoming_message_dispatch_weight, + ); + assert!( + max_delivery_transaction_dispatch_weight.all_lte(max_extrinsic_weight), + "Weight of maximal message delivery transaction + {} is larger than maximal possible transaction weight {}", + max_delivery_transaction_dispatch_weight, + max_extrinsic_weight, + ); +} + +/// Ensure that we're able to receive maximal confirmation from other chain. +pub fn ensure_able_to_receive_confirmation( + max_extrinsic_size: u32, + max_extrinsic_weight: Weight, + max_inbound_lane_data_proof_size_from_peer_chain: u32, + max_unrewarded_relayer_entries_at_peer_inbound_lane: MessageNonce, + max_unconfirmed_messages_at_inbound_lane: MessageNonce, +) { + // verify that we're able to receive confirmation of maximal-size + let max_confirmation_transaction_size = + max_inbound_lane_data_proof_size_from_peer_chain.saturating_add(SIGNED_EXTENSIONS_SIZE); + assert!( + max_confirmation_transaction_size <= max_extrinsic_size, + "Size of maximal message delivery confirmation transaction {} + {} is larger than maximal possible transaction size {}", + max_inbound_lane_data_proof_size_from_peer_chain, + SIGNED_EXTENSIONS_SIZE, + max_extrinsic_size, + ); + + // verify that we're able to reward maximal number of relayers that have delivered maximal + // number of messages + let max_confirmation_transaction_dispatch_weight = W::receive_messages_delivery_proof_weight( + &PreComputedSize(max_inbound_lane_data_proof_size_from_peer_chain as usize), + &UnrewardedRelayersState { + unrewarded_relayer_entries: max_unrewarded_relayer_entries_at_peer_inbound_lane, + total_messages: max_unconfirmed_messages_at_inbound_lane, + ..Default::default() + }, + ); + assert!( + max_confirmation_transaction_dispatch_weight.all_lte(max_extrinsic_weight), + "Weight of maximal confirmation transaction {} is larger than maximal possible transaction weight {}", + max_confirmation_transaction_dispatch_weight, + max_extrinsic_weight, + ); +} + +/// Extended weight info. +pub trait WeightInfoExt: WeightInfo { + /// Size of proof that is already included in the single message delivery weight. + /// + /// The message submitter (at source chain) has already covered this cost. But there are two + /// factors that may increase proof size: (1) the message size may be larger than predefined + /// and (2) relayer may add extra trie nodes to the proof. So if proof size is larger than + /// this value, we're going to charge relayer for that. + fn expected_extra_storage_proof_size() -> u32; + + // Functions that are directly mapped to extrinsics weights. + + /// Weight of message delivery extrinsic. + fn receive_messages_proof_weight( + proof: &impl Size, + messages_count: u32, + dispatch_weight: Weight, + ) -> Weight { + // basic components of extrinsic weight + let transaction_overhead = Self::receive_messages_proof_overhead(); + let outbound_state_delivery_weight = + Self::receive_messages_proof_outbound_lane_state_overhead(); + let messages_delivery_weight = + Self::receive_messages_proof_messages_overhead(MessageNonce::from(messages_count)); + let messages_dispatch_weight = dispatch_weight; + + // proof size overhead weight + let expected_proof_size = EXPECTED_DEFAULT_MESSAGE_LENGTH + .saturating_mul(messages_count.saturating_sub(1)) + .saturating_add(Self::expected_extra_storage_proof_size()); + let actual_proof_size = proof.size(); + let proof_size_overhead = Self::storage_proof_size_overhead( + actual_proof_size.saturating_sub(expected_proof_size), + ); + + transaction_overhead + .saturating_add(outbound_state_delivery_weight) + .saturating_add(messages_delivery_weight) + .saturating_add(messages_dispatch_weight) + .saturating_add(proof_size_overhead) + } + + /// Weight of confirmation delivery extrinsic. + fn receive_messages_delivery_proof_weight( + proof: &impl Size, + relayers_state: &UnrewardedRelayersState, + ) -> Weight { + // basic components of extrinsic weight + let transaction_overhead = Self::receive_messages_delivery_proof_overhead(); + let messages_overhead = + Self::receive_messages_delivery_proof_messages_overhead(relayers_state.total_messages); + let relayers_overhead = Self::receive_messages_delivery_proof_relayers_overhead( + relayers_state.unrewarded_relayer_entries, + ); + + // proof size overhead weight + let expected_proof_size = Self::expected_extra_storage_proof_size(); + let actual_proof_size = proof.size(); + let proof_size_overhead = Self::storage_proof_size_overhead( + actual_proof_size.saturating_sub(expected_proof_size), + ); + + transaction_overhead + .saturating_add(messages_overhead) + .saturating_add(relayers_overhead) + .saturating_add(proof_size_overhead) + } + + // Functions that are used by extrinsics weights formulas. + + /// Returns weight overhead of message delivery transaction (`receive_messages_proof`). + fn receive_messages_proof_overhead() -> Weight { + let weight_of_two_messages_and_two_tx_overheads = + Self::receive_single_message_proof().saturating_mul(2); + let weight_of_two_messages_and_single_tx_overhead = Self::receive_two_messages_proof(); + weight_of_two_messages_and_two_tx_overheads + .saturating_sub(weight_of_two_messages_and_single_tx_overhead) + } + + /// Returns weight that needs to be accounted when receiving given a number of messages with + /// message delivery transaction (`receive_messages_proof`). + fn receive_messages_proof_messages_overhead(messages: MessageNonce) -> Weight { + let weight_of_two_messages_and_single_tx_overhead = Self::receive_two_messages_proof(); + let weight_of_single_message_and_single_tx_overhead = Self::receive_single_message_proof(); + weight_of_two_messages_and_single_tx_overhead + .saturating_sub(weight_of_single_message_and_single_tx_overhead) + .saturating_mul(messages as _) + } + + /// Returns weight that needs to be accounted when message delivery transaction + /// (`receive_messages_proof`) is carrying outbound lane state proof. + fn receive_messages_proof_outbound_lane_state_overhead() -> Weight { + let weight_of_single_message_and_lane_state = + Self::receive_single_message_proof_with_outbound_lane_state(); + let weight_of_single_message = Self::receive_single_message_proof(); + weight_of_single_message_and_lane_state.saturating_sub(weight_of_single_message) + } + + /// Returns weight overhead of delivery confirmation transaction + /// (`receive_messages_delivery_proof`). + fn receive_messages_delivery_proof_overhead() -> Weight { + let weight_of_two_messages_and_two_tx_overheads = + Self::receive_delivery_proof_for_single_message().saturating_mul(2); + let weight_of_two_messages_and_single_tx_overhead = + Self::receive_delivery_proof_for_two_messages_by_single_relayer(); + weight_of_two_messages_and_two_tx_overheads + .saturating_sub(weight_of_two_messages_and_single_tx_overhead) + } + + /// Returns weight that needs to be accounted when receiving confirmations for given a number of + /// messages with delivery confirmation transaction (`receive_messages_delivery_proof`). + fn receive_messages_delivery_proof_messages_overhead(messages: MessageNonce) -> Weight { + let weight_of_two_messages = + Self::receive_delivery_proof_for_two_messages_by_single_relayer(); + let weight_of_single_message = Self::receive_delivery_proof_for_single_message(); + weight_of_two_messages + .saturating_sub(weight_of_single_message) + .saturating_mul(messages as _) + } + + /// Returns weight that needs to be accounted when receiving confirmations for given a number of + /// relayers entries with delivery confirmation transaction (`receive_messages_delivery_proof`). + fn receive_messages_delivery_proof_relayers_overhead(relayers: MessageNonce) -> Weight { + let weight_of_two_messages_by_two_relayers = + Self::receive_delivery_proof_for_two_messages_by_two_relayers(); + let weight_of_two_messages_by_single_relayer = + Self::receive_delivery_proof_for_two_messages_by_single_relayer(); + weight_of_two_messages_by_two_relayers + .saturating_sub(weight_of_two_messages_by_single_relayer) + .saturating_mul(relayers as _) + } + + /// Returns weight that needs to be accounted when storage proof of given size is received + /// (either in `receive_messages_proof` or `receive_messages_delivery_proof`). + /// + /// **IMPORTANT**: this overhead is already included in the 'base' transaction cost - e.g. proof + /// size depends on messages count or number of entries in the unrewarded relayers set. So this + /// shouldn't be added to cost of transaction, but instead should act as a minimal cost that the + /// relayer must pay when it relays proof of given size (even if cost based on other parameters + /// is less than that cost). + fn storage_proof_size_overhead(proof_size: u32) -> Weight { + let proof_size_in_bytes = proof_size; + let byte_weight = (Self::receive_single_message_proof_16_kb() - + Self::receive_single_message_proof_1_kb()) / + (15 * 1024); + proof_size_in_bytes * byte_weight + } + + /// Returns weight of the pay-dispatch-fee operation for inbound messages. + /// + /// This function may return zero if runtime doesn't support pay-dispatch-fee-at-target-chain + /// option. + fn pay_inbound_dispatch_fee_overhead() -> Weight { + Self::receive_single_message_proof() + .saturating_sub(Self::receive_single_prepaid_message_proof()) + } +} + +impl WeightInfoExt for () { + fn expected_extra_storage_proof_size() -> u32 { + EXTRA_STORAGE_PROOF_SIZE + } +} + +impl WeightInfoExt for crate::weights::BridgeWeight { + fn expected_extra_storage_proof_size() -> u32 { + EXTRA_STORAGE_PROOF_SIZE + } +} diff --git a/modules/parachains/Cargo.toml b/modules/parachains/Cargo.toml new file mode 100644 index 00000000000..ce674459eac --- /dev/null +++ b/modules/parachains/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "pallet-bridge-parachains" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Bridge Dependencies + +bp-header-chain = { path = "../../primitives/header-chain", default-features = false } +bp-parachains = { path = "../../primitives/parachains", default-features = false } +bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } +pallet-bridge-grandpa = { path = "../grandpa", default-features = false } + +# Substrate Dependencies + +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +bp-header-chain = { path = "../../primitives/header-chain" } +bp-test-utils = { path = "../../primitives/test-utils" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = ["std"] +std = [ + "bp-header-chain/std", + "bp-parachains/std", + "bp-polkadot-core/std", + "bp-runtime/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "log/std", + "pallet-bridge-grandpa/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", +] diff --git a/modules/parachains/src/benchmarking.rs b/modules/parachains/src/benchmarking.rs new file mode 100644 index 00000000000..aba296dfc1d --- /dev/null +++ b/modules/parachains/src/benchmarking.rs @@ -0,0 +1,106 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Parachains finality pallet benchmarking. + +use crate::{ + weights_ext::DEFAULT_PARACHAIN_HEAD_SIZE, Call, RelayBlockHash, RelayBlockHasher, + RelayBlockNumber, +}; + +use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; +use bp_runtime::StorageProofSize; +use frame_benchmarking::{account, benchmarks_instance_pallet}; +use frame_system::RawOrigin; +use sp_std::prelude::*; + +/// Pallet we're benchmarking here. +pub struct Pallet, I: 'static>(crate::Pallet); + +/// Trait that must be implemented by runtime to benchmark the parachains finality pallet. +pub trait Config: crate::Config { + /// Generate parachain heads proof and prepare environment for verifying this proof. + fn prepare_parachain_heads_proof( + parachains: &[ParaId], + parachain_head_size: u32, + proof_size: StorageProofSize, + ) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>); +} + +benchmarks_instance_pallet! { + where_clause { + where + >::BridgedChain: + bp_runtime::Chain< + BlockNumber = RelayBlockNumber, + Hash = RelayBlockHash, + Hasher = RelayBlockHasher, + >, + } + + // Benchmark `submit_parachain_heads` extrinsic with different number of parachains. + submit_parachain_heads_with_n_parachains { + let p in 1..1024; + + let sender = account("sender", 0, 0); + let parachains = (1..=p).map(ParaId).collect::>(); + let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof( + ¶chains, + DEFAULT_PARACHAIN_HEAD_SIZE, + StorageProofSize::Minimal(0), + ); + let at_relay_block = (relay_block_number, relay_block_hash); + }: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof) + verify { + for parachain in parachains { + assert!(crate::Pallet::::best_parachain_head(parachain).is_some()); + } + } + + // Benchmark `submit_parachain_heads` extrinsic with 1kb proof size. + submit_parachain_heads_with_1kb_proof { + let sender = account("sender", 0, 0); + let parachains = vec![ParaId(1)]; + let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof( + ¶chains, + DEFAULT_PARACHAIN_HEAD_SIZE, + StorageProofSize::HasExtraNodes(1024), + ); + let at_relay_block = (relay_block_number, relay_block_hash); + }: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof) + verify { + for parachain in parachains { + assert!(crate::Pallet::::best_parachain_head(parachain).is_some()); + } + } + + // Benchmark `submit_parachain_heads` extrinsic with 16kb proof size. + submit_parachain_heads_with_16kb_proof { + let sender = account("sender", 0, 0); + let parachains = vec![ParaId(1)]; + let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof( + ¶chains, + DEFAULT_PARACHAIN_HEAD_SIZE, + StorageProofSize::HasExtraNodes(16 * 1024), + ); + let at_relay_block = (relay_block_number, relay_block_hash); + }: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof) + verify { + for parachain in parachains { + assert!(crate::Pallet::::best_parachain_head(parachain).is_some()); + } + } +} diff --git a/modules/parachains/src/extension.rs b/modules/parachains/src/extension.rs new file mode 100644 index 00000000000..5c2f54257ff --- /dev/null +++ b/modules/parachains/src/extension.rs @@ -0,0 +1,166 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{Config, Pallet, RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use bp_runtime::FilterCall; +use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}; + +/// Validate parachain heads in order to avoid "mining" transactions that provide +/// outdated bridged parachain heads. Without this validation, even honest relayers +/// may lose their funds if there are multiple relays running and submitting the +/// same information. +/// +/// This validation only works with transactions that are updating single parachain +/// head. We can't use unbounded validation - it may take too long and either break +/// block production, or "eat" significant portion of block production time literally +/// for nothing. In addition, the single-parachain-head-per-transaction is how the +/// pallet will be used in our environment. +impl< + Call: IsSubType, T>>, + T: frame_system::Config + Config, + I: 'static, + > FilterCall for Pallet +where + >::BridgedChain: + bp_runtime::Chain< + BlockNumber = RelayBlockNumber, + Hash = RelayBlockHash, + Hasher = RelayBlockHasher, + >, +{ + fn validate(call: &Call) -> TransactionValidity { + let (updated_at_relay_block_number, parachains) = match call.is_sub_type() { + Some(crate::Call::::submit_parachain_heads { + ref at_relay_block, + ref parachains, + .. + }) => (at_relay_block.0, parachains), + _ => return Ok(ValidTransaction::default()), + }; + let (parachain, parachain_head_hash) = match parachains.as_slice() { + &[(parachain, parachain_head_hash)] => (parachain, parachain_head_hash), + _ => return Ok(ValidTransaction::default()), + }; + + let maybe_stored_best_head = crate::ParasInfo::::get(parachain); + let is_valid = Self::validate_updated_parachain_head( + parachain, + &maybe_stored_best_head, + updated_at_relay_block_number, + parachain_head_hash, + "Rejecting obsolete parachain-head transaction", + ); + + if is_valid { + Ok(ValidTransaction::default()) + } else { + InvalidTransaction::Stale.into() + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + extension::FilterCall, + mock::{run_test, RuntimeCall, TestRuntime}, + ParaInfo, ParasInfo, RelayBlockNumber, + }; + use bp_parachains::BestParaHeadHash; + use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; + + fn validate_submit_parachain_heads( + num: RelayBlockNumber, + parachains: Vec<(ParaId, ParaHash)>, + ) -> bool { + crate::Pallet::::validate(&RuntimeCall::Parachains(crate::Call::< + TestRuntime, + (), + >::submit_parachain_heads { + at_relay_block: (num, Default::default()), + parachains, + parachain_heads_proof: ParaHeadsProof(Vec::new()), + })) + .is_ok() + } + + fn sync_to_relay_header_10() { + ParasInfo::::insert( + ParaId(1), + ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 10, + head_hash: [1u8; 32].into(), + }, + next_imported_hash_position: 0, + }, + ); + } + + #[test] + fn extension_rejects_header_from_the_obsolete_relay_block() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#5 => tx is + // rejected + sync_to_relay_header_10(); + assert!(!validate_submit_parachain_heads(5, vec![(ParaId(1), [1u8; 32].into())])); + }); + } + + #[test] + fn extension_rejects_header_from_the_same_relay_block() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#10 => tx is + // rejected + sync_to_relay_header_10(); + assert!(!validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())])); + }); + } + + #[test] + fn extension_rejects_header_from_new_relay_block_with_same_hash() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#10 => tx is + // rejected + sync_to_relay_header_10(); + assert!(!validate_submit_parachain_heads(20, vec![(ParaId(1), [1u8; 32].into())])); + }); + } + + #[test] + fn extension_accepts_new_header() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#15 => tx is + // accepted + sync_to_relay_header_10(); + assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())])); + }); + } + + #[test] + fn extension_accepts_if_more_than_one_parachain_is_submitted() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#5, but another + // parachain head is also supplied => tx is accepted + sync_to_relay_header_10(); + assert!(validate_submit_parachain_heads( + 5, + vec![(ParaId(1), [1u8; 32].into()), (ParaId(2), [1u8; 32].into())] + )); + }); + } +} diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs new file mode 100644 index 00000000000..e6625476a3d --- /dev/null +++ b/modules/parachains/src/lib.rs @@ -0,0 +1,1403 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Parachains finality module. +//! +//! This module needs to be deployed with GRANDPA module, which is syncing relay +//! chain blocks. The main entry point of this module is `submit_parachain_heads`, which +//! accepts storage proof of some parachain `Heads` entries from bridged relay chain. +//! It requires corresponding relay headers to be already synced. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use weights::WeightInfo; +pub use weights_ext::WeightInfoExt; + +use bp_header_chain::HeaderChain; +use bp_parachains::{parachain_head_storage_key_at_source, ParaInfo}; +use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; +use bp_runtime::{HashOf, HeaderOf, Parachain, StorageProofError}; +use codec::Decode; +use frame_support::{dispatch::PostDispatchInfo, traits::Contains}; +use sp_runtime::traits::Header as HeaderT; +use sp_std::{marker::PhantomData, vec::Vec}; + +// Re-export in crate namespace for `construct_runtime!`. +pub use pallet::*; + +pub mod weights; +pub mod weights_ext; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +mod extension; +#[cfg(test)] +mod mock; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-parachains"; + +/// Block hash of the bridged relay chain. +pub type RelayBlockHash = bp_polkadot_core::Hash; +/// Block number of the bridged relay chain. +pub type RelayBlockNumber = bp_polkadot_core::BlockNumber; +/// Hasher of the bridged relay chain. +pub type RelayBlockHasher = bp_polkadot_core::Hasher; + +/// Artifacts of the parachains head update. +struct UpdateParachainHeadArtifacts { + /// New best head of the parachain. + pub best_head: ParaInfo, + /// If `true`, some old parachain head has been pruned during update. + pub prune_happened: bool, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use bp_parachains::{BestParaHeadHash, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider}; + use bp_runtime::{ + BasicOperatingMode, BoundedStorageValue, OwnedBridgeModule, StorageDoubleMapKeyProvider, + StorageMapKeyProvider, + }; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + /// Stored parachain head of given parachains pallet. + pub type StoredParaHeadOf = + BoundedStorageValue<>::MaxParaHeadSize, ParaHead>; + /// Weight info of the given parachains pallet. + pub type WeightInfoOf = >::WeightInfo; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// The caller has provided head of parachain that the pallet is not configured to track. + UntrackedParachainRejected { parachain: ParaId }, + /// The caller has declared that he has provided given parachain head, but it is missing + /// from the storage proof. + MissingParachainHead { parachain: ParaId }, + /// The caller has provided parachain head hash that is not matching the hash read from the + /// storage proof. + IncorrectParachainHeadHash { + parachain: ParaId, + parachain_head_hash: ParaHash, + actual_parachain_head_hash: ParaHash, + }, + /// The caller has provided obsolete parachain head, which is already known to the pallet. + RejectedObsoleteParachainHead { parachain: ParaId, parachain_head_hash: ParaHash }, + /// The caller has provided parachain head that exceeds the maximal configured head size. + RejectedLargeParachainHead { + parachain: ParaId, + parachain_head_hash: ParaHash, + parachain_head_size: u32, + }, + /// Parachain head has been updated. + UpdatedParachainHead { parachain: ParaId, parachain_head_hash: ParaHash }, + } + + #[pallet::error] + pub enum Error { + /// Relay chain block hash is unknown to us. + UnknownRelayChainBlock, + /// The number of stored relay block is different from what the relayer has provided. + InvalidRelayChainBlockNumber, + /// Invalid storage proof has been passed. + InvalidStorageProof, + /// Given parachain head is unknown. + UnknownParaHead, + /// The storage proof doesn't contains storage root. So it is invalid for given header. + StorageRootMismatch, + /// Failed to extract state root from given parachain head. + FailedToExtractStateRoot, + /// Error generated by the `OwnedBridgeModule` trait. + BridgeModule(bp_runtime::OwnedBridgeModuleError), + } + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: + pallet_bridge_grandpa::Config + { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + /// Benchmarks results from runtime we're plugged into. + type WeightInfo: WeightInfoExt; + + /// Instance of bridges GRANDPA pallet (within this runtime) that this pallet is linked to. + /// + /// The GRANDPA pallet instance must be configured to import headers of relay chain that + /// we're interested in. + type BridgesGrandpaPalletInstance: 'static; + + /// Name of the original `paras` pallet in the `construct_runtime!()` call at the bridged + /// chain. + /// + /// Please keep in mind that this should be the name of the `runtime_parachains::paras` + /// pallet from polkadot repository, not the `pallet-bridge-parachains`. + #[pallet::constant] + type ParasPalletName: Get<&'static str>; + + /// Set of parachains that are tracked by this pallet. + /// + /// The set may be extended easily, without requiring any runtime upgrades. Removing tracked + /// parachain requires special handling - pruning existing heads and cleaning related data + /// structures. + type TrackedParachains: Contains; + + /// Maximal number of single parachain heads to keep in the storage. + /// + /// The setting is there to prevent growing the on-chain state indefinitely. Note + /// the setting does not relate to parachain block numbers - we will simply keep as much + /// items in the storage, so it doesn't guarantee any fixed timeframe for heads. + /// + /// Incautious change of this constant may lead to orphan entries in the runtime storage. + #[pallet::constant] + type HeadsToKeep: Get; + + /// Maximal size (in bytes) of the SCALE-encoded parachain head. + /// + /// Keep in mind that the size of any tracked parachain header must not exceed this value. + /// So if you're going to track multiple parachains, one of which is storing large digests + /// in its headers, you shall choose this maximal value. + /// + /// There's no mandatory headers in this pallet, so it can't stall if there's some header + /// that exceeds this bound. + #[pallet::constant] + type MaxParaHeadSize: Get; + } + + /// Optional pallet owner. + /// + /// Pallet owner has a right to halt all pallet operations and then resume them. If it is + /// `None`, then there are no direct ways to halt/resume pallet operations, but other + /// runtime methods may still be used to do that (i.e. democracy::referendum to update halt + /// flag directly or call the `halt_operations`). + #[pallet::storage] + pub type PalletOwner, I: 'static = ()> = + StorageValue<_, T::AccountId, OptionQuery>; + + /// The current operating mode of the pallet. + /// + /// Depending on the mode either all, or no transactions will be allowed. + #[pallet::storage] + pub type PalletOperatingMode, I: 'static = ()> = + StorageValue<_, BasicOperatingMode, ValueQuery>; + + /// Parachains info. + /// + /// Contains the following info: + /// - best parachain head hash + /// - the head of the `ImportedParaHashes` ring buffer + #[pallet::storage] + pub type ParasInfo, I: 'static = ()> = StorageMap< + _, + ::Hasher, + ::Key, + ::Value, + >; + + /// Parachain heads which have been imported into the pallet. + #[pallet::storage] + pub type ImportedParaHeads, I: 'static = ()> = StorageDoubleMap< + _, + ::Hasher1, + ::Key1, + ::Hasher2, + ::Key2, + StoredParaHeadOf, + >; + + /// A ring buffer of imported parachain head hashes. Ordered by the insertion time. + #[pallet::storage] + pub(super) type ImportedParaHashes, I: 'static = ()> = + StorageDoubleMap<_, Blake2_128Concat, ParaId, Twox64Concat, u32, ParaHash>; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); + + impl, I: 'static> OwnedBridgeModule for Pallet { + const LOG_TARGET: &'static str = LOG_TARGET; + type OwnerStorage = PalletOwner; + type OperatingMode = BasicOperatingMode; + type OperatingModeStorage = PalletOperatingMode; + } + + #[pallet::call] + impl, I: 'static> Pallet + where + >::BridgedChain: + bp_runtime::Chain< + BlockNumber = RelayBlockNumber, + Hash = RelayBlockHash, + Hasher = RelayBlockHasher, + >, + { + /// Submit proof of one or several parachain heads. + /// + /// The proof is supposed to be proof of some `Heads` entries from the + /// `polkadot-runtime-parachains::paras` pallet instance, deployed at the bridged chain. + /// The proof is supposed to be crafted at the `relay_header_hash` that must already be + /// imported by corresponding GRANDPA pallet at this chain. + #[pallet::weight(WeightInfoOf::::submit_parachain_heads_weight( + T::DbWeight::get(), + parachain_heads_proof, + parachains.len() as _, + ))] + pub fn submit_parachain_heads( + _origin: OriginFor, + at_relay_block: (RelayBlockNumber, RelayBlockHash), + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + ) -> DispatchResultWithPostInfo { + Self::ensure_not_halted().map_err(Error::::BridgeModule)?; + // we'll need relay chain header to verify that parachains heads are always increasing. + let (relay_block_number, relay_block_hash) = at_relay_block; + let relay_block = pallet_bridge_grandpa::ImportedHeaders::< + T, + T::BridgesGrandpaPalletInstance, + >::get(relay_block_hash) + .ok_or(Error::::UnknownRelayChainBlock)?; + ensure!( + *relay_block.number() == relay_block_number, + Error::::InvalidRelayChainBlockNumber, + ); + + // now parse storage proof and read parachain heads + let mut actual_weight = WeightInfoOf::::submit_parachain_heads_weight( + T::DbWeight::get(), + ¶chain_heads_proof, + parachains.len() as _, + ); + + pallet_bridge_grandpa::Pallet::::parse_finalized_storage_proof( + relay_block_hash, + sp_trie::StorageProof::new(parachain_heads_proof.0), + move |storage| { + for (parachain, parachain_head_hash) in parachains { + // if we're not tracking this parachain, we'll just ignore its head proof here + if !T::TrackedParachains::contains(¶chain) { + log::trace!( + target: LOG_TARGET, + "The head of parachain {:?} has been provided, but it is not tracked by the pallet", + parachain, + ); + Self::deposit_event(Event::UntrackedParachainRejected { parachain }); + continue; + } + + let parachain_head = match Pallet::::read_parachain_head(&storage, parachain) { + Ok(Some(parachain_head)) => parachain_head, + Ok(None) => { + log::trace!( + target: LOG_TARGET, + "The head of parachain {:?} is None. {}", + parachain, + if ParasInfo::::contains_key(parachain) { + "Looks like it is not yet registered at the source relay chain" + } else { + "Looks like it has been deregistered from the source relay chain" + }, + ); + Self::deposit_event(Event::MissingParachainHead { parachain }); + continue; + }, + Err(e) => { + log::trace!( + target: LOG_TARGET, + "The read of head of parachain {:?} has failed: {:?}", + parachain, + e, + ); + Self::deposit_event(Event::MissingParachainHead { parachain }); + continue; + }, + }; + + // if relayer has specified invalid parachain head hash, ignore the head + // (this isn't strictly necessary, but better safe than sorry) + let actual_parachain_head_hash = parachain_head.hash(); + if parachain_head_hash != actual_parachain_head_hash { + log::trace!( + target: LOG_TARGET, + "The submitter has specified invalid parachain {:?} head hash: {:?} vs {:?}", + parachain, + parachain_head_hash, + actual_parachain_head_hash, + ); + Self::deposit_event(Event::IncorrectParachainHeadHash { + parachain, + parachain_head_hash, + actual_parachain_head_hash, + }); + continue; + } + + let update_result: Result<_, ()> = ParasInfo::::try_mutate(parachain, |stored_best_head| { + let artifacts = Pallet::::update_parachain_head( + parachain, + stored_best_head.take(), + relay_block_number, + parachain_head, + parachain_head_hash, + )?; + *stored_best_head = Some(artifacts.best_head); + Ok(artifacts.prune_happened) + }); + + // we're refunding weight if update has not happened and if pruning has not happened + let is_update_happened = matches!(update_result, Ok(_)); + if !is_update_happened { + actual_weight = actual_weight + .saturating_sub(WeightInfoOf::::parachain_head_storage_write_weight(T::DbWeight::get())); + } + let is_prune_happened = matches!(update_result, Ok(true)); + if !is_prune_happened { + actual_weight = actual_weight + .saturating_sub(WeightInfoOf::::parachain_head_pruning_weight(T::DbWeight::get())); + } + } + }, + ) + .map_err(|_| Error::::InvalidStorageProof)?; + + Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + } + + impl, I: 'static> Pallet { + /// Get best finalized header of the given parachain. + pub fn best_parachain_head(parachain: ParaId) -> Option { + let best_para_head_hash = ParasInfo::::get(parachain)?.best_head_hash.head_hash; + ImportedParaHeads::::get(parachain, best_para_head_hash).map(|h| h.into_inner()) + } + + /// Get parachain head with given hash. + pub fn parachain_head(parachain: ParaId, hash: ParaHash) -> Option { + ImportedParaHeads::::get(parachain, hash).map(|h| h.into_inner()) + } + + /// Read parachain head from storage proof. + fn read_parachain_head( + storage: &bp_runtime::StorageProofChecker, + parachain: ParaId, + ) -> Result, StorageProofError> { + let parachain_head_key = + parachain_head_storage_key_at_source(T::ParasPalletName::get(), parachain); + storage.read_and_decode_value(parachain_head_key.0.as_ref()) + } + + /// Check if para head has been already updated at better relay chain block. + /// Without this check, we may import heads in random order. + /// + /// Returns `true` if the pallet is ready to import given parachain head. + /// Returns `false` if the pallet already knows the same or better parachain head. + #[must_use] + pub fn validate_updated_parachain_head( + parachain: ParaId, + maybe_stored_best_head: &Option, + updated_at_relay_block_number: RelayBlockNumber, + updated_head_hash: ParaHash, + err_log_prefix: &str, + ) -> bool { + let stored_best_head = match maybe_stored_best_head { + Some(stored_best_head) => stored_best_head, + None => return true, + }; + + if stored_best_head.best_head_hash.at_relay_block_number >= + updated_at_relay_block_number + { + log::trace!( + target: LOG_TARGET, + "{}. The parachain head for {:?} was already updated at better relay chain block {} >= {}.", + err_log_prefix, + parachain, + stored_best_head.best_head_hash.at_relay_block_number, + updated_at_relay_block_number + ); + return false + } + + if stored_best_head.best_head_hash.head_hash == updated_head_hash { + log::trace!( + target: LOG_TARGET, + "{}. The parachain head hash for {:?} was already updated to {} at block {} < {}.", + err_log_prefix, + parachain, + updated_head_hash, + stored_best_head.best_head_hash.at_relay_block_number, + updated_at_relay_block_number + ); + return false + } + + true + } + + /// Try to update parachain head. + pub(super) fn update_parachain_head( + parachain: ParaId, + stored_best_head: Option, + updated_at_relay_block_number: RelayBlockNumber, + updated_head: ParaHead, + updated_head_hash: ParaHash, + ) -> Result { + // check if head has been already updated at better relay chain block. Without this + // check, we may import heads in random order + let err_log_prefix = "The parachain head can't be updated"; + let is_valid = Self::validate_updated_parachain_head( + parachain, + &stored_best_head, + updated_at_relay_block_number, + updated_head_hash, + err_log_prefix, + ); + if !is_valid { + Self::deposit_event(Event::RejectedObsoleteParachainHead { + parachain, + parachain_head_hash: updated_head_hash, + }); + return Err(()) + } + + // verify that the parachain head size is <= `MaxParaHeadSize` + let updated_head = match StoredParaHeadOf::::try_from_inner(updated_head) { + Ok(updated_head) => updated_head, + Err(e) => { + log::trace!( + target: LOG_TARGET, + "{}. The parachain head size for {:?} is {}. It exceeds maximal configured size {}.", + err_log_prefix, + parachain, + e.value_size, + e.maximal_size, + ); + + Self::deposit_event(Event::RejectedLargeParachainHead { + parachain, + parachain_head_hash: updated_head_hash, + parachain_head_size: e.value_size as _, + }); + + return Err(()) + }, + }; + + let next_imported_hash_position = stored_best_head + .map_or(0, |stored_best_head| stored_best_head.next_imported_hash_position); + + // insert updated best parachain head + let head_hash_to_prune = + ImportedParaHashes::::try_get(parachain, next_imported_hash_position); + let updated_best_para_head = ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: updated_at_relay_block_number, + head_hash: updated_head_hash, + }, + next_imported_hash_position: (next_imported_hash_position + 1) % + T::HeadsToKeep::get(), + }; + ImportedParaHashes::::insert( + parachain, + next_imported_hash_position, + updated_head_hash, + ); + ImportedParaHeads::::insert(parachain, updated_head_hash, updated_head); + log::trace!( + target: LOG_TARGET, + "Updated head of parachain {:?} to {}", + parachain, + updated_head_hash, + ); + + // remove old head + let prune_happened = head_hash_to_prune.is_ok(); + if let Ok(head_hash_to_prune) = head_hash_to_prune { + log::trace!( + target: LOG_TARGET, + "Pruning old head of parachain {:?}: {}", + parachain, + head_hash_to_prune, + ); + ImportedParaHeads::::remove(parachain, head_hash_to_prune); + } + Self::deposit_event(Event::UpdatedParachainHead { + parachain, + parachain_head_hash: updated_head_hash, + }); + + Ok(UpdateParachainHeadArtifacts { best_head: updated_best_para_head, prune_happened }) + } + } + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + /// Initial pallet operating mode. + pub operating_mode: BasicOperatingMode, + /// Initial pallet owner. + pub owner: Option, + /// Dummy marker. + pub phantom: sp_std::marker::PhantomData, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + operating_mode: Default::default(), + owner: Default::default(), + phantom: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + PalletOperatingMode::::put(self.operating_mode); + if let Some(ref owner) = self.owner { + PalletOwner::::put(owner); + } + } + } +} + +/// Single parachain header chain adapter. +pub struct ParachainHeaders(PhantomData<(T, I, C)>); + +impl, I: 'static, C: Parachain> HeaderChain + for ParachainHeaders +{ + fn finalized_header(hash: HashOf) -> Option> { + Pallet::::parachain_head(ParaId(C::PARACHAIN_ID), hash) + .and_then(|head| Decode::decode(&mut &head.0[..]).ok()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{ + run_test, test_relay_header, RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime, + MAXIMAL_PARACHAIN_HEAD_SIZE, PARAS_PALLET_NAME, UNTRACKED_PARACHAIN_ID, + }; + use codec::Encode; + + use bp_parachains::{BestParaHeadHash, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider}; + use bp_runtime::{ + record_all_trie_keys, BasicOperatingMode, OwnedBridgeModuleError, + StorageDoubleMapKeyProvider, StorageMapKeyProvider, + }; + use bp_test_utils::{ + authority_list, generate_owned_bridge_module_tests, make_default_justification, + }; + use frame_support::{ + assert_noop, assert_ok, + dispatch::DispatchResultWithPostInfo, + storage::generator::{StorageDoubleMap, StorageMap}, + traits::{Get, OnInitialize}, + weights::Weight, + }; + use frame_system::{EventRecord, Pallet as System, Phase}; + use sp_runtime::DispatchError; + use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, Recorder, TrieMut}; + + type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1; + type WeightInfo = ::WeightInfo; + type DbWeight = ::DbWeight; + + fn initialize(state_root: RelayBlockHash) { + pallet_bridge_grandpa::Pallet::::initialize( + RuntimeOrigin::root(), + bp_header_chain::InitializationData { + header: Box::new(test_relay_header(0, state_root)), + authority_list: authority_list(), + set_id: 1, + operating_mode: BasicOperatingMode::Normal, + }, + ) + .unwrap(); + } + + fn proceed(num: RelayBlockNumber, state_root: RelayBlockHash) { + pallet_bridge_grandpa::Pallet::::on_initialize( + 0, + ); + + let header = test_relay_header(num, state_root); + let justification = make_default_justification(&header); + assert_ok!( + pallet_bridge_grandpa::Pallet::::submit_finality_proof( + RuntimeOrigin::signed(1), + Box::new(header), + justification, + ) + ); + } + + fn prepare_parachain_heads_proof( + heads: Vec<(u32, ParaHead)>, + ) -> (RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>) { + let mut parachains = Vec::with_capacity(heads.len()); + let mut root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = TrieDBMutBuilderV1::::new(&mut mdb, &mut root).build(); + for (parachain, head) in heads { + let storage_key = + parachain_head_storage_key_at_source(PARAS_PALLET_NAME, ParaId(parachain)); + trie.insert(&storage_key.0, &head.encode()) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in tests"); + parachains.push((ParaId(parachain), head.hash())); + } + } + + // generate storage proof to be delivered to This chain + let mut proof_recorder = Recorder::>::new(); + record_all_trie_keys::, _>(&mdb, &root, &mut proof_recorder) + .map_err(|_| "record_all_trie_keys has failed") + .expect("record_all_trie_keys should not fail in benchmarks"); + let storage_proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect(); + + (root, ParaHeadsProof(storage_proof), parachains) + } + + fn initial_best_head(parachain: u32) -> ParaInfo { + ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(parachain, 0).hash(), + }, + next_imported_hash_position: 1, + } + } + + fn head_data(parachain: u32, head_number: u32) -> ParaHead { + ParaHead((parachain, head_number).encode()) + } + + fn large_head_data(parachain: u32, head_number: u32) -> ParaHead { + ParaHead( + (parachain, head_number, vec![42u8; MAXIMAL_PARACHAIN_HEAD_SIZE as usize]).encode(), + ) + } + + fn head_hash(parachain: u32, head_number: u32) -> ParaHash { + head_data(parachain, head_number).hash() + } + + fn import_parachain_1_head( + relay_chain_block: RelayBlockNumber, + relay_state_root: RelayBlockHash, + parachains: Vec<(ParaId, ParaHash)>, + proof: ParaHeadsProof, + ) -> DispatchResultWithPostInfo { + Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_chain_block, test_relay_header(relay_chain_block, relay_state_root).hash()), + parachains, + proof, + ) + } + + fn weight_of_import_parachain_1_head(proof: &ParaHeadsProof, prune_expected: bool) -> Weight { + let db_weight = ::DbWeight::get(); + WeightInfoOf::::submit_parachain_heads_weight(db_weight, proof, 1) + .saturating_sub(if prune_expected { + Weight::zero() + } else { + WeightInfoOf::::parachain_head_pruning_weight(db_weight) + }) + } + + #[test] + fn submit_parachain_heads_checks_operating_mode() { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 0))]); + + run_test(|| { + initialize(state_root); + + // `submit_parachain_heads()` should fail when the pallet is halted. + PalletOperatingMode::::put(BasicOperatingMode::Halted); + assert_noop!( + Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains.clone(), + proof.clone(), + ), + Error::::BridgeModule(OwnedBridgeModuleError::Halted) + ); + + // `submit_parachain_heads()` should succeed now that the pallet is resumed. + PalletOperatingMode::::put(BasicOperatingMode::Normal); + assert_ok!(Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains, + proof, + ),); + }); + } + + #[test] + fn imports_initial_parachain_heads() { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 0)), (3, head_data(3, 10))]); + run_test(|| { + initialize(state_root); + + // we're trying to update heads of parachains 1, 2 and 3 + let expected_weight = + WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 2); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains, + proof, + ); + assert_ok!(result); + assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); + + // but only 1 and 2 are updated, because proof is missing head of parachain#2 + assert_eq!(ParasInfo::::get(ParaId(1)), Some(initial_best_head(1))); + assert_eq!(ParasInfo::::get(ParaId(2)), None); + assert_eq!( + ParasInfo::::get(ParaId(3)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(3, 10).hash() + }, + next_imported_hash_position: 1, + }) + ); + + assert_eq!( + ImportedParaHeads::::get( + ParaId(1), + initial_best_head(1).best_head_hash.head_hash + ) + .map(|h| h.into_inner()), + Some(head_data(1, 0)) + ); + assert_eq!( + ImportedParaHeads::::get( + ParaId(2), + initial_best_head(2).best_head_hash.head_hash + ) + .map(|h| h.into_inner()), + None + ); + assert_eq!( + ImportedParaHeads::::get(ParaId(3), head_hash(3, 10)) + .map(|h| h.into_inner()), + Some(head_data(3, 10)) + ); + + assert_eq!( + System::::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: initial_best_head(1).best_head_hash.head_hash, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(3), + parachain_head_hash: head_data(3, 10).hash(), + }), + topics: vec![], + } + ], + ); + }); + } + + #[test] + fn imports_parachain_heads_is_able_to_progress() { + let (state_root_5, proof_5, parachains_5) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 5))]); + let (state_root_10, proof_10, parachains_10) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 10))]); + run_test(|| { + // start with relay block #0 and import head#5 of parachain#1 + initialize(state_root_5); + assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5)); + assert_eq!( + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(1, 5).hash() + }, + next_imported_hash_position: 1, + }) + ); + assert_eq!( + ImportedParaHeads::::get(ParaId(1), head_data(1, 5).hash()) + .map(|h| h.into_inner()), + Some(head_data(1, 5)) + ); + assert_eq!( + ImportedParaHeads::::get(ParaId(1), head_data(1, 10).hash()) + .map(|h| h.into_inner()), + None + ); + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 5).hash(), + }), + topics: vec![], + }], + ); + + // import head#10 of parachain#1 at relay block #1 + proceed(1, state_root_10); + assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10)); + assert_eq!( + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 1, + head_hash: head_data(1, 10).hash() + }, + next_imported_hash_position: 2, + }) + ); + assert_eq!( + ImportedParaHeads::::get(ParaId(1), head_data(1, 5).hash()) + .map(|h| h.into_inner()), + Some(head_data(1, 5)) + ); + assert_eq!( + ImportedParaHeads::::get(ParaId(1), head_data(1, 10).hash()) + .map(|h| h.into_inner()), + Some(head_data(1, 10)) + ); + assert_eq!( + System::::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 5).hash(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 10).hash(), + }), + topics: vec![], + } + ], + ); + }); + } + + #[test] + fn ignores_untracked_parachain() { + let (state_root, proof, parachains) = prepare_parachain_heads_proof(vec![ + (1, head_data(1, 5)), + (UNTRACKED_PARACHAIN_ID, head_data(1, 5)), + (2, head_data(1, 5)), + ]); + run_test(|| { + // start with relay block #0 and try to import head#5 of parachain#1 and untracked + // parachain + let expected_weight = + WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 3) + .saturating_sub(WeightInfo::parachain_head_storage_write_weight( + DbWeight::get(), + )); + initialize(state_root); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains, + proof, + ); + assert_ok!(result); + assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); + assert_eq!( + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(1, 5).hash() + }, + next_imported_hash_position: 1, + }) + ); + assert_eq!(ParasInfo::::get(ParaId(UNTRACKED_PARACHAIN_ID)), None,); + assert_eq!( + ParasInfo::::get(ParaId(2)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(1, 5).hash() + }, + next_imported_hash_position: 1, + }) + ); + assert_eq!( + System::::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 5).hash(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UntrackedParachainRejected { + parachain: ParaId(UNTRACKED_PARACHAIN_ID), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(2), + parachain_head_hash: head_data(1, 5).hash(), + }), + topics: vec![], + } + ], + ); + }); + } + + #[test] + fn does_nothing_when_already_imported_this_head_at_previous_relay_header() { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 0))]); + run_test(|| { + // import head#0 of parachain#1 at relay block#0 + initialize(state_root); + assert_ok!(import_parachain_1_head(0, state_root, parachains.clone(), proof.clone())); + assert_eq!(ParasInfo::::get(ParaId(1)), Some(initial_best_head(1))); + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: initial_best_head(1).best_head_hash.head_hash, + }), + topics: vec![], + }], + ); + + // try to import head#0 of parachain#1 at relay block#1 + // => call succeeds, but nothing is changed + proceed(1, state_root); + assert_ok!(import_parachain_1_head(1, state_root, parachains, proof)); + assert_eq!(ParasInfo::::get(ParaId(1)), Some(initial_best_head(1))); + assert_eq!( + System::::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: initial_best_head(1).best_head_hash.head_hash, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead { + parachain: ParaId(1), + parachain_head_hash: initial_best_head(1).best_head_hash.head_hash, + }), + topics: vec![], + } + ], + ); + }); + } + + #[test] + fn does_nothing_when_already_imported_head_at_better_relay_header() { + let (state_root_5, proof_5, parachains_5) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 5))]); + let (state_root_10, proof_10, parachains_10) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 10))]); + run_test(|| { + // start with relay block #0 + initialize(state_root_5); + + // head#10 of parachain#1 at relay block#1 + proceed(1, state_root_10); + assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10)); + assert_eq!( + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 1, + head_hash: head_data(1, 10).hash() + }, + next_imported_hash_position: 1, + }) + ); + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 10).hash(), + }), + topics: vec![], + }], + ); + + // now try to import head#5 at relay block#0 + // => nothing is changed, because better head has already been imported + assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5)); + assert_eq!( + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 1, + head_hash: head_data(1, 10).hash() + }, + next_imported_hash_position: 1, + }) + ); + assert_eq!( + System::::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 10).hash(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 5).hash(), + }), + topics: vec![], + } + ], + ); + }); + } + + #[test] + fn does_nothing_when_parachain_head_is_too_large() { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 5)), (2, large_head_data(1, 5))]); + run_test(|| { + // start with relay block #0 and try to import head#5 of parachain#1 and untracked + // parachain + initialize(state_root); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains, + proof, + ); + assert_ok!(result); + assert_eq!( + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(1, 5).hash() + }, + next_imported_hash_position: 1, + }) + ); + assert_eq!(ParasInfo::::get(ParaId(2)), None); + assert_eq!( + System::::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::UpdatedParachainHead { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 5).hash(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::RejectedLargeParachainHead { + parachain: ParaId(2), + parachain_head_hash: large_head_data(1, 5).hash(), + parachain_head_size: large_head_data(1, 5).encoded_size() as u32, + }), + topics: vec![], + }, + ], + ); + }); + } + + #[test] + fn prunes_old_heads() { + run_test(|| { + let heads_to_keep = crate::mock::HeadsToKeep::get(); + + // import exactly `HeadsToKeep` headers + for i in 0..heads_to_keep { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof(vec![(1, head_data(1, i))]); + if i == 0 { + initialize(state_root); + } else { + proceed(i, state_root); + } + + let expected_weight = weight_of_import_parachain_1_head(&proof, false); + let result = import_parachain_1_head(i, state_root, parachains, proof); + assert_ok!(result); + assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); + } + + // nothing is pruned yet + for i in 0..heads_to_keep { + assert!(ImportedParaHeads::::get(ParaId(1), head_data(1, i).hash()) + .is_some()); + } + + // import next relay chain header and next parachain head + let (state_root, proof, parachains) = + prepare_parachain_heads_proof(vec![(1, head_data(1, heads_to_keep))]); + proceed(heads_to_keep, state_root); + let expected_weight = weight_of_import_parachain_1_head(&proof, true); + let result = import_parachain_1_head(heads_to_keep, state_root, parachains, proof); + assert_ok!(result); + assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); + + // and the head#0 is pruned + assert!( + ImportedParaHeads::::get(ParaId(1), head_data(1, 0).hash()).is_none() + ); + for i in 1..=heads_to_keep { + assert!(ImportedParaHeads::::get(ParaId(1), head_data(1, i).hash()) + .is_some()); + } + }); + } + + #[test] + fn fails_on_unknown_relay_chain_block() { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 5))]); + run_test(|| { + // start with relay block #0 + initialize(state_root); + + // try to import head#5 of parachain#1 at unknown relay chain block #1 + assert_noop!( + import_parachain_1_head(1, state_root, parachains, proof), + Error::::UnknownRelayChainBlock + ); + }); + } + + #[test] + fn fails_on_invalid_storage_proof() { + let (_state_root, proof, parachains) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 5))]); + run_test(|| { + // start with relay block #0 + initialize(Default::default()); + + // try to import head#5 of parachain#1 at relay chain block #0 + assert_noop!( + import_parachain_1_head(0, Default::default(), parachains, proof), + Error::::InvalidStorageProof + ); + }); + } + + #[test] + fn is_not_rewriting_existing_head_if_failed_to_read_updated_head() { + let (state_root_5, proof_5, parachains_5) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 5))]); + let (state_root_10_at_20, proof_10_at_20, parachains_10_at_20) = + prepare_parachain_heads_proof(vec![(2, head_data(2, 10))]); + let (state_root_10_at_30, proof_10_at_30, parachains_10_at_30) = + prepare_parachain_heads_proof(vec![(1, head_data(1, 10))]); + run_test(|| { + // we've already imported head#5 of parachain#1 at relay block#10 + initialize(state_root_5); + import_parachain_1_head(0, state_root_5, parachains_5, proof_5).expect("ok"); + assert_eq!( + Pallet::::best_parachain_head(ParaId(1)), + Some(head_data(1, 5)) + ); + + // then if someone is pretending to provide updated head#10 of parachain#1 at relay + // block#20, but fails to do that + // + // => we'll leave previous value + proceed(20, state_root_10_at_20); + assert_ok!(Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (20, test_relay_header(20, state_root_10_at_20).hash()), + parachains_10_at_20, + proof_10_at_20, + ),); + assert_eq!( + Pallet::::best_parachain_head(ParaId(1)), + Some(head_data(1, 5)) + ); + + // then if someone is pretending to provide updated head#10 of parachain#1 at relay + // block#30, and actualy provides it + // + // => we'll update value + proceed(30, state_root_10_at_30); + assert_ok!(Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (30, test_relay_header(30, state_root_10_at_30).hash()), + parachains_10_at_30, + proof_10_at_30, + ),); + assert_eq!( + Pallet::::best_parachain_head(ParaId(1)), + Some(head_data(1, 10)) + ); + }); + } + + #[test] + fn storage_keys_computed_properly() { + assert_eq!( + ParasInfo::::storage_map_final_key(ParaId(42)).to_vec(), + ParasInfoKeyProvider::final_key("Parachains", &ParaId(42)).0 + ); + + assert_eq!( + ImportedParaHeads::::storage_double_map_final_key( + ParaId(42), + ParaHash::from([21u8; 32]) + ) + .to_vec(), + ImportedParaHeadsKeyProvider::final_key( + "Parachains", + &ParaId(42), + &ParaHash::from([21u8; 32]) + ) + .0, + ); + } + + #[test] + fn ignores_parachain_head_if_it_is_missing_from_storage_proof() { + let (state_root, proof, _) = prepare_parachain_heads_proof(vec![(1, head_data(1, 0))]); + let parachains = vec![(ParaId(2), Default::default())]; + run_test(|| { + initialize(state_root); + assert_ok!(Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains, + proof, + )); + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::MissingParachainHead { + parachain: ParaId(2), + }), + topics: vec![], + }], + ); + }); + } + + #[test] + fn ignores_parachain_head_if_parachain_head_hash_is_wrong() { + let (state_root, proof, _) = prepare_parachain_heads_proof(vec![(1, head_data(1, 0))]); + let parachains = vec![(ParaId(1), head_data(1, 10).hash())]; + run_test(|| { + initialize(state_root); + assert_ok!(Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains, + proof, + )); + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Parachains(Event::IncorrectParachainHeadHash { + parachain: ParaId(1), + parachain_head_hash: head_data(1, 10).hash(), + actual_parachain_head_hash: head_data(1, 0).hash(), + }), + topics: vec![], + }], + ); + }); + } + + generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted); +} diff --git a/modules/parachains/src/mock.rs b/modules/parachains/src/mock.rs new file mode 100644 index 00000000000..9638472aa98 --- /dev/null +++ b/modules/parachains/src/mock.rs @@ -0,0 +1,194 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use bp_polkadot_core::parachains::ParaId; +use bp_runtime::Chain; +use frame_support::{construct_runtime, parameter_types, traits::IsInVec, weights::Weight}; +use sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, Header as HeaderT, IdentityLookup}, + Perbill, +}; + +use crate as pallet_bridge_parachains; + +pub type AccountId = u64; +pub type TestNumber = u64; + +pub type RelayBlockHeader = + sp_runtime::generic::Header; + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +pub const PARAS_PALLET_NAME: &str = "Paras"; +pub const UNTRACKED_PARACHAIN_ID: u32 = 10; +pub const MAXIMAL_PARACHAIN_HEAD_SIZE: u32 = 512; + +construct_runtime! { + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Grandpa1: pallet_bridge_grandpa::::{Pallet}, + Grandpa2: pallet_bridge_grandpa::::{Pallet}, + Parachains: pallet_bridge_parachains::{Call, Pallet, Event}, + } +} + +parameter_types! { + pub const BlockHashCount: TestNumber = 250; + pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = TestNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type DbWeight = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const MaxRequests: u32 = 2; + pub const HeadersToKeep: u32 = 5; + pub const SessionLength: u64 = 5; + pub const NumValidators: u32 = 5; +} + +impl pallet_bridge_grandpa::Config for TestRuntime { + type BridgedChain = TestBridgedChain; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type MaxBridgedAuthorities = frame_support::traits::ConstU32<5>; + type MaxBridgedHeaderSize = frame_support::traits::ConstU32<512>; + type WeightInfo = (); +} + +impl pallet_bridge_grandpa::Config for TestRuntime { + type BridgedChain = TestBridgedChain; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type MaxBridgedAuthorities = frame_support::traits::ConstU32<5>; + type MaxBridgedHeaderSize = frame_support::traits::ConstU32<512>; + type WeightInfo = (); +} + +parameter_types! { + pub const HeadsToKeep: u32 = 4; + pub const ParasPalletName: &'static str = PARAS_PALLET_NAME; + pub GetTenFirstParachains: Vec = (0..10).map(ParaId).collect(); +} + +impl pallet_bridge_parachains::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1; + type ParasPalletName = ParasPalletName; + type TrackedParachains = IsInVec; + type HeadsToKeep = HeadsToKeep; + type MaxParaHeadSize = frame_support::traits::ConstU32; +} + +#[derive(Debug)] +pub struct TestBridgedChain; + +impl Chain for TestBridgedChain { + type BlockNumber = crate::RelayBlockNumber; + type Hash = crate::RelayBlockHash; + type Hasher = crate::RelayBlockHasher; + type Header = RelayBlockHeader; + + type AccountId = AccountId; + type Balance = u32; + type Index = u32; + type Signature = sp_runtime::testing::TestSignature; + + fn max_extrinsic_size() -> u32 { + unreachable!() + } + + fn max_extrinsic_weight() -> Weight { + unreachable!() + } +} + +#[derive(Debug)] +pub struct OtherBridgedChain; + +impl Chain for OtherBridgedChain { + type BlockNumber = u64; + type Hash = crate::RelayBlockHash; + type Hasher = crate::RelayBlockHasher; + type Header = sp_runtime::generic::Header; + + type AccountId = AccountId; + type Balance = u32; + type Index = u32; + type Signature = sp_runtime::testing::TestSignature; + + fn max_extrinsic_size() -> u32 { + unreachable!() + } + + fn max_extrinsic_weight() -> Weight { + unreachable!() + } +} + +pub fn run_test(test: impl FnOnce() -> T) -> T { + sp_io::TestExternalities::new(Default::default()).execute_with(|| { + System::set_block_number(1); + System::reset_events(); + test() + }) +} + +pub fn test_relay_header( + num: crate::RelayBlockNumber, + state_root: crate::RelayBlockHash, +) -> RelayBlockHeader { + RelayBlockHeader::new( + num, + Default::default(), + state_root, + Default::default(), + Default::default(), + ) +} diff --git a/modules/parachains/src/weights.rs b/modules/parachains/src/weights.rs new file mode 100644 index 00000000000..5c9206d0ca0 --- /dev/null +++ b/modules/parachains/src/weights.rs @@ -0,0 +1,101 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Autogenerated weights for `pallet_bridge_parachains` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-17, STEPS: 50, REPEAT: 20 +//! LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled +//! CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/millau-bridge-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_bridge_parachains +// --extrinsic=* +// --execution=wasm +// --wasm-execution=Compiled +// --heap-pages=4096 +// --output=./modules/parachains/src/weights.rs +// --template=./.maintain/millau-weight-template.hbs + +#![allow(clippy::all)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for `pallet_bridge_parachains`. +pub trait WeightInfo { + fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight; + fn submit_parachain_heads_with_1kb_proof() -> Weight; + fn submit_parachain_heads_with_16kb_proof() -> Weight; +} + +/// Weights for `pallet_bridge_parachains` that are generated using one of the Bridge testnets. +/// +/// Those weights are test only and must never be used in production. +pub struct BridgeWeight(PhantomData); +impl WeightInfo for BridgeWeight { + fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight { + Weight::from_ref_time(51_173_000 as u64) + .saturating_add(Weight::from_ref_time(24_495_968 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(p as u64))) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(p as u64))) + } + fn submit_parachain_heads_with_1kb_proof() -> Weight { + Weight::from_ref_time(58_175_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + fn submit_parachain_heads_with_16kb_proof() -> Weight { + Weight::from_ref_time(101_796_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight { + Weight::from_ref_time(51_173_000 as u64) + .saturating_add(Weight::from_ref_time(24_495_968 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(p as u64))) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(p as u64))) + } + fn submit_parachain_heads_with_1kb_proof() -> Weight { + Weight::from_ref_time(58_175_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn submit_parachain_heads_with_16kb_proof() -> Weight { + Weight::from_ref_time(101_796_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/modules/parachains/src/weights_ext.rs b/modules/parachains/src/weights_ext.rs new file mode 100644 index 00000000000..eecdfe90359 --- /dev/null +++ b/modules/parachains/src/weights_ext.rs @@ -0,0 +1,107 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Weight-related utilities. + +use crate::weights::{BridgeWeight, WeightInfo}; + +use bp_runtime::Size; +use frame_support::weights::{RuntimeDbWeight, Weight}; + +/// Size of the regular parachain head. +/// +/// It's not that we are expecting all parachain heads to share the same size or that we would +/// reject all heads that have larger/lesser size. It is about head size that we use in benchmarks. +/// Relayer would need to pay additional fee for extra bytes. +/// +/// 384 is a bit larger (1.3 times) than the size of the randomly chosen Polkadot block. +pub const DEFAULT_PARACHAIN_HEAD_SIZE: u32 = 384; + +/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at +/// the Rialto chain. +pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; + +/// Extended weight info. +pub trait WeightInfoExt: WeightInfo { + /// Storage proof overhead, that is included in every storage proof. + /// + /// The relayer would pay some extra fee for additional proof bytes, since they mean + /// more hashing operations. + fn expected_extra_storage_proof_size() -> u32; + + /// Weight of the parachain heads delivery extrinsic. + fn submit_parachain_heads_weight( + db_weight: RuntimeDbWeight, + proof: &impl Size, + parachains_count: u32, + ) -> Weight { + // weight of the `submit_parachain_heads` with exactly `parachains_count` parachain + // heads of the default size (`DEFAULT_PARACHAIN_HEAD_SIZE`) + let base_weight = Self::submit_parachain_heads_with_n_parachains(parachains_count); + + // overhead because of extra storage proof bytes + let expected_proof_size = parachains_count + .saturating_mul(DEFAULT_PARACHAIN_HEAD_SIZE) + .saturating_add(Self::expected_extra_storage_proof_size()); + let actual_proof_size = proof.size(); + let proof_size_overhead = Self::storage_proof_size_overhead( + actual_proof_size.saturating_sub(expected_proof_size), + ); + + // potential pruning weight (refunded if hasn't happened) + let pruning_weight = + Self::parachain_head_pruning_weight(db_weight).saturating_mul(parachains_count as u64); + + base_weight.saturating_add(proof_size_overhead).saturating_add(pruning_weight) + } + + /// Returns weight of single parachain head storage update. + /// + /// This weight only includes db write operations that happens if parachain head is actually + /// updated. All extra weights (weight of storage proof validation, additional checks, ...) is + /// not included. + fn parachain_head_storage_write_weight(db_weight: RuntimeDbWeight) -> Weight { + // it's just a couple of operations - we need to write the hash (`ImportedParaHashes`) and + // the head itself (`ImportedParaHeads`. Pruning is not included here + db_weight.writes(2) + } + + /// Returns weight of single parachain head pruning. + fn parachain_head_pruning_weight(db_weight: RuntimeDbWeight) -> Weight { + // it's just one write operation, we don't want any benchmarks for that + db_weight.writes(1) + } + + /// Returns weight that needs to be accounted when storage proof of given size is received. + fn storage_proof_size_overhead(extra_proof_bytes: u32) -> Weight { + let extra_byte_weight = (Self::submit_parachain_heads_with_16kb_proof() - + Self::submit_parachain_heads_with_1kb_proof()) / + (15 * 1024); + extra_byte_weight.saturating_mul(extra_proof_bytes as u64) + } +} + +impl WeightInfoExt for () { + fn expected_extra_storage_proof_size() -> u32 { + EXTRA_STORAGE_PROOF_SIZE + } +} + +impl WeightInfoExt for BridgeWeight { + fn expected_extra_storage_proof_size() -> u32 { + EXTRA_STORAGE_PROOF_SIZE + } +} diff --git a/modules/relayers/Cargo.toml b/modules/relayers/Cargo.toml new file mode 100644 index 00000000000..00d1bb59120 --- /dev/null +++ b/modules/relayers/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "pallet-bridge-relayers" +description = "Module used to store relayer rewards and coordinate relayers set." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Bridge dependencies + +bp-messages = { path = "../../primitives/messages", default-features = false } +bp-relayers = { path = "../../primitives/relayers", default-features = false } +pallet-bridge-messages = { path = "../messages", default-features = false } + +# Substrate Dependencies + +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +bp-runtime = { path = "../../primitives/runtime" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = ["std"] +std = [ + "bp-messages/std", + "bp-relayers/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-bridge-messages/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", +] diff --git a/modules/relayers/src/benchmarking.rs b/modules/relayers/src/benchmarking.rs new file mode 100644 index 00000000000..706454e0497 --- /dev/null +++ b/modules/relayers/src/benchmarking.rs @@ -0,0 +1,40 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Benchmarks for the relayers Pallet. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::*; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_system::RawOrigin; + +/// Reward amount that is (hopefully) is larger than existential deposit across all chains. +const REWARD_AMOUNT: u32 = u32::MAX; + +benchmarks! { + // Benchmark `claim_rewards` call. + claim_rewards { + let relayer: T::AccountId = whitelisted_caller(); + RelayerRewards::::insert(&relayer, T::Reward::from(REWARD_AMOUNT)); + }: _(RawOrigin::Signed(relayer)) + verify { + // we can't check anything here, because `PaymentProcedure` is responsible for + // payment logic, so we assume that if call has succeeded, the procedure has + // also completed successfully + } +} diff --git a/modules/relayers/src/lib.rs b/modules/relayers/src/lib.rs new file mode 100644 index 00000000000..f3195f1288e --- /dev/null +++ b/modules/relayers/src/lib.rs @@ -0,0 +1,197 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Runtime module that is used to store relayer rewards and (in the future) to +//! coordinate relations between relayers. + +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +use bp_relayers::PaymentProcedure; +use sp_arithmetic::traits::AtLeast32BitUnsigned; +use sp_std::marker::PhantomData; +use weights::WeightInfo; + +pub use pallet::*; +pub use payment_adapter::MessageDeliveryAndDispatchPaymentAdapter; + +mod benchmarking; +mod mock; +mod payment_adapter; + +pub mod weights; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-relayers"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Type of relayer reward. + type Reward: AtLeast32BitUnsigned + Copy + Parameter + MaxEncodedLen; + /// Pay rewards adapter. + type PaymentProcedure: PaymentProcedure; + /// Pallet call weights. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); + + #[pallet::call] + impl Pallet { + /// Claim accumulated rewards. + #[pallet::weight(T::WeightInfo::claim_rewards())] + pub fn claim_rewards(origin: OriginFor) -> DispatchResult { + let relayer = ensure_signed(origin)?; + + RelayerRewards::::try_mutate_exists(&relayer, |maybe_reward| -> DispatchResult { + let reward = maybe_reward.take().ok_or(Error::::NoRewardForRelayer)?; + T::PaymentProcedure::pay_reward(&relayer, reward).map_err(|e| { + log::trace!( + target: LOG_TARGET, + "Failed to pay rewards to {:?}: {:?}", + relayer, + e, + ); + Error::::FailedToPayReward + })?; + + Self::deposit_event(Event::::RewardPaid { relayer: relayer.clone(), reward }); + Ok(()) + }) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Reward has been paid to the relayer. + RewardPaid { + /// Relayer account that has been rewarded. + relayer: T::AccountId, + /// Reward amount. + reward: T::Reward, + }, + } + + #[pallet::error] + pub enum Error { + /// No reward can be claimed by given relayer. + NoRewardForRelayer, + /// Reward payment procedure has failed. + FailedToPayReward, + } + + /// Map of the relayer => accumulated reward. + #[pallet::storage] + pub type RelayerRewards = + StorageMap<_, Blake2_128Concat, T::AccountId, T::Reward, OptionQuery>; +} + +#[cfg(test)] +mod tests { + use super::*; + use mock::{RuntimeEvent as TestEvent, *}; + + use crate::Event::RewardPaid; + use frame_support::{assert_noop, assert_ok, traits::fungible::Inspect}; + use frame_system::{EventRecord, Pallet as System, Phase}; + use sp_runtime::DispatchError; + + fn get_ready_for_events() { + System::::set_block_number(1); + System::::reset_events(); + } + + #[test] + fn root_cant_claim_anything() { + run_test(|| { + assert_noop!( + Pallet::::claim_rewards(RuntimeOrigin::root()), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn relayer_cant_claim_if_no_reward_exists() { + run_test(|| { + assert_noop!( + Pallet::::claim_rewards(RuntimeOrigin::signed(REGULAR_RELAYER)), + Error::::NoRewardForRelayer, + ); + }); + } + + #[test] + fn relayer_cant_claim_if_payment_procedure_fails() { + run_test(|| { + RelayerRewards::::insert(FAILING_RELAYER, 100); + assert_noop!( + Pallet::::claim_rewards(RuntimeOrigin::signed(FAILING_RELAYER)), + Error::::FailedToPayReward, + ); + }); + } + + #[test] + fn relayer_can_claim_reward() { + run_test(|| { + get_ready_for_events(); + + RelayerRewards::::insert(REGULAR_RELAYER, 100); + assert_ok!(Pallet::::claim_rewards(RuntimeOrigin::signed( + REGULAR_RELAYER + ))); + assert_eq!(RelayerRewards::::get(REGULAR_RELAYER), None); + + //Check if the `RewardPaid` event was emitted. + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::Relayers(RewardPaid { + relayer: REGULAR_RELAYER, + reward: 100 + }), + topics: vec![], + }], + ); + }); + } + + #[test] + fn mint_reward_payment_procedure_actually_mints_tokens() { + type Balances = pallet_balances::Pallet; + + run_test(|| { + assert_eq!(Balances::balance(&1), 0); + assert_eq!(Balances::total_issuance(), 0); + bp_relayers::MintReward::::pay_reward(&1, 100).unwrap(); + assert_eq!(Balances::balance(&1), 100); + assert_eq!(Balances::total_issuance(), 100); + }); + } +} diff --git a/modules/relayers/src/mock.rs b/modules/relayers/src/mock.rs new file mode 100644 index 00000000000..7cf5575f5f3 --- /dev/null +++ b/modules/relayers/src/mock.rs @@ -0,0 +1,152 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg(test)] + +use crate as pallet_bridge_relayers; + +use bp_messages::{source_chain::ForbidOutboundMessages, target_chain::ForbidInboundMessages}; +use bp_relayers::PaymentProcedure; +use frame_support::{parameter_types, weights::RuntimeDbWeight}; +use sp_core::H256; +use sp_runtime::{ + testing::Header as SubstrateHeader, + traits::{BlakeTwo256, IdentityLookup}, +}; + +pub type AccountId = u64; +pub type Balance = u64; + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +frame_support::construct_runtime! { + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Event}, + Messages: pallet_bridge_messages::{Pallet, Event}, + Relayers: pallet_bridge_relayers::{Pallet, Call, Event}, + } +} + +parameter_types! { + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 }; +} + +impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = SubstrateHeader; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = DbWeight; + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_balances::Config for TestRuntime { + type MaxLocks = (); + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = frame_support::traits::ConstU64<1>; + type AccountStore = frame_system::Pallet; + type WeightInfo = (); + type MaxReserves = (); + type ReserveIdentifier = (); +} + +parameter_types! { + pub const TestBridgedChainId: bp_runtime::ChainId = *b"test"; + pub ActiveOutboundLanes: &'static [bp_messages::LaneId] = &[[0, 0, 0, 0]]; +} + +// we're not testing messages pallet here, so values in this config might be crazy +impl pallet_bridge_messages::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type ActiveOutboundLanes = ActiveOutboundLanes; + type MaxUnrewardedRelayerEntriesAtInboundLane = frame_support::traits::ConstU64<8>; + type MaxUnconfirmedMessagesAtInboundLane = frame_support::traits::ConstU64<8>; + + type MaximalOutboundPayloadSize = frame_support::traits::ConstU32<1024>; + type OutboundPayload = (); + + type InboundPayload = (); + type InboundRelayer = AccountId; + + type TargetHeaderChain = ForbidOutboundMessages; + type LaneMessageVerifier = ForbidOutboundMessages; + type MessageDeliveryAndDispatchPayment = (); + + type SourceHeaderChain = ForbidInboundMessages; + type MessageDispatch = ForbidInboundMessages; + type BridgedChainId = TestBridgedChainId; +} + +impl pallet_bridge_relayers::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type Reward = Balance; + type PaymentProcedure = TestPaymentProcedure; + type WeightInfo = (); +} + +/// Regular relayer that may receive rewards. +pub const REGULAR_RELAYER: AccountId = 1; + +/// Relayer that can't receive rewards. +pub const FAILING_RELAYER: AccountId = 2; + +/// Payment procedure that rejects payments to the `FAILING_RELAYER`. +pub struct TestPaymentProcedure; + +impl PaymentProcedure for TestPaymentProcedure { + type Error = (); + + fn pay_reward(relayer: &AccountId, _reward: Balance) -> Result<(), Self::Error> { + match *relayer { + FAILING_RELAYER => Err(()), + _ => Ok(()), + } + } +} + +/// Run pallet test. +pub fn run_test(test: impl FnOnce() -> T) -> T { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(test) +} diff --git a/modules/relayers/src/payment_adapter.rs b/modules/relayers/src/payment_adapter.rs new file mode 100644 index 00000000000..772d07d2ad9 --- /dev/null +++ b/modules/relayers/src/payment_adapter.rs @@ -0,0 +1,166 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Code that allows relayers pallet to be used as a delivery+dispatch payment mechanism +//! for the messages pallet. + +use crate::{Config, RelayerRewards}; + +use bp_messages::source_chain::{MessageDeliveryAndDispatchPayment, RelayersRewards}; +use frame_support::sp_runtime::SaturatedConversion; +use sp_arithmetic::traits::{Saturating, UniqueSaturatedFrom, Zero}; +use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive}; + +/// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism +/// for the messages pallet. +pub struct MessageDeliveryAndDispatchPaymentAdapter( + PhantomData<(T, MessagesInstance)>, +); + +impl MessageDeliveryAndDispatchPayment + for MessageDeliveryAndDispatchPaymentAdapter +where + T: Config + pallet_bridge_messages::Config, + MessagesInstance: 'static, +{ + type Error = &'static str; + + fn pay_relayers_rewards( + _lane_id: bp_messages::LaneId, + messages_relayers: VecDeque>, + confirmation_relayer: &T::AccountId, + received_range: &RangeInclusive, + ) { + let relayers_rewards = pallet_bridge_messages::calc_relayers_rewards::( + messages_relayers, + received_range, + ); + + register_relayers_rewards::( + confirmation_relayer, + relayers_rewards, + // TODO (https://github.com/paritytech/parity-bridges-common/issues/1318): this shall be fixed + // in some way. ATM the future of the `register_relayers_rewards` is not yet known + 100_000_u32.into(), + 10_000_u32.into(), + ); + } +} + +// Update rewards to given relayers, optionally rewarding confirmation relayer. +fn register_relayers_rewards( + confirmation_relayer: &T::AccountId, + relayers_rewards: RelayersRewards, + delivery_fee: T::Reward, + confirmation_fee: T::Reward, +) { + // reward every relayer except `confirmation_relayer` + let mut confirmation_relayer_reward = T::Reward::zero(); + for (relayer, messages) in relayers_rewards { + // sane runtime configurations guarantee that the number of messages will be below + // `u32::MAX` + let mut relayer_reward = + T::Reward::unique_saturated_from(messages).saturating_mul(delivery_fee); + + if relayer != *confirmation_relayer { + // If delivery confirmation is submitted by other relayer, let's deduct confirmation fee + // from relayer reward. + // + // If confirmation fee has been increased (or if it was the only component of message + // fee), then messages relayer may receive zero reward. + let mut confirmation_reward = + T::Reward::saturated_from(messages).saturating_mul(confirmation_fee); + confirmation_reward = sp_std::cmp::min(confirmation_reward, relayer_reward); + relayer_reward = relayer_reward.saturating_sub(confirmation_reward); + confirmation_relayer_reward = + confirmation_relayer_reward.saturating_add(confirmation_reward); + register_relayer_reward::(&relayer, relayer_reward); + } else { + // If delivery confirmation is submitted by this relayer, let's add confirmation fee + // from other relayers to this relayer reward. + confirmation_relayer_reward = + confirmation_relayer_reward.saturating_add(relayer_reward); + } + } + + // finally - pay reward to confirmation relayer + register_relayer_reward::(confirmation_relayer, confirmation_relayer_reward); +} + +/// Remember that the reward shall be paid to the relayer. +fn register_relayer_reward(relayer: &T::AccountId, reward: T::Reward) { + if reward.is_zero() { + return + } + + RelayerRewards::::mutate(relayer, |old_reward: &mut Option| { + let new_reward = old_reward.unwrap_or_else(Zero::zero).saturating_add(reward); + *old_reward = Some(new_reward); + + log::trace!( + target: crate::LOG_TARGET, + "Relayer {:?} can now claim reward: {:?}", + relayer, + new_reward, + ); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + + const RELAYER_1: AccountId = 1; + const RELAYER_2: AccountId = 2; + const RELAYER_3: AccountId = 3; + + fn relayers_rewards() -> RelayersRewards { + vec![(RELAYER_1, 2), (RELAYER_2, 3)].into_iter().collect() + } + + #[test] + fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() { + run_test(|| { + register_relayers_rewards::(&RELAYER_2, relayers_rewards(), 50, 10); + + assert_eq!(RelayerRewards::::get(RELAYER_1), Some(80)); + assert_eq!(RelayerRewards::::get(RELAYER_2), Some(170)); + }); + } + + #[test] + fn confirmation_relayer_is_rewarded_if_it_has_not_delivered_any_delivered_messages() { + run_test(|| { + register_relayers_rewards::(&RELAYER_3, relayers_rewards(), 50, 10); + + assert_eq!(RelayerRewards::::get(RELAYER_1), Some(80)); + assert_eq!(RelayerRewards::::get(RELAYER_2), Some(120)); + assert_eq!(RelayerRewards::::get(RELAYER_3), Some(50)); + }); + } + + #[test] + fn only_confirmation_relayer_is_rewarded_if_confirmation_fee_has_significantly_increased() { + run_test(|| { + register_relayers_rewards::(&RELAYER_3, relayers_rewards(), 50, 1000); + + assert_eq!(RelayerRewards::::get(RELAYER_1), None); + assert_eq!(RelayerRewards::::get(RELAYER_2), None); + assert_eq!(RelayerRewards::::get(RELAYER_3), Some(250)); + }); + } +} diff --git a/modules/relayers/src/weights.rs b/modules/relayers/src/weights.rs new file mode 100644 index 00000000000..7bcd8711a9e --- /dev/null +++ b/modules/relayers/src/weights.rs @@ -0,0 +1,75 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Autogenerated weights for `pallet_bridge_relayers` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-17, STEPS: 50, REPEAT: 20 +//! LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled +//! CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/millau-bridge-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_bridge_relayers +// --extrinsic=* +// --execution=wasm +// --wasm-execution=Compiled +// --heap-pages=4096 +// --output=./modules/relayers/src/weights.rs +// --template=./.maintain/millau-weight-template.hbs + +#![allow(clippy::all)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for `pallet_bridge_relayers`. +pub trait WeightInfo { + fn claim_rewards() -> Weight; +} + +/// Weights for `pallet_bridge_relayers` that are generated using one of the Bridge testnets. +/// +/// Those weights are test only and must never be used in production. +pub struct BridgeWeight(PhantomData); +impl WeightInfo for BridgeWeight { + fn claim_rewards() -> Weight { + Weight::from_ref_time(59_334_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn claim_rewards() -> Weight { + Weight::from_ref_time(59_334_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +} diff --git a/modules/shift-session-manager/Cargo.toml b/modules/shift-session-manager/Cargo.toml new file mode 100644 index 00000000000..5dae3e00fd3 --- /dev/null +++ b/modules/shift-session-manager/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "pallet-shift-session-manager" +description = "A Substrate Runtime module that selects 2/3 of initial validators for every session" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-staking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "pallet-session/std", + "scale-info/std", + "sp-staking/std", + "sp-std/std", +] diff --git a/modules/shift-session-manager/src/lib.rs b/modules/shift-session-manager/src/lib.rs new file mode 100644 index 00000000000..073013d92d0 --- /dev/null +++ b/modules/shift-session-manager/src/lib.rs @@ -0,0 +1,268 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate session manager that selects 2/3 validators from initial set, +//! starting from session 2. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::{ValidatorSet, ValidatorSetWithIdentification}; +use sp_std::prelude::*; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: pallet_session::Config {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} + + /// Validators of first two sessions. + #[pallet::storage] + pub(super) type InitialValidators = StorageValue<_, Vec>; +} + +impl ValidatorSet for Pallet { + type ValidatorId = T::ValidatorId; + type ValidatorIdOf = T::ValidatorIdOf; + + fn session_index() -> sp_staking::SessionIndex { + pallet_session::Pallet::::current_index() + } + + fn validators() -> Vec { + pallet_session::Pallet::::validators() + } +} + +impl ValidatorSetWithIdentification for Pallet { + type Identification = (); + type IdentificationOf = (); +} + +impl pallet_session::SessionManager for Pallet { + fn end_session(_: sp_staking::SessionIndex) {} + fn start_session(_: sp_staking::SessionIndex) {} + fn new_session(session_index: sp_staking::SessionIndex) -> Option> { + // we don't want to add even more fields to genesis config => just return None + if session_index == 0 || session_index == 1 { + return None + } + + // the idea that on first call (i.e. when session 1 ends) we're reading current + // set of validators from session module (they are initial validators) and save + // in our 'local storage'. + // then for every session we select (deterministically) 2/3 of these initial + // validators to serve validators of new session + let available_validators = InitialValidators::::get().unwrap_or_else(|| { + let validators = >::validators(); + InitialValidators::::put(validators.clone()); + validators + }); + + Some(Self::select_validators(session_index, &available_validators)) + } +} + +impl Pallet { + /// Select validators for session. + fn select_validators( + session_index: sp_staking::SessionIndex, + available_validators: &[T::ValidatorId], + ) -> Vec { + let available_validators_count = available_validators.len(); + let count = sp_std::cmp::max(1, 2 * available_validators_count / 3); + let offset = session_index as usize % available_validators_count; + let end = offset + count; + let session_validators = match end.overflowing_sub(available_validators_count) { + (wrapped_end, false) if wrapped_end != 0 => available_validators[offset..] + .iter() + .chain(available_validators[..wrapped_end].iter()) + .cloned() + .collect(), + _ => available_validators[offset..end].to_vec(), + }; + + session_validators + } +} + +#[cfg(test)] +mod tests { + // From construct_runtime macro + #![allow(clippy::from_over_into)] + + use super::*; + use frame_support::{ + parameter_types, + sp_io::TestExternalities, + sp_runtime::{ + testing::{Header, UintAuthorityId}, + traits::{BlakeTwo256, ConvertInto, IdentityLookup}, + Perbill, RuntimeAppPublic, + }, + traits::GenesisBuild, + weights::Weight, + BasicExternalities, + }; + use sp_core::H256; + + type AccountId = u64; + + type Block = frame_system::mocking::MockBlock; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + + frame_support::construct_runtime! { + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Session: pallet_session::{Pallet}, + } + } + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + } + + impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = (); + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + parameter_types! { + pub const Period: u64 = 1; + pub const Offset: u64 = 0; + } + + impl pallet_session::Config for TestRuntime { + type RuntimeEvent = (); + type ValidatorId = ::AccountId; + type ValidatorIdOf = ConvertInto; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = (); + type SessionHandler = TestSessionHandler; + type Keys = UintAuthorityId; + type WeightInfo = (); + } + + impl Config for TestRuntime {} + + pub struct TestSessionHandler; + impl pallet_session::SessionHandler for TestSessionHandler { + const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[UintAuthorityId::ID]; + + fn on_genesis_session(_validators: &[(AccountId, Ks)]) { + } + + fn on_new_session( + _: bool, + _: &[(AccountId, Ks)], + _: &[(AccountId, Ks)], + ) { + } + + fn on_disabled(_: u32) {} + } + + fn new_test_ext() -> TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let keys = vec![ + (1, 1, UintAuthorityId(1)), + (2, 2, UintAuthorityId(2)), + (3, 3, UintAuthorityId(3)), + (4, 4, UintAuthorityId(4)), + (5, 5, UintAuthorityId(5)), + ]; + + BasicExternalities::execute_with_storage(&mut t, || { + for (ref k, ..) in &keys { + frame_system::Pallet::::inc_providers(k); + } + }); + + pallet_session::GenesisConfig:: { keys } + .assimilate_storage(&mut t) + .unwrap(); + TestExternalities::new(t) + } + + #[test] + fn shift_session_manager_works() { + new_test_ext().execute_with(|| { + let all_accs = vec![1, 2, 3, 4, 5]; + + // at least 1 validator is selected + assert_eq!(Pallet::::select_validators(0, &[1]), vec![1],); + + // at session#0, shift is also 0 + assert_eq!(Pallet::::select_validators(0, &all_accs), vec![1, 2, 3],); + + // at session#1, shift is also 1 + assert_eq!(Pallet::::select_validators(1, &all_accs), vec![2, 3, 4],); + + // at session#3, we're wrapping + assert_eq!(Pallet::::select_validators(3, &all_accs), vec![4, 5, 1],); + + // at session#5, we're starting from the beginning again + assert_eq!(Pallet::::select_validators(5, &all_accs), vec![1, 2, 3],); + }); + } +} diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml new file mode 100644 index 00000000000..6b169e66113 --- /dev/null +++ b/primitives/beefy/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "bp-beefy" +description = "Primitives of pallet-bridge-beefy module." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "bit-vec"] } +scale-info = { version = "2.0.1", default-features = false, features = ["bit-vec", "derive"] } +serde = { version = "1.0", optional = true } +static_assertions = "1.1" + +# Bridge Dependencies + +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Dependencies + +beefy-merkle-tree = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "beefy-primitives/std", + "bp-runtime/std", + "codec/std", + "frame-support/std", + "pallet-beefy-mmr/std", + "pallet-mmr/std", + "scale-info/std", + "serde", + "sp-application-crypto/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std" +] diff --git a/primitives/beefy/src/lib.rs b/primitives/beefy/src/lib.rs new file mode 100644 index 00000000000..a0a096bdce1 --- /dev/null +++ b/primitives/beefy/src/lib.rs @@ -0,0 +1,152 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives that are used to interact with BEEFY bridge pallet. + +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +pub use beefy_merkle_tree::{merkle_root, Keccak256 as BeefyKeccak256}; +pub use beefy_primitives::{ + crypto::{AuthorityId as EcdsaValidatorId, AuthoritySignature as EcdsaValidatorSignature}, + known_payloads::MMR_ROOT_ID as MMR_ROOT_PAYLOAD_ID, + mmr::{BeefyAuthoritySet, MmrLeafVersion}, + BeefyAuthorityId, BeefyVerify, Commitment, Payload as BeefyPayload, SignedCommitment, + ValidatorSet, ValidatorSetId, BEEFY_ENGINE_ID, +}; +pub use pallet_beefy_mmr::BeefyEcdsaToEthereum; +pub use pallet_mmr::{ + primitives::{DataOrHash as MmrDataOrHash, Proof as MmrProof}, + verify_leaves_proof as verify_mmr_leaves_proof, +}; + +use bp_runtime::{BasicOperatingMode, BlockNumberOf, Chain, HashOf}; +use codec::{Decode, Encode}; +use frame_support::Parameter; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Convert, MaybeSerializeDeserialize}, + RuntimeDebug, +}; +use sp_std::prelude::*; + +/// Substrate-based chain with BEEFY && MMR pallets deployed. +/// +/// Both BEEFY and MMR pallets and their clients may be configured to use different +/// primitives. Some of types can be configured in low-level pallets, but are constrained +/// when BEEFY+MMR bundle is used. +pub trait ChainWithBeefy: Chain { + /// The hashing algorithm used to compute the digest of the BEEFY commitment. + /// + /// Corresponds to the hashing algorithm, used by `beefy_gadget::BeefyKeystore`. + type CommitmentHasher: sp_runtime::traits::Hash; + + /// The hashing algorithm used to build the MMR. + /// + /// The same algorithm is also used to compute merkle roots in BEEFY + /// (e.g. validator addresses root in leaf data). + /// + /// Corresponds to the `Hashing` field of the `pallet-mmr` configuration. + type MmrHashing: sp_runtime::traits::Hash; + + /// The output type of the hashing algorithm used to build the MMR. + /// + /// This type is actually stored in the MMR. + + /// Corresponds to the `Hash` field of the `pallet-mmr` configuration. + type MmrHash: sp_std::hash::Hash + + Parameter + + Copy + + AsRef<[u8]> + + Default + + MaybeSerializeDeserialize; + + /// The type expected for the MMR leaf extra data. + type BeefyMmrLeafExtra: Parameter; + + /// A way to identify a BEEFY validator. + /// + /// Corresponds to the `BeefyId` field of the `pallet-beefy` configuration. + type AuthorityId: BeefyAuthorityId + Parameter; + + /// The signature type used by BEEFY. + /// + /// Corresponds to the `BeefyId` field of the `pallet-beefy` configuration. + type Signature: BeefyVerify + Parameter; + + /// A way to convert validator id to its raw representation in the BEEFY merkle tree. + /// + /// Corresponds to the `BeefyAuthorityToMerkleLeaf` field of the `pallet-beefy-mmr` + /// configuration. + type AuthorityIdToMerkleLeaf: Convert>; +} + +/// BEEFY validator id used by given Substrate chain. +pub type BeefyAuthorityIdOf = ::AuthorityId; +/// BEEFY validator set, containing both validator identifiers and the numeric set id. +pub type BeefyAuthoritySetOf = ValidatorSet>; +/// BEEFY authority set, containing both validator identifiers and the numeric set id. +pub type BeefyAuthoritySetInfoOf = beefy_primitives::mmr::BeefyAuthoritySet>; +/// BEEFY validator signature used by given Substrate chain. +pub type BeefyValidatorSignatureOf = ::Signature; +/// Signed BEEFY commitment used by given Substrate chain. +pub type BeefySignedCommitmentOf = + SignedCommitment, BeefyValidatorSignatureOf>; +/// Hash algorithm, used to compute the digest of the BEEFY commitment before signing it. +pub type BeefyCommitmentHasher = ::CommitmentHasher; +/// Hash algorithm used in Beefy MMR construction by given Substrate chain. +pub type MmrHashingOf = ::MmrHashing; +/// Hash type, used in MMR construction by given Substrate chain. +pub type MmrHashOf = ::MmrHash; +/// BEEFY MMR proof type used by the given Substrate chain. +pub type MmrProofOf = MmrProof>; +/// The type of the MMR leaf extra data used by the given Substrate chain. +pub type BeefyMmrLeafExtraOf = ::BeefyMmrLeafExtra; +/// A way to convert a validator id to its raw representation in the BEEFY merkle tree, used by +/// the given Substrate chain. +pub type BeefyAuthorityIdToMerkleLeafOf = ::AuthorityIdToMerkleLeaf; +/// Actual type of leafs in the BEEFY MMR. +pub type BeefyMmrLeafOf = beefy_primitives::mmr::MmrLeaf< + BlockNumberOf, + HashOf, + MmrHashOf, + BeefyMmrLeafExtraOf, +>; + +/// Data required for initializing the BEEFY pallet. +/// +/// Provides the initial context that the bridge needs in order to know +/// where to start the sync process from. +#[derive(Encode, Decode, RuntimeDebug, PartialEq, Clone, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct InitializationData { + /// Pallet operating mode. + pub operating_mode: BasicOperatingMode, + /// Number of the best block, finalized by BEEFY. + pub best_block_number: BlockNumber, + /// BEEFY authority set that will be finalizing descendants of the `best_beefy_block_number` + /// block. + pub authority_set: BeefyAuthoritySet, +} + +/// Basic data, stored by the pallet for every imported commitment. +#[derive(Encode, Decode, RuntimeDebug, PartialEq, TypeInfo)] +pub struct ImportedCommitment { + /// Block number and hash of the finalized block parent. + pub parent_number_and_hash: (BlockNumber, BlockHash), + /// MMR root at the imported block. + pub mmr_root: MmrHash, +} diff --git a/primitives/chain-bridge-hub-rococo/Cargo.toml b/primitives/chain-bridge-hub-rococo/Cargo.toml new file mode 100644 index 00000000000..51bc88baf0e --- /dev/null +++ b/primitives/chain-bridge-hub-rococo/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "bp-bridge-hub-rococo" +description = "Primitives of BridgeHubRococo parachain runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +smallvec = "1.10.0" + +# Bridge Dependencies + +bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } +bp-messages = { path = "../../primitives/messages", default-features = false } + +# Substrate Based Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# Polkadot Dependencies +polkadot-runtime-constants = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } + +[features] +default = ["std"] +std = [ + "bp-polkadot-core/std", + "bp-messages/std", + "bp-runtime/std", + "frame-support/std", + "sp-api/std", + "sp-std/std", + "polkadot-runtime-constants/std", +] diff --git a/primitives/chain-bridge-hub-rococo/src/lib.rs b/primitives/chain-bridge-hub-rococo/src/lib.rs new file mode 100644 index 00000000000..aa65f378542 --- /dev/null +++ b/primitives/chain-bridge-hub-rococo/src/lib.rs @@ -0,0 +1,107 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Module with configuration which reflects BridgeHubRococo runtime setup (AccountId, Headers, +//! Hashes...) + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_messages::*; +pub use bp_polkadot_core::*; +use bp_runtime::{ + decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain, +}; +use frame_support::{ + dispatch::DispatchClass, + parameter_types, + sp_runtime::{MultiAddress, MultiSigner}, + weights::{ + constants::ExtrinsicBaseWeight, WeightToFeeCoefficient, WeightToFeeCoefficients, + WeightToFeePolynomial, + }, + RuntimeDebug, +}; +use sp_std::prelude::*; + +/// BridgeHubRococo parachain. +#[derive(RuntimeDebug)] +pub struct BridgeHubRococo; + +impl Chain for BridgeHubRococo { + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; + + type AccountId = AccountId; + type Balance = Balance; + type Index = Index; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + *BlockLength::get().max.get(DispatchClass::Normal) + } + + fn max_extrinsic_weight() -> Weight { + BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or(Weight::MAX) + } +} + +impl Parachain for BridgeHubRococo { + const PARACHAIN_ID: u32 = BRIDGE_HUB_ROCOCO_PARACHAIN_ID; +} + +/// [`WeightToFee`] should reflect cumulus/bridge-hub-rococo-runtime [`WeightToFee`] +pub struct WeightToFee; +impl WeightToFeePolynomial for WeightToFee { + type Balance = Balance; + fn polynomial() -> WeightToFeeCoefficients { + pub const CENTS: Balance = polkadot_runtime_constants::currency::CENTS; + + // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: + // in BridgeHub, we map to 1/10 of that, or 1/100 CENT + let p = CENTS; + let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); + smallvec::smallvec![WeightToFeeCoefficient { + degree: 1, + negative: false, + coeff_frac: Perbill::from_rational(p % q, q), + coeff_integer: p / q, + }] + } +} + +/// Public key of the chain account that may be used to verify signatures. +pub type AccountSigner = MultiSigner; + +/// The address format for describing accounts. +pub type Address = MultiAddress; + +/// Identifier of BridgeHubRococo in the Rococo relay chain. +pub const BRIDGE_HUB_ROCOCO_PARACHAIN_ID: u32 = 1013; + +/// Name of the With-BridgeHubRococo messages pallet instance that is deployed at bridged chains. +pub const WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME: &str = "BridgeRococoMessages"; + +parameter_types! { + pub const SS58Prefix: u16 = 42; +} + +decl_bridge_finality_runtime_apis!(bridge_hub_rococo); +decl_bridge_messages_runtime_apis!(bridge_hub_rococo); diff --git a/primitives/chain-bridge-hub-wococo/Cargo.toml b/primitives/chain-bridge-hub-wococo/Cargo.toml new file mode 100644 index 00000000000..94ac501fbd5 --- /dev/null +++ b/primitives/chain-bridge-hub-wococo/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bp-bridge-hub-wococo" +description = "Primitives of BridgeHubWococo parachain runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Bridge Dependencies + +bp-bridge-hub-rococo = { path = "../chain-bridge-hub-rococo", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } +bp-messages = { path = "../../primitives/messages", default-features = false } + +# Substrate Based Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-runtime/std", + "bp-messages/std", + "frame-support/std", + "sp-api/std", + "sp-std/std", + "bp-bridge-hub-rococo/std", +] diff --git a/primitives/chain-bridge-hub-wococo/src/lib.rs b/primitives/chain-bridge-hub-wococo/src/lib.rs new file mode 100644 index 00000000000..2b0dc344f14 --- /dev/null +++ b/primitives/chain-bridge-hub-wococo/src/lib.rs @@ -0,0 +1,78 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Module with configuration which reflects BridgeHubWococo runtime setup +//! (AccountId, Headers, Hashes...) +//! +//! but actually this is just reexported BridgeHubRococo stuff, because they are supposed to be +//! identical, at least uses the same parachain runtime + +#![cfg_attr(not(feature = "std"), no_std)] + +// Re-export only what is really needed +pub use bp_bridge_hub_rococo::{ + account_info_storage_key, AccountId, AccountPublic, AccountSigner, Address, Balance, + BlockLength, BlockNumber, BlockWeights, Hash, Hasher, Hashing, Header, Index, Nonce, + SS58Prefix, Signature, SignedBlock, SignedExtensions, UncheckedExtrinsic, WeightToFee, + MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + TX_EXTRA_BYTES, +}; +use bp_messages::*; +use bp_runtime::{ + decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain, +}; +use frame_support::{dispatch::DispatchClass, RuntimeDebug}; +use sp_std::prelude::*; + +/// BridgeHubWococo parachain. +#[derive(RuntimeDebug)] +pub struct BridgeHubWococo; + +impl Chain for BridgeHubWococo { + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; + + type AccountId = AccountId; + type Balance = Balance; + type Index = Index; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + *BlockLength::get().max.get(DispatchClass::Normal) + } + + fn max_extrinsic_weight() -> Weight { + BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or(Weight::MAX) + } +} + +impl Parachain for BridgeHubWococo { + const PARACHAIN_ID: u32 = BRIDGE_HUB_WOCOCO_PARACHAIN_ID; +} + +/// Identifier of BridgeHubWococo in the Wococo relay chain. +pub const BRIDGE_HUB_WOCOCO_PARACHAIN_ID: u32 = 1014; + +/// Name of the With-BridgeHubWococo messages pallet instance that is deployed at bridged chains. +pub const WITH_BRIDGE_HUB_WOCOCO_MESSAGES_PALLET_NAME: &str = "BridgeWococoMessages"; + +decl_bridge_finality_runtime_apis!(bridge_hub_wococo); +decl_bridge_messages_runtime_apis!(bridge_hub_wococo); diff --git a/primitives/chain-kusama/Cargo.toml b/primitives/chain-kusama/Cargo.toml new file mode 100644 index 00000000000..3bca5f4c3f0 --- /dev/null +++ b/primitives/chain-kusama/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bp-kusama" +description = "Primitives of Kusama runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Bridge Dependencies + +bp-polkadot-core = { path = "../polkadot-core", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Based Dependencies + +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-polkadot-core/std", + "bp-runtime/std", + "sp-api/std", +] diff --git a/primitives/chain-kusama/src/lib.rs b/primitives/chain-kusama/src/lib.rs new file mode 100644 index 00000000000..27d5f125ee2 --- /dev/null +++ b/primitives/chain-kusama/src/lib.rs @@ -0,0 +1,30 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +pub use bp_polkadot_core::*; +use bp_runtime::decl_bridge_finality_runtime_apis; + +/// Kusama Chain +pub type Kusama = PolkadotLike; + +/// Name of the With-Kusama GRANDPA pallet instance that is deployed at bridged chains. +pub const WITH_KUSAMA_GRANDPA_PALLET_NAME: &str = "BridgeKusamaGrandpa"; + +decl_bridge_finality_runtime_apis!(kusama); diff --git a/primitives/chain-millau/Cargo.toml b/primitives/chain-millau/Cargo.toml new file mode 100644 index 00000000000..b422e1545d6 --- /dev/null +++ b/primitives/chain-millau/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "bp-millau" +description = "Primitives of Millau runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Bridge Dependencies + +bp-beefy = { path = "../beefy", default-features = false } +bp-messages = { path = "../messages", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } +fixed-hash = { version = "0.7.0", default-features = false } +hash256-std-hasher = { version = "0.15.2", default-features = false } +impl-codec = { version = "0.6", default-features = false } +impl-serde = { version = "0.3.1", optional = true } +parity-util-mem = { version = "0.12", default-features = false, features = ["primitive-types"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0", optional = true, features = ["derive"] } + +# Substrate Based Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-beefy/std", + "bp-messages/std", + "bp-runtime/std", + "fixed-hash/std", + "frame-support/std", + "frame-system/std", + "hash256-std-hasher/std", + "impl-codec/std", + "impl-serde", + "parity-util-mem/std", + "scale-info/std", + "serde", + "sp-api/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", +] diff --git a/primitives/chain-millau/src/lib.rs b/primitives/chain-millau/src/lib.rs new file mode 100644 index 00000000000..ceb4be21b81 --- /dev/null +++ b/primitives/chain-millau/src/lib.rs @@ -0,0 +1,220 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +mod millau_hash; + +use bp_beefy::ChainWithBeefy; +use bp_messages::{ + InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, +}; +use bp_runtime::{decl_bridge_runtime_apis, Chain}; +use frame_support::{ + dispatch::DispatchClass, + weights::{constants::WEIGHT_PER_SECOND, IdentityFee, Weight}, + RuntimeDebug, +}; +use frame_system::limits; +use scale_info::TypeInfo; +use sp_core::{storage::StateVersion, Hasher as HasherT}; +use sp_runtime::{ + traits::{IdentifyAccount, Verify}, + MultiSignature, MultiSigner, Perbill, +}; +use sp_std::prelude::*; +use sp_trie::{LayoutV0, LayoutV1, TrieConfiguration}; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_runtime::traits::Keccak256; + +pub use millau_hash::MillauHash; + +/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at +/// Millau chain. This mostly depends on number of entries (and their density) in the storage trie. +/// Some reserve is reserved to account future chain growth. +pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; + +/// Number of bytes, included in the signed Millau transaction apart from the encoded call itself. +/// +/// Can be computed by subtracting encoded call size from raw transaction size. +pub const TX_EXTRA_BYTES: u32 = 103; + +/// Maximum weight of single Millau block. +/// +/// This represents 0.5 seconds of compute assuming a target block time of six seconds. +// TODO: https://github.com/paritytech/parity-bridges-common/issues/1543 - remove `set_proof_size` +pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND.set_proof_size(1_000).saturating_div(2); + +/// Represents the portion of a block that will be used by Normal extrinsics. +pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +/// Maximal number of unrewarded relayer entries in Millau confirmation transaction. +pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 128; + +/// Maximal number of unconfirmed messages in Millau confirmation transaction. +pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128; + +/// The target length of a session (how often authorities change) on Millau measured in of number of +/// blocks. +/// +/// Note that since this is a target sessions may change before/after this time depending on network +/// conditions. +pub const SESSION_LENGTH: BlockNumber = 5 * time_units::MINUTES; + +/// Maximal number of GRANDPA authorities at Millau. +pub const MAX_AUTHORITIES_COUNT: u32 = 5; + +/// Maximal SCALE-encoded header size (in bytes) at Millau. +pub const MAX_HEADER_SIZE: u32 = 1024; + +/// Re-export `time_units` to make usage easier. +pub use time_units::*; + +/// Human readable time units defined in terms of number of blocks. +pub mod time_units { + use super::BlockNumber; + + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + + pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; +} + +/// Block number type used in Millau. +pub type BlockNumber = u64; + +/// Hash type used in Millau. +pub type Hash = ::Out; + +/// Type of object that can produce hashes on Millau. +pub type Hasher = BlakeTwoAndKeccak256; + +/// The header type used by Millau. +pub type Header = sp_runtime::generic::Header; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// Public key of the chain account that may be used to verify signatures. +pub type AccountSigner = MultiSigner; + +/// Balance of an account. +pub type Balance = u64; + +/// Index of a transaction in the chain. +pub type Index = u32; + +/// Weight-to-Fee type used by Millau. +pub type WeightToFee = IdentityFee; + +/// Millau chain. +#[derive(RuntimeDebug)] +pub struct Millau; + +impl Chain for Millau { + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; + + type AccountId = AccountId; + type Balance = Balance; + type Index = Index; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + *BlockLength::get().max.get(DispatchClass::Normal) + } + + fn max_extrinsic_weight() -> Weight { + BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or(Weight::MAX) + } +} + +impl ChainWithBeefy for Millau { + type CommitmentHasher = Keccak256; + type MmrHashing = Keccak256; + type MmrHash = ::Output; + type BeefyMmrLeafExtra = (); + type AuthorityId = bp_beefy::EcdsaValidatorId; + type Signature = bp_beefy::EcdsaValidatorSignature; + type AuthorityIdToMerkleLeaf = bp_beefy::BeefyEcdsaToEthereum; +} + +/// Millau Hasher (Blake2-256 ++ Keccak-256) implementation. +#[derive(PartialEq, Eq, Clone, Copy, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct BlakeTwoAndKeccak256; + +impl sp_core::Hasher for BlakeTwoAndKeccak256 { + type Out = MillauHash; + type StdHasher = hash256_std_hasher::Hash256StdHasher; + const LENGTH: usize = 64; + + fn hash(s: &[u8]) -> Self::Out { + let mut combined_hash = MillauHash::default(); + combined_hash.as_mut()[..32].copy_from_slice(&sp_io::hashing::blake2_256(s)); + combined_hash.as_mut()[32..].copy_from_slice(&sp_io::hashing::keccak_256(s)); + combined_hash + } +} + +impl sp_runtime::traits::Hash for BlakeTwoAndKeccak256 { + type Output = MillauHash; + + fn trie_root(input: Vec<(Vec, Vec)>, state_version: StateVersion) -> Self::Output { + match state_version { + StateVersion::V0 => LayoutV0::::trie_root(input), + StateVersion::V1 => LayoutV1::::trie_root(input), + } + } + + fn ordered_trie_root(input: Vec>, state_version: StateVersion) -> Self::Output { + match state_version { + StateVersion::V0 => LayoutV0::::ordered_trie_root(input), + StateVersion::V1 => LayoutV1::::ordered_trie_root(input), + } + } +} + +frame_support::parameter_types! { + pub BlockLength: limits::BlockLength = + limits::BlockLength::max_with_normal_ratio(2 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub BlockWeights: limits::BlockWeights = + limits::BlockWeights::with_sensible_defaults(MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO); +} + +/// Name of the With-Millau GRANDPA pallet instance that is deployed at bridged chains. +pub const WITH_MILLAU_GRANDPA_PALLET_NAME: &str = "BridgeMillauGrandpa"; +/// Name of the With-Millau messages pallet instance that is deployed at bridged chains. +pub const WITH_MILLAU_MESSAGES_PALLET_NAME: &str = "BridgeMillauMessages"; +/// Name of the transaction payment pallet at the Millau runtime. +pub const TRANSACTION_PAYMENT_PALLET_NAME: &str = "TransactionPayment"; + +decl_bridge_runtime_apis!(millau); diff --git a/primitives/chain-millau/src/millau_hash.rs b/primitives/chain-millau/src/millau_hash.rs new file mode 100644 index 00000000000..11968b2f282 --- /dev/null +++ b/primitives/chain-millau/src/millau_hash.rs @@ -0,0 +1,58 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use parity_util_mem::MallocSizeOf; +use scale_info::TypeInfo; +use sp_runtime::traits::CheckEqual; + +// `sp_core::H512` can't be used, because it doesn't implement `CheckEqual`, which is required +// by `frame_system::Config::Hash`. + +fixed_hash::construct_fixed_hash! { + /// Hash type used in Millau chain. + #[derive(MallocSizeOf, TypeInfo)] + pub struct MillauHash(64); +} + +#[cfg(feature = "std")] +impl_serde::impl_fixed_hash_serde!(MillauHash, 64); + +impl_codec::impl_fixed_hash_codec!(MillauHash, 64); + +impl CheckEqual for MillauHash { + #[cfg(feature = "std")] + fn check_equal(&self, other: &Self) { + use sp_core::hexdisplay::HexDisplay; + if self != other { + println!( + "Hash: given={}, expected={}", + HexDisplay::from(self.as_fixed_bytes()), + HexDisplay::from(other.as_fixed_bytes()), + ); + } + } + + #[cfg(not(feature = "std"))] + fn check_equal(&self, other: &Self) { + use frame_support::Printable; + + if self != other { + "Hash not equal".print(); + self.as_bytes().print(); + other.as_bytes().print(); + } + } +} diff --git a/primitives/chain-polkadot/Cargo.toml b/primitives/chain-polkadot/Cargo.toml new file mode 100644 index 00000000000..b26093a0570 --- /dev/null +++ b/primitives/chain-polkadot/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bp-polkadot" +description = "Primitives of Polkadot runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +# Bridge Dependencies + +bp-polkadot-core = { path = "../polkadot-core", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Based Dependencies + +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-polkadot-core/std", + "bp-runtime/std", + "sp-api/std", +] diff --git a/primitives/chain-polkadot/src/lib.rs b/primitives/chain-polkadot/src/lib.rs new file mode 100644 index 00000000000..0cada4e49a9 --- /dev/null +++ b/primitives/chain-polkadot/src/lib.rs @@ -0,0 +1,30 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +pub use bp_polkadot_core::*; +use bp_runtime::decl_bridge_finality_runtime_apis; + +/// Polkadot Chain +pub type Polkadot = PolkadotLike; + +/// Name of the With-Polkadot GRANDPA pallet instance that is deployed at bridged chains. +pub const WITH_POLKADOT_GRANDPA_PALLET_NAME: &str = "BridgePolkadotGrandpa"; + +decl_bridge_finality_runtime_apis!(polkadot); diff --git a/primitives/chain-rialto-parachain/Cargo.toml b/primitives/chain-rialto-parachain/Cargo.toml new file mode 100644 index 00000000000..a15c4092957 --- /dev/null +++ b/primitives/chain-rialto-parachain/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "bp-rialto-parachain" +description = "Primitives of Rialto parachain runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Bridge Dependencies + +bp-messages = { path = "../messages", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Based Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-messages/std", + "bp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-api/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/primitives/chain-rialto-parachain/src/lib.rs b/primitives/chain-rialto-parachain/src/lib.rs new file mode 100644 index 00000000000..82b6a12c182 --- /dev/null +++ b/primitives/chain-rialto-parachain/src/lib.rs @@ -0,0 +1,146 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +use bp_messages::{ + InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, +}; +use bp_runtime::{decl_bridge_runtime_apis, Chain, Parachain}; +use frame_support::{ + dispatch::DispatchClass, + weights::{constants::WEIGHT_PER_SECOND, IdentityFee, Weight}, + RuntimeDebug, +}; +use frame_system::limits; +use sp_core::Hasher as HasherT; +use sp_runtime::{ + traits::{BlakeTwo256, IdentifyAccount, Verify}, + MultiSignature, MultiSigner, Perbill, +}; +use sp_std::vec::Vec; + +/// Identifier of RialtoParachain in the Rialto relay chain. +/// +/// This identifier is not something that is declared either by Rialto or RialtoParachain. This +/// is an identifier of registration. So in theory it may be changed. But since bridge is going +/// to be deployed after parachain registration AND since parachain de-registration is highly +/// likely impossible, it is fine to declare this constant here. +pub const RIALTO_PARACHAIN_ID: u32 = 2000; + +/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at +/// RialtoParachain chain. This mostly depends on number of entries (and their density) in the +/// storage trie. Some reserve is reserved to account future chain growth. +pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; + +/// Can be computed by subtracting encoded call size from raw transaction size. +pub const TX_EXTRA_BYTES: u32 = 104; + +/// Maximal weight of single RialtoParachain block. +/// +/// This represents two seconds of compute assuming a target block time of six seconds. +// TODO: https://github.com/paritytech/parity-bridges-common/issues/1543 - remove `set_proof_size` +pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND.set_proof_size(1_000).saturating_mul(2); + +/// Represents the portion of a block that will be used by Normal extrinsics. +pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +/// Maximal number of unrewarded relayer entries in Rialto confirmation transaction. +pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024; + +/// Maximal number of unconfirmed messages in Rialto confirmation transaction. +pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1024; + +/// Block number type used in Rialto. +pub type BlockNumber = u32; + +/// Hash type used in Rialto. +pub type Hash = ::Out; + +/// The type of object that can produce hashes on Rialto. +pub type Hasher = BlakeTwo256; + +/// The header type used by Rialto. +pub type Header = sp_runtime::generic::Header; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// Public key of the chain account that may be used to verify signatures. +pub type AccountSigner = MultiSigner; + +/// Balance of an account. +pub type Balance = u128; + +/// An instant or duration in time. +pub type Moment = u64; + +/// Index of a transaction in the parachain. +pub type Index = u32; + +/// Weight-to-Fee type used by Rialto parachain. +pub type WeightToFee = IdentityFee; + +/// Rialto parachain. +#[derive(RuntimeDebug)] +pub struct RialtoParachain; + +impl Chain for RialtoParachain { + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; + + type AccountId = AccountId; + type Balance = Balance; + type Index = Index; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + *BlockLength::get().max.get(DispatchClass::Normal) + } + + fn max_extrinsic_weight() -> Weight { + BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or(Weight::MAX) + } +} + +impl Parachain for RialtoParachain { + const PARACHAIN_ID: u32 = RIALTO_PARACHAIN_ID; +} + +frame_support::parameter_types! { + pub BlockLength: limits::BlockLength = + limits::BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub BlockWeights: limits::BlockWeights = + limits::BlockWeights::with_sensible_defaults(MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO); +} + +/// Name of the With-Rialto-Parachain messages pallet instance that is deployed at bridged chains. +pub const WITH_RIALTO_PARACHAIN_MESSAGES_PALLET_NAME: &str = "BridgeRialtoParachainMessages"; +/// Name of the transaction payment pallet at the Rialto parachain runtime. +pub const TRANSACTION_PAYMENT_PALLET_NAME: &str = "TransactionPayment"; + +decl_bridge_runtime_apis!(rialto_parachain); diff --git a/primitives/chain-rialto/Cargo.toml b/primitives/chain-rialto/Cargo.toml new file mode 100644 index 00000000000..663f9076657 --- /dev/null +++ b/primitives/chain-rialto/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "bp-rialto" +description = "Primitives of Rialto runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Bridge Dependencies + +bp-messages = { path = "../messages", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Based Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-messages/std", + "bp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-api/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/primitives/chain-rialto/src/lib.rs b/primitives/chain-rialto/src/lib.rs new file mode 100644 index 00000000000..dfb727829d9 --- /dev/null +++ b/primitives/chain-rialto/src/lib.rs @@ -0,0 +1,180 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +use bp_messages::{ + InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, +}; +use bp_runtime::{decl_bridge_runtime_apis, Chain}; +use frame_support::{ + dispatch::DispatchClass, + weights::{constants::WEIGHT_PER_SECOND, IdentityFee, Weight}, + RuntimeDebug, +}; +use frame_system::limits; +use sp_core::Hasher as HasherT; +use sp_runtime::{ + traits::{BlakeTwo256, IdentifyAccount, Verify}, + MultiSignature, MultiSigner, Perbill, +}; +use sp_std::prelude::*; + +/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at +/// Rialto chain. This mostly depends on number of entries (and their density) in the storage trie. +/// Some reserve is reserved to account future chain growth. +pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; + +/// Number of bytes, included in the signed Rialto transaction apart from the encoded call itself. +/// +/// Can be computed by subtracting encoded call size from raw transaction size. +pub const TX_EXTRA_BYTES: u32 = 104; + +/// Maximal weight of single Rialto block. +/// +/// This represents two seconds of compute assuming a target block time of six seconds. +// TODO: https://github.com/paritytech/parity-bridges-common/issues/1543 - remove `set_proof_size` +pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND.set_proof_size(1_000).saturating_mul(2); + +/// Represents the portion of a block that will be used by Normal extrinsics. +pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +/// Maximal number of unrewarded relayer entries in Rialto confirmation transaction. +pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 1024; + +/// Maximal number of unconfirmed messages in Rialto confirmation transaction. +pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1024; + +/// The target length of a session (how often authorities change) on Rialto measured in of number of +/// blocks. +/// +/// Note that since this is a target sessions may change before/after this time depending on network +/// conditions. +pub const SESSION_LENGTH: BlockNumber = 4; + +/// Maximal number of GRANDPA authorities at Rialto. +pub const MAX_AUTHORITIES_COUNT: u32 = 5; + +/// Maximal SCALE-encoded header size (in bytes) at Rialto. +pub const MAX_HEADER_SIZE: u32 = 1024; + +/// Maximal SCALE-encoded size of parachains headers that are stored at Rialto `Paras` pallet. +pub const MAX_NESTED_PARACHAIN_HEAD_SIZE: u32 = MAX_HEADER_SIZE; + +/// Re-export `time_units` to make usage easier. +pub use time_units::*; + +/// Human readable time units defined in terms of number of blocks. +pub mod time_units { + use super::{BlockNumber, SESSION_LENGTH}; + + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + + pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; + + pub const EPOCH_DURATION_IN_SLOTS: BlockNumber = SESSION_LENGTH; + + // 1 in 4 blocks (on average, not counting collisions) will be primary babe blocks. + pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); +} + +/// Block number type used in Rialto. +pub type BlockNumber = u32; + +/// Hash type used in Rialto. +pub type Hash = ::Out; + +/// The type of object that can produce hashes on Rialto. +pub type Hasher = BlakeTwo256; + +/// The header type used by Rialto. +pub type Header = sp_runtime::generic::Header; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// Public key of the chain account that may be used to verify signatures. +pub type AccountSigner = MultiSigner; + +/// Balance of an account. +pub type Balance = u128; + +/// An instant or duration in time. +pub type Moment = u64; + +/// Index of a transaction in the chain. +pub type Index = u32; + +/// Weight-to-Fee type used by Rialto. +pub type WeightToFee = IdentityFee; + +/// Rialto chain. +#[derive(RuntimeDebug)] +pub struct Rialto; + +impl Chain for Rialto { + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; + + type AccountId = AccountId; + type Balance = Balance; + type Index = Index; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + *BlockLength::get().max.get(DispatchClass::Normal) + } + + fn max_extrinsic_weight() -> Weight { + BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or(Weight::MAX) + } +} + +frame_support::parameter_types! { + pub BlockLength: limits::BlockLength = + limits::BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub BlockWeights: limits::BlockWeights = + limits::BlockWeights::with_sensible_defaults(MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO); +} + +/// Name of the With-Rialto GRANDPA pallet instance that is deployed at bridged chains. +pub const WITH_RIALTO_GRANDPA_PALLET_NAME: &str = "BridgeRialtoGrandpa"; +/// Name of the With-Rialto messages pallet instance that is deployed at bridged chains. +pub const WITH_RIALTO_MESSAGES_PALLET_NAME: &str = "BridgeRialtoMessages"; +/// Name of the With-Rialto parachains bridge pallet instance that is deployed at bridged chains. +pub const WITH_RIALTO_BRIDGE_PARAS_PALLET_NAME: &str = "BridgeRialtoParachains"; + +/// Name of the parachain registrar pallet in the Rialto runtime. +pub const PARAS_REGISTRAR_PALLET_NAME: &str = "Registrar"; + +/// Name of the parachains pallet in the Rialto runtime. +pub const PARAS_PALLET_NAME: &str = "Paras"; + +decl_bridge_runtime_apis!(rialto); diff --git a/primitives/chain-rococo/Cargo.toml b/primitives/chain-rococo/Cargo.toml new file mode 100644 index 00000000000..73a2450cd17 --- /dev/null +++ b/primitives/chain-rococo/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bp-rococo" +description = "Primitives of Rococo runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Bridge Dependencies +bp-polkadot-core = { path = "../polkadot-core", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Based Dependencies +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-polkadot-core/std", + "bp-runtime/std", + "sp-api/std", + "frame-support/std", +] diff --git a/primitives/chain-rococo/src/lib.rs b/primitives/chain-rococo/src/lib.rs new file mode 100644 index 00000000000..6fd7228fd75 --- /dev/null +++ b/primitives/chain-rococo/src/lib.rs @@ -0,0 +1,53 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +pub use bp_polkadot_core::*; +use bp_runtime::decl_bridge_finality_runtime_apis; +use frame_support::parameter_types; + +/// Rococo Chain +pub type Rococo = PolkadotLike; + +parameter_types! { + pub const SS58Prefix: u8 = 42; +} + +/// Name of the parachains pallet in the Rococo runtime. +pub const PARAS_PALLET_NAME: &str = "Paras"; + +/// Name of the With-Rococo GRANDPA pallet instance that is deployed at bridged chains. +pub const WITH_ROCOCO_GRANDPA_PALLET_NAME: &str = "BridgeRococoGrandpa"; + +/// Maximal SCALE-encoded header size (in bytes) at Rococo. +/// +/// Let's assume that the largest header is header that enacts new authorities set with +/// `MAX_AUTHORITES_COUNT`. Every authority means 32-byte key and 8-byte weight. Let's also have +/// some fixed reserve for other things (digest, block hash and number, ...) as well. +pub const MAX_HEADER_SIZE: u32 = 4096 + MAX_AUTHORITIES_COUNT * 40; + +/// Maximal SCALE-encoded size of parachains headers that are stored at Rococo `Paras` pallet. +pub const MAX_NESTED_PARACHAIN_HEAD_SIZE: u32 = MAX_HEADER_SIZE; + +/// Maximal number of GRANDPA authorities at Rococo. +/// +/// Corresponds to the `MaxAuthorities` constant value from the Rococo runtime configuration. +pub const MAX_AUTHORITIES_COUNT: u32 = 100_000; + +decl_bridge_finality_runtime_apis!(rococo); diff --git a/primitives/chain-westend/Cargo.toml b/primitives/chain-westend/Cargo.toml new file mode 100644 index 00000000000..7d74362b09a --- /dev/null +++ b/primitives/chain-westend/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bp-westend" +description = "Primitives of Westend runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Bridge Dependencies + +bp-polkadot-core = { path = "../polkadot-core", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Based Dependencies + +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-polkadot-core/std", + "bp-runtime/std", + "sp-api/std", +] diff --git a/primitives/chain-westend/src/lib.rs b/primitives/chain-westend/src/lib.rs new file mode 100644 index 00000000000..9f4a98baedc --- /dev/null +++ b/primitives/chain-westend/src/lib.rs @@ -0,0 +1,55 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +pub use bp_polkadot_core::*; +use bp_runtime::decl_bridge_finality_runtime_apis; + +/// Westend Chain +pub type Westend = PolkadotLike; + +/// Name of the parachains pallet at the Westend runtime. +pub const PARAS_PALLET_NAME: &str = "Paras"; + +/// Name of the With-Westend GRANDPA pallet instance that is deployed at bridged chains. +pub const WITH_WESTEND_GRANDPA_PALLET_NAME: &str = "BridgeWestendGrandpa"; +/// Name of the With-Westend parachains bridge pallet instance that is deployed at bridged chains. +pub const WITH_WESTEND_BRIDGE_PARAS_PALLET_NAME: &str = "BridgeWestendParachains"; + +/// Maximal number of GRANDPA authorities at Westend. +/// +/// Corresponds to the `MaxAuthorities` constant value from the Westend runtime configuration. +pub const MAX_AUTHORITIES_COUNT: u32 = 100_000; + +/// Maximal SCALE-encoded header size (in bytes) at Westend. +/// +/// Let's assume that the largest header is header that enacts new authorities set with +/// `MAX_AUTHORITES_COUNT`. Every authority means 32-byte key and 8-byte weight. Let's also have +/// some fixed reserve for other things (digest, block hash and number, ...) as well. +pub const MAX_HEADER_SIZE: u32 = 4096 + MAX_AUTHORITIES_COUNT * 40; + +/// Maximal SCALE-encoded size of parachains headers that are stored at Westend `Paras` pallet. +pub const MAX_NESTED_PARACHAIN_HEAD_SIZE: u32 = MAX_HEADER_SIZE; + +/// Identifier of Westmint parachain at the Westend relay chain. +pub const WESTMINT_PARACHAIN_ID: u32 = 2000; + +decl_bridge_finality_runtime_apis!(westend); + +decl_bridge_finality_runtime_apis!(westmint); diff --git a/primitives/chain-wococo/Cargo.toml b/primitives/chain-wococo/Cargo.toml new file mode 100644 index 00000000000..6b741cd4b73 --- /dev/null +++ b/primitives/chain-wococo/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bp-wococo" +description = "Primitives of Wococo runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Bridge Dependencies +bp-polkadot-core = { path = "../polkadot-core", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } +bp-rococo = { path = "../chain-rococo", default-features = false } + +# Substrate Based Dependencies +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-polkadot-core/std", + "bp-runtime/std", + "bp-rococo/std", + "sp-api/std", +] diff --git a/primitives/chain-wococo/src/lib.rs b/primitives/chain-wococo/src/lib.rs new file mode 100644 index 00000000000..0436b03c036 --- /dev/null +++ b/primitives/chain-wococo/src/lib.rs @@ -0,0 +1,34 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +pub use bp_polkadot_core::*; +pub use bp_rococo::{ + SS58Prefix, MAX_AUTHORITIES_COUNT, MAX_HEADER_SIZE, MAX_NESTED_PARACHAIN_HEAD_SIZE, + PARAS_PALLET_NAME, +}; +use bp_runtime::decl_bridge_finality_runtime_apis; + +/// Wococo Chain +pub type Wococo = PolkadotLike; + +/// Name of the With-Wococo GRANDPA pallet instance that is deployed at bridged chains. +pub const WITH_WOCOCO_GRANDPA_PALLET_NAME: &str = "BridgeWococoGrandpa"; + +decl_bridge_finality_runtime_apis!(wococo); diff --git a/primitives/header-chain/Cargo.toml b/primitives/header-chain/Cargo.toml new file mode 100644 index 00000000000..3b7ea58cb96 --- /dev/null +++ b/primitives/header-chain/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "bp-header-chain" +description = "A common interface for describing what a bridge pallet should be able to do." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +finality-grandpa = { version = "0.16.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0", optional = true } + +# Bridge dependencies + +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +bp-test-utils = { path = "../test-utils" } +hex = "0.4" +hex-literal = "0.3" + +[features] +default = ["std"] +std = [ + "bp-runtime/std", + "codec/std", + "finality-grandpa/std", + "serde/std", + "frame-support/std", + "scale-info/std", + "sp-core/std", + "sp-finality-grandpa/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", +] diff --git a/primitives/header-chain/src/justification.rs b/primitives/header-chain/src/justification.rs new file mode 100644 index 00000000000..dadd48a4850 --- /dev/null +++ b/primitives/header-chain/src/justification.rs @@ -0,0 +1,227 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Pallet for checking GRANDPA Finality Proofs. +//! +//! Adapted copy of substrate/client/finality-grandpa/src/justification.rs. If origin +//! will ever be moved to the sp_finality_grandpa, we should reuse that implementation. + +use codec::{Decode, Encode}; +use finality_grandpa::voter_set::VoterSet; +use frame_support::RuntimeDebug; +use scale_info::TypeInfo; +use sp_finality_grandpa::{AuthorityId, AuthoritySignature, SetId}; +use sp_runtime::traits::Header as HeaderT; +use sp_std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + prelude::*, +}; + +/// A GRANDPA Justification is a proof that a given header was finalized +/// at a certain height and with a certain set of authorities. +/// +/// This particular proof is used to prove that headers on a bridged chain +/// (so not our chain) have been finalized correctly. +#[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)] +pub struct GrandpaJustification { + /// The round (voting period) this justification is valid for. + pub round: u64, + /// The set of votes for the chain which is to be finalized. + pub commit: + finality_grandpa::Commit, + /// A proof that the chain of blocks in the commit are related to each other. + pub votes_ancestries: Vec
, +} + +impl crate::FinalityProof for GrandpaJustification { + fn target_header_number(&self) -> H::Number { + self.commit.target_number + } +} + +/// Justification verification error. +#[derive(Eq, RuntimeDebug, PartialEq)] +pub enum Error { + /// Failed to decode justification. + JustificationDecode, + /// Justification is finalizing unexpected header. + InvalidJustificationTarget, + /// The authority has provided an invalid signature. + InvalidAuthoritySignature, + /// The justification contains precommit for header that is not a descendant of the commit + /// header. + PrecommitIsNotCommitDescendant, + /// The cumulative weight of all votes in the justification is not enough to justify commit + /// header finalization. + TooLowCumulativeWeight, + /// The justification contains extra (unused) headers in its `votes_ancestries` field. + ExtraHeadersInVotesAncestries, +} + +/// Decode justification target. +pub fn decode_justification_target( + raw_justification: &[u8], +) -> Result<(Header::Hash, Header::Number), Error> { + GrandpaJustification::
::decode(&mut &*raw_justification) + .map(|justification| (justification.commit.target_hash, justification.commit.target_number)) + .map_err(|_| Error::JustificationDecode) +} + +/// Verify that justification, that is generated by given authority set, finalizes given header. +pub fn verify_justification( + finalized_target: (Header::Hash, Header::Number), + authorities_set_id: SetId, + authorities_set: &VoterSet, + justification: &GrandpaJustification
, +) -> Result<(), Error> +where + Header::Number: finality_grandpa::BlockNumberOps, +{ + // ensure that it is justification for the expected header + if (justification.commit.target_hash, justification.commit.target_number) != finalized_target { + return Err(Error::InvalidJustificationTarget) + } + + let mut chain = AncestryChain::new(&justification.votes_ancestries); + let mut signature_buffer = Vec::new(); + let mut votes = BTreeSet::new(); + let mut cumulative_weight = 0u64; + for signed in &justification.commit.precommits { + // authority must be in the set + let authority_info = match authorities_set.get(&signed.id) { + Some(authority_info) => authority_info, + None => { + // just ignore precommit from unknown authority as + // `finality_grandpa::import_precommit` does + continue + }, + }; + + // check if authority has already voted in the same round. + // + // there's a lot of code in `validate_commit` and `import_precommit` functions inside + // `finality-grandpa` crate (mostly related to reporting equivocations). But the only thing + // that we care about is that only first vote from the authority is accepted + if !votes.insert(signed.id.clone()) { + continue + } + + // everything below this line can't just `continue`, because state is already altered + + // precommits aren't allowed for block lower than the target + if signed.precommit.target_number < justification.commit.target_number { + return Err(Error::PrecommitIsNotCommitDescendant) + } + // all precommits must be descendants of target block + chain = chain + .ensure_descendant(&justification.commit.target_hash, &signed.precommit.target_hash)?; + // since we know now that the precommit target is the descendant of the justification + // target, we may increase 'weight' of the justification target + // + // there's a lot of code in the `VoteGraph::insert` method inside `finality-grandpa` crate, + // but in the end it is only used to find GHOST, which we don't care about. The only thing + // that we care about is that the justification target has enough weight + cumulative_weight = cumulative_weight.checked_add(authority_info.weight().0.into()).expect( + "sum of weights of ALL authorities is expected not to overflow - this is guaranteed by\ + existence of VoterSet;\ + the order of loop conditions guarantees that we can account vote from same authority\ + multiple times;\ + thus we'll never overflow the u64::MAX;\ + qed", + ); + // verify authority signature + if !sp_finality_grandpa::check_message_signature_with_buffer( + &finality_grandpa::Message::Precommit(signed.precommit.clone()), + &signed.id, + &signed.signature, + justification.round, + authorities_set_id, + &mut signature_buffer, + ) { + return Err(Error::InvalidAuthoritySignature) + } + } + + // check that there are no extra headers in the justification + if !chain.unvisited.is_empty() { + return Err(Error::ExtraHeadersInVotesAncestries) + } + + // check that the cumulative weight of validators voted for the justification target (or one + // of its descendents) is larger than required threshold. + let threshold = authorities_set.threshold().0.into(); + if cumulative_weight >= threshold { + Ok(()) + } else { + Err(Error::TooLowCumulativeWeight) + } +} + +/// Votes ancestries with useful methods. +#[derive(RuntimeDebug)] +pub struct AncestryChain { + /// Header hash => parent header hash mapping. + pub parents: BTreeMap, + /// Hashes of headers that were not visited by `is_ancestor` method. + pub unvisited: BTreeSet, +} + +impl AncestryChain
{ + /// Create new ancestry chain. + pub fn new(ancestry: &[Header]) -> AncestryChain
{ + let mut parents = BTreeMap::new(); + let mut unvisited = BTreeSet::new(); + for ancestor in ancestry { + let hash = ancestor.hash(); + let parent_hash = *ancestor.parent_hash(); + parents.insert(hash, parent_hash); + unvisited.insert(hash); + } + AncestryChain { parents, unvisited } + } + + /// Returns `Ok(_)` if `precommit_target` is a descendant of the `commit_target` block and + /// `Err(_)` otherwise. + pub fn ensure_descendant( + mut self, + commit_target: &Header::Hash, + precommit_target: &Header::Hash, + ) -> Result { + let mut current_hash = *precommit_target; + loop { + if current_hash == *commit_target { + break + } + + let is_visited_before = !self.unvisited.remove(¤t_hash); + current_hash = match self.parents.get(¤t_hash) { + Some(parent_hash) => { + if is_visited_before { + // `Some(parent_hash)` means that the `current_hash` is in the `parents` + // container `is_visited_before` means that it has been visited before in + // some of previous calls => since we assume that previous call has finished + // with `true`, this also will be finished with `true` + return Ok(self) + } + + *parent_hash + }, + None => return Err(Error::PrecommitIsNotCommitDescendant), + }; + } + Ok(self) + } +} diff --git a/primitives/header-chain/src/lib.rs b/primitives/header-chain/src/lib.rs new file mode 100644 index 00000000000..2b4e0802a5a --- /dev/null +++ b/primitives/header-chain/src/lib.rs @@ -0,0 +1,147 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Defines traits which represent a common interface for Substrate pallets which want to +//! incorporate bridge functionality. + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_runtime::{BasicOperatingMode, Chain, HashOf, HasherOf, HeaderOf, StorageProofChecker}; +use codec::{Codec, Decode, Encode, EncodeLike}; +use core::{clone::Clone, cmp::Eq, default::Default, fmt::Debug}; +use frame_support::PalletError; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_finality_grandpa::{AuthorityList, ConsensusLog, SetId, GRANDPA_ENGINE_ID}; +use sp_runtime::{traits::Header as HeaderT, Digest, RuntimeDebug}; +use sp_std::boxed::Box; +use sp_trie::StorageProof; + +pub mod justification; +pub mod storage_keys; + +/// Header chain error. +#[derive(Clone, Copy, Decode, Encode, Eq, PalletError, PartialEq, RuntimeDebug, TypeInfo)] +pub enum HeaderChainError { + /// Header with given hash is missing from the chain. + UnknownHeader, + /// The storage proof doesn't contains storage root. + StorageRootMismatch, +} + +impl From for &'static str { + fn from(err: HeaderChainError) -> &'static str { + match err { + HeaderChainError::UnknownHeader => "UnknownHeader", + HeaderChainError::StorageRootMismatch => "StorageRootMismatch", + } + } +} + +/// Substrate header chain, abstracted from the way it is stored. +pub trait HeaderChain { + /// Returns finalized header by its hash. + fn finalized_header(hash: HashOf) -> Option>; + /// Parse storage proof using finalized header. + fn parse_finalized_storage_proof( + hash: HashOf, + storage_proof: StorageProof, + parse: impl FnOnce(StorageProofChecker>) -> R, + ) -> Result { + let header = Self::finalized_header(hash).ok_or(HeaderChainError::UnknownHeader)?; + let storage_proof_checker = + bp_runtime::StorageProofChecker::new(*header.state_root(), storage_proof) + .map_err(|_| HeaderChainError::StorageRootMismatch)?; + + Ok(parse(storage_proof_checker)) + } +} + +/// A type that can be used as a parameter in a dispatchable function. +/// +/// When using `decl_module` all arguments for call functions must implement this trait. +pub trait Parameter: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {} +impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {} + +/// A GRANDPA Authority List and ID. +#[derive(Default, Encode, Eq, Decode, RuntimeDebug, PartialEq, Clone, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct AuthoritySet { + /// List of GRANDPA authorities for the current round. + pub authorities: AuthorityList, + /// Monotonic identifier of the current GRANDPA authority set. + pub set_id: SetId, +} + +impl AuthoritySet { + /// Create a new GRANDPA Authority Set. + pub fn new(authorities: AuthorityList, set_id: SetId) -> Self { + Self { authorities, set_id } + } +} + +/// Data required for initializing the bridge pallet. +/// +/// The bridge needs to know where to start its sync from, and this provides that initial context. +#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Eq, Clone, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct InitializationData { + /// The header from which we should start syncing. + pub header: Box, + /// The initial authorities of the pallet. + pub authority_list: AuthorityList, + /// The ID of the initial authority set. + pub set_id: SetId, + /// Pallet operating mode. + pub operating_mode: BasicOperatingMode, +} + +/// Abstract finality proof that is justifying block finality. +pub trait FinalityProof: Clone + Send + Sync + Debug { + /// Return number of header that this proof is generated for. + fn target_header_number(&self) -> Number; +} + +/// A trait that provides helper methods for querying the consensus log. +pub trait ConsensusLogReader { + /// Returns true if digest contains item that schedules authorities set change. + fn schedules_authorities_change(digest: &Digest) -> bool; +} + +/// A struct that provides helper methods for querying the GRANDPA consensus log. +pub struct GrandpaConsensusLogReader(sp_std::marker::PhantomData); + +impl GrandpaConsensusLogReader { + pub fn find_authorities_change( + digest: &Digest, + ) -> Option> { + // find the first consensus digest with the right ID which converts to + // the right kind of consensus log. + digest + .convert_first(|log| log.consensus_try_to(&GRANDPA_ENGINE_ID)) + .and_then(|log| match log { + ConsensusLog::ScheduledChange(change) => Some(change), + _ => None, + }) + } +} + +impl ConsensusLogReader for GrandpaConsensusLogReader { + fn schedules_authorities_change(digest: &Digest) -> bool { + GrandpaConsensusLogReader::::find_authorities_change(digest).is_some() + } +} diff --git a/primitives/header-chain/src/storage_keys.rs b/primitives/header-chain/src/storage_keys.rs new file mode 100644 index 00000000000..bb642b1817f --- /dev/null +++ b/primitives/header-chain/src/storage_keys.rs @@ -0,0 +1,78 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Storage keys of bridge GRANDPA pallet. + +/// Name of the `IsHalted` storage value. +pub const PALLET_OPERATING_MODE_VALUE_NAME: &str = "PalletOperatingMode"; +/// Name of the `BestFinalized` storage value. +pub const BEST_FINALIZED_VALUE_NAME: &str = "BestFinalized"; + +use sp_core::storage::StorageKey; + +/// Storage key of the `PalletOperatingMode` variable in the runtime storage. +pub fn pallet_operating_mode_key(pallet_prefix: &str) -> StorageKey { + StorageKey( + bp_runtime::storage_value_final_key( + pallet_prefix.as_bytes(), + PALLET_OPERATING_MODE_VALUE_NAME.as_bytes(), + ) + .to_vec(), + ) +} + +/// Storage key of the best finalized header number and hash value in the runtime storage. +pub fn best_finalized_key(pallet_prefix: &str) -> StorageKey { + StorageKey( + bp_runtime::storage_value_final_key( + pallet_prefix.as_bytes(), + BEST_FINALIZED_VALUE_NAME.as_bytes(), + ) + .to_vec(), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn pallet_operating_mode_key_computed_properly() { + // If this test fails, then something has been changed in module storage that is breaking + // compatibility with previous pallet. + let storage_key = pallet_operating_mode_key("BridgeGrandpa").0; + assert_eq!( + storage_key, + hex!("0b06f475eddb98cf933a12262e0388de0f4cf0917788d791142ff6c1f216e7b3").to_vec(), + "Unexpected storage key: {}", + hex::encode(&storage_key), + ); + } + + #[test] + fn best_finalized_key_computed_properly() { + // If this test fails, then something has been changed in module storage that is breaking + // compatibility with previous pallet. + let storage_key = best_finalized_key("BridgeGrandpa").0; + assert_eq!( + storage_key, + hex!("0b06f475eddb98cf933a12262e0388dea4ebafdd473c549fdb24c5c991c5591c").to_vec(), + "Unexpected storage key: {}", + hex::encode(&storage_key), + ); + } +} diff --git a/primitives/header-chain/tests/implementation_match.rs b/primitives/header-chain/tests/implementation_match.rs new file mode 100644 index 00000000000..aaa19d4b918 --- /dev/null +++ b/primitives/header-chain/tests/implementation_match.rs @@ -0,0 +1,328 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tests inside this module are made to ensure that our custom justification verification +//! implementation works exactly as `fn finality_grandpa::validate_commit`. +//! +//! Some of tests in this module may partially duplicate tests from `justification.rs`, +//! but their purpose is different. + +use bp_header_chain::justification::{verify_justification, Error, GrandpaJustification}; +use bp_test_utils::{ + header_id, make_justification_for_header, signed_precommit, test_header, Account, + JustificationGeneratorParams, ALICE, BOB, CHARLIE, DAVE, EVE, TEST_GRANDPA_SET_ID, +}; +use finality_grandpa::voter_set::VoterSet; +use sp_finality_grandpa::{AuthorityId, AuthorityWeight}; +use sp_runtime::traits::Header as HeaderT; + +type TestHeader = sp_runtime::testing::Header; +type TestHash = ::Hash; +type TestNumber = ::Number; + +/// Implementation of `finality_grandpa::Chain` that is used in tests. +struct AncestryChain(bp_header_chain::justification::AncestryChain); + +impl AncestryChain { + fn new(ancestry: &[TestHeader]) -> Self { + Self(bp_header_chain::justification::AncestryChain::new(ancestry)) + } +} + +impl finality_grandpa::Chain for AncestryChain { + fn ancestry( + &self, + base: TestHash, + block: TestHash, + ) -> Result, finality_grandpa::Error> { + let mut route = Vec::new(); + let mut current_hash = block; + loop { + if current_hash == base { + break + } + match self.0.parents.get(¤t_hash).cloned() { + Some(parent_hash) => { + current_hash = parent_hash; + route.push(current_hash); + }, + _ => return Err(finality_grandpa::Error::NotDescendent), + } + } + route.pop(); // remove the base + + Ok(route) + } +} + +/// Get a full set of accounts. +fn full_accounts_set() -> Vec<(Account, AuthorityWeight)> { + vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1), (EVE, 1)] +} + +/// Get a full set of GRANDPA authorities. +fn full_voter_set() -> VoterSet { + VoterSet::new(full_accounts_set().iter().map(|(id, w)| (AuthorityId::from(*id), *w))).unwrap() +} + +/// Get a minimal set of accounts. +fn minimal_accounts_set() -> Vec<(Account, AuthorityWeight)> { + // there are 5 accounts in the full set => we need 2/3 + 1 accounts, which results in 4 accounts + vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1)] +} + +/// Get a minimal subset of GRANDPA authorities that have enough cumulative vote weight to justify a +/// header finality. +pub fn minimal_voter_set() -> VoterSet { + VoterSet::new(minimal_accounts_set().iter().map(|(id, w)| (AuthorityId::from(*id), *w))) + .unwrap() +} + +/// Make a valid GRANDPA justification with sensible defaults. +pub fn make_default_justification(header: &TestHeader) -> GrandpaJustification { + make_justification_for_header(JustificationGeneratorParams { + header: header.clone(), + authorities: minimal_accounts_set(), + ..Default::default() + }) +} + +// the `finality_grandpa::validate_commit` function has two ways to report an unsuccessful +// commit validation: +// +// 1) to return `Err()` (which only may happen if `finality_grandpa::Chain` implementation +// returns an error); +// 2) to return `Ok(validation_result)` if `validation_result.is_valid()` is false. +// +// Our implementation would just return error in both cases. + +#[test] +fn same_result_when_precommit_target_has_lower_number_than_commit_target() { + let mut justification = make_default_justification(&test_header(1)); + // the number of header in precommit (0) is lower than number of header in commit (1) + justification.commit.precommits[0].precommit.target_number = 0; + + // our implementation returns an error + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &full_voter_set(), + &justification, + ), + Err(Error::PrecommitIsNotCommitDescendant), + ); + + // original implementation returns `Ok(validation_result)` + // with `validation_result.is_valid() == false`. + let result = finality_grandpa::validate_commit( + &justification.commit, + &full_voter_set(), + &AncestryChain::new(&justification.votes_ancestries), + ) + .unwrap(); + + assert!(!result.is_valid()); +} + +#[test] +fn same_result_when_precommit_target_is_not_descendant_of_commit_target() { + let not_descendant = test_header::(10); + let mut justification = make_default_justification(&test_header(1)); + // the route from header of commit (1) to header of precommit (10) is missing from + // the votes ancestries + justification.commit.precommits[0].precommit.target_number = *not_descendant.number(); + justification.commit.precommits[0].precommit.target_hash = not_descendant.hash(); + justification.votes_ancestries.push(not_descendant); + + // our implementation returns an error + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &full_voter_set(), + &justification, + ), + Err(Error::PrecommitIsNotCommitDescendant), + ); + + // original implementation returns `Ok(validation_result)` + // with `validation_result.is_valid() == false`. + let result = finality_grandpa::validate_commit( + &justification.commit, + &full_voter_set(), + &AncestryChain::new(&justification.votes_ancestries), + ) + .unwrap(); + + assert!(!result.is_valid()); +} + +#[test] +fn same_result_when_justification_contains_duplicate_vote() { + let mut justification = make_justification_for_header(JustificationGeneratorParams { + header: test_header(1), + authorities: minimal_accounts_set(), + ancestors: 0, + ..Default::default() + }); + // the justification may contain exactly the same vote (i.e. same precommit and same signature) + // multiple times && it isn't treated as an error by original implementation + justification.commit.precommits.push(justification.commit.precommits[0].clone()); + justification.commit.precommits.push(justification.commit.precommits[0].clone()); + + // our implementation succeeds + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &full_voter_set(), + &justification, + ), + Ok(()), + ); + // original implementation returns `Ok(validation_result)` + // with `validation_result.is_valid() == true`. + let result = finality_grandpa::validate_commit( + &justification.commit, + &full_voter_set(), + &AncestryChain::new(&justification.votes_ancestries), + ) + .unwrap(); + + assert!(result.is_valid()); +} + +#[test] +fn same_result_when_authority_equivocates_once_in_a_round() { + let mut justification = make_justification_for_header(JustificationGeneratorParams { + header: test_header(1), + authorities: minimal_accounts_set(), + ancestors: 0, + ..Default::default() + }); + // the justification original implementation allows authority to submit two different + // votes in a single round, of which only first is 'accepted' + justification.commit.precommits.push(signed_precommit::( + &ALICE, + header_id::(1), + justification.round, + TEST_GRANDPA_SET_ID, + )); + + // our implementation succeeds + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &full_voter_set(), + &justification, + ), + Ok(()), + ); + // original implementation returns `Ok(validation_result)` + // with `validation_result.is_valid() == true`. + let result = finality_grandpa::validate_commit( + &justification.commit, + &full_voter_set(), + &AncestryChain::new(&justification.votes_ancestries), + ) + .unwrap(); + + assert!(result.is_valid()); +} + +#[test] +fn same_result_when_authority_equivocates_twice_in_a_round() { + let mut justification = make_justification_for_header(JustificationGeneratorParams { + header: test_header(1), + authorities: minimal_accounts_set(), + ancestors: 0, + ..Default::default() + }); + // there's some code in the original implementation that should return an error when + // same authority submits more than two different votes in a single round: + // https://github.com/paritytech/finality-grandpa/blob/6aeea2d1159d0f418f0b86e70739f2130629ca09/src/lib.rs#L473 + // but there's also a code that prevents this from happening: + // https://github.com/paritytech/finality-grandpa/blob/6aeea2d1159d0f418f0b86e70739f2130629ca09/src/round.rs#L287 + // => so now we are also just ignoring all votes from the same authority, except the first one + justification.commit.precommits.push(signed_precommit::( + &ALICE, + header_id::(1), + justification.round, + TEST_GRANDPA_SET_ID, + )); + justification.commit.precommits.push(signed_precommit::( + &ALICE, + header_id::(1), + justification.round, + TEST_GRANDPA_SET_ID, + )); + + // our implementation succeeds + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &full_voter_set(), + &justification, + ), + Ok(()), + ); + // original implementation returns `Ok(validation_result)` + // with `validation_result.is_valid() == true`. + let result = finality_grandpa::validate_commit( + &justification.commit, + &full_voter_set(), + &AncestryChain::new(&justification.votes_ancestries), + ) + .unwrap(); + + assert!(result.is_valid()); +} + +#[test] +fn same_result_when_there_are_not_enough_cumulative_weight_to_finalize_commit_target() { + // just remove one authority from the minimal set and we shall not reach the threshold + let mut authorities_set = minimal_accounts_set(); + authorities_set.pop(); + let justification = make_justification_for_header(JustificationGeneratorParams { + header: test_header(1), + authorities: authorities_set, + ..Default::default() + }); + + // our implementation returns an error + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &full_voter_set(), + &justification, + ), + Err(Error::TooLowCumulativeWeight), + ); + // original implementation returns `Ok(validation_result)` + // with `validation_result.is_valid() == false`. + let result = finality_grandpa::validate_commit( + &justification.commit, + &full_voter_set(), + &AncestryChain::new(&justification.votes_ancestries), + ) + .unwrap(); + + assert!(!result.is_valid()); +} diff --git a/primitives/header-chain/tests/justification.rs b/primitives/header-chain/tests/justification.rs new file mode 100644 index 00000000000..5b4981a0f69 --- /dev/null +++ b/primitives/header-chain/tests/justification.rs @@ -0,0 +1,192 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tests for Grandpa Justification code. + +use bp_header_chain::justification::{verify_justification, Error}; +use bp_test_utils::*; + +type TestHeader = sp_runtime::testing::Header; + +#[test] +fn valid_justification_accepted() { + let authorities = vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1)]; + let params = JustificationGeneratorParams { + header: test_header(1), + round: TEST_GRANDPA_ROUND, + set_id: TEST_GRANDPA_SET_ID, + authorities: authorities.clone(), + ancestors: 7, + forks: 3, + }; + + let justification = make_justification_for_header::(params.clone()); + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &voter_set(), + &justification, + ), + Ok(()), + ); + + assert_eq!(justification.commit.precommits.len(), authorities.len()); + assert_eq!(justification.votes_ancestries.len(), params.ancestors as usize); +} + +#[test] +fn valid_justification_accepted_with_single_fork() { + let params = JustificationGeneratorParams { + header: test_header(1), + round: TEST_GRANDPA_ROUND, + set_id: TEST_GRANDPA_SET_ID, + authorities: vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1), (EVE, 1)], + ancestors: 5, + forks: 1, + }; + + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &voter_set(), + &make_justification_for_header::(params) + ), + Ok(()), + ); +} + +#[test] +fn valid_justification_accepted_with_arbitrary_number_of_authorities() { + use finality_grandpa::voter_set::VoterSet; + use sp_finality_grandpa::AuthorityId; + + let n = 15; + let authorities = accounts(n).iter().map(|k| (*k, 1)).collect::>(); + + let params = JustificationGeneratorParams { + header: test_header(1), + round: TEST_GRANDPA_ROUND, + set_id: TEST_GRANDPA_SET_ID, + authorities: authorities.clone(), + ancestors: n.into(), + forks: n.into(), + }; + + let authorities = authorities + .iter() + .map(|(id, w)| (AuthorityId::from(*id), *w)) + .collect::>(); + let voter_set = VoterSet::new(authorities).unwrap(); + + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &voter_set, + &make_justification_for_header::(params) + ), + Ok(()), + ); +} + +#[test] +fn justification_with_invalid_target_rejected() { + assert_eq!( + verify_justification::( + header_id::(2), + TEST_GRANDPA_SET_ID, + &voter_set(), + &make_default_justification::(&test_header(1)), + ), + Err(Error::InvalidJustificationTarget), + ); +} + +#[test] +fn justification_with_invalid_commit_rejected() { + let mut justification = make_default_justification::(&test_header(1)); + justification.commit.precommits.clear(); + + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &voter_set(), + &justification, + ), + Err(Error::ExtraHeadersInVotesAncestries), + ); +} + +#[test] +fn justification_with_invalid_authority_signature_rejected() { + let mut justification = make_default_justification::(&test_header(1)); + justification.commit.precommits[0].signature = + sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]); + + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &voter_set(), + &justification, + ), + Err(Error::InvalidAuthoritySignature), + ); +} + +#[test] +fn justification_with_invalid_precommit_ancestry() { + let mut justification = make_default_justification::(&test_header(1)); + justification.votes_ancestries.push(test_header(10)); + + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &voter_set(), + &justification, + ), + Err(Error::ExtraHeadersInVotesAncestries), + ); +} + +#[test] +fn justification_is_invalid_if_we_dont_meet_threshold() { + // Need at least three authorities to sign off or else the voter set threshold can't be reached + let authorities = vec![(ALICE, 1), (BOB, 1)]; + + let params = JustificationGeneratorParams { + header: test_header(1), + round: TEST_GRANDPA_ROUND, + set_id: TEST_GRANDPA_SET_ID, + authorities: authorities.clone(), + ancestors: 2 * authorities.len() as u32, + forks: 2, + }; + + assert_eq!( + verify_justification::( + header_id::(1), + TEST_GRANDPA_SET_ID, + &voter_set(), + &make_justification_for_header::(params) + ), + Err(Error::TooLowCumulativeWeight), + ); +} diff --git a/primitives/messages/Cargo.toml b/primitives/messages/Cargo.toml new file mode 100644 index 00000000000..4899a1f22da --- /dev/null +++ b/primitives/messages/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "bp-messages" +description = "Primitives of messages module." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive", "bit-vec"] } +impl-trait-for-tuples = "0.2" +scale-info = { version = "2.1.1", default-features = false, features = ["bit-vec", "derive"] } +serde = { version = "1.0", optional = true, features = ["derive"] } + +# Bridge dependencies + +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +hex = "0.4" +hex-literal = "0.3" + +[features] +default = ["std"] +std = [ + "bp-runtime/std", + "codec/std", + "frame-support/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-std/std" +] diff --git a/primitives/messages/src/lib.rs b/primitives/messages/src/lib.rs new file mode 100644 index 00000000000..722a73cb24e --- /dev/null +++ b/primitives/messages/src/lib.rs @@ -0,0 +1,415 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives of messages module. + +#![cfg_attr(not(feature = "std"), no_std)] +// RuntimeApi generated functions +#![allow(clippy::too_many_arguments)] + +use bp_runtime::{BasicOperatingMode, OperatingMode}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::RuntimeDebug; +use scale_info::TypeInfo; +use sp_std::{collections::vec_deque::VecDeque, prelude::*}; + +pub mod source_chain; +pub mod storage_keys; +pub mod target_chain; + +// Weight is reexported to avoid additional frame-support dependencies in related crates. +use bp_runtime::messages::MessageDispatchResult; +pub use frame_support::weights::Weight; + +/// Messages pallet operating mode. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum MessagesOperatingMode { + /// Basic operating mode (Normal/Halted) + Basic(BasicOperatingMode), + /// The pallet is not accepting outbound messages. Inbound messages and receiving proofs + /// are still accepted. + /// + /// This mode may be used e.g. when bridged chain expects upgrade. Then to avoid dispatch + /// failures, the pallet owner may stop accepting new messages, while continuing to deliver + /// queued messages to the bridged chain. Once upgrade is completed, the mode may be switched + /// back to `Normal`. + RejectingOutboundMessages, +} + +impl Default for MessagesOperatingMode { + fn default() -> Self { + MessagesOperatingMode::Basic(BasicOperatingMode::Normal) + } +} + +impl OperatingMode for MessagesOperatingMode { + fn is_halted(&self) -> bool { + match self { + Self::Basic(operating_mode) => operating_mode.is_halted(), + _ => false, + } + } +} + +/// Lane identifier. +pub type LaneId = [u8; 4]; + +/// Message nonce. Valid messages will never have 0 nonce. +pub type MessageNonce = u64; + +/// Message id as a tuple. +pub type BridgeMessageId = (LaneId, MessageNonce); + +/// Opaque message payload. We only decode this payload when it is dispatched. +pub type MessagePayload = Vec; + +/// Message key (unique message identifier) as it is stored in the storage. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct MessageKey { + /// ID of the message lane. + pub lane_id: LaneId, + /// Message nonce. + pub nonce: MessageNonce, +} + +/// Message as it is stored in the storage. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Message { + /// Message key. + pub key: MessageKey, + /// Message payload. + pub payload: MessagePayload, +} + +/// Inbound lane data. +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct InboundLaneData { + /// Identifiers of relayers and messages that they have delivered to this lane (ordered by + /// message nonce). + /// + /// This serves as a helper storage item, to allow the source chain to easily pay rewards + /// to the relayers who successfully delivered messages to the target chain (inbound lane). + /// + /// It is guaranteed to have at most N entries, where N is configured at the module level. + /// If there are N entries in this vec, then: + /// 1) all incoming messages are rejected if they're missing corresponding + /// `proof-of(outbound-lane.state)`; 2) all incoming messages are rejected if + /// `proof-of(outbound-lane.state).last_delivered_nonce` is equal to + /// `self.last_confirmed_nonce`. Given what is said above, all nonces in this queue are in + /// range: `(self.last_confirmed_nonce; self.last_delivered_nonce()]`. + /// + /// When a relayer sends a single message, both of MessageNonces are the same. + /// When relayer sends messages in a batch, the first arg is the lowest nonce, second arg the + /// highest nonce. Multiple dispatches from the same relayer are allowed. + pub relayers: VecDeque>, + + /// Nonce of the last message that + /// a) has been delivered to the target (this) chain and + /// b) the delivery has been confirmed on the source chain + /// + /// that the target chain knows of. + /// + /// This value is updated indirectly when an `OutboundLane` state of the source + /// chain is received alongside with new messages delivery. + pub last_confirmed_nonce: MessageNonce, +} + +impl Default for InboundLaneData { + fn default() -> Self { + InboundLaneData { relayers: VecDeque::new(), last_confirmed_nonce: 0 } + } +} + +impl InboundLaneData { + /// Returns approximate size of the struct, given a number of entries in the `relayers` set and + /// size of each entry. + /// + /// Returns `None` if size overflows `usize` limits. + pub fn encoded_size_hint(relayers_entries: usize) -> Option + where + RelayerId: MaxEncodedLen, + { + let message_nonce_size = MessageNonce::max_encoded_len(); + let relayer_id_encoded_size = RelayerId::max_encoded_len(); + let relayers_entry_size = relayer_id_encoded_size.checked_add(2 * message_nonce_size)?; + let relayers_size = relayers_entries.checked_mul(relayers_entry_size)?; + relayers_size.checked_add(message_nonce_size) + } + + /// Returns the approximate size of the struct as u32, given a number of entries in the + /// `relayers` set and the size of each entry. + /// + /// Returns `u32::MAX` if size overflows `u32` limits. + pub fn encoded_size_hint_u32(relayers_entries: usize) -> u32 + where + RelayerId: MaxEncodedLen, + { + Self::encoded_size_hint(relayers_entries) + .and_then(|x| u32::try_from(x).ok()) + .unwrap_or(u32::MAX) + } + + /// Nonce of the last message that has been delivered to this (target) chain. + pub fn last_delivered_nonce(&self) -> MessageNonce { + self.relayers + .back() + .map(|entry| entry.messages.end) + .unwrap_or(self.last_confirmed_nonce) + } +} + +/// Outbound message details, returned by runtime APIs. +#[derive(Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq)] +pub struct OutboundMessageDetails { + /// Nonce assigned to the message. + pub nonce: MessageNonce, + /// Message dispatch weight. + /// + /// Depending on messages pallet configuration, it may be declared by the message submitter, + /// computed automatically or just be zero if dispatch fee is paid at the target chain. + pub dispatch_weight: Weight, + /// Size of the encoded message. + pub size: u32, +} + +/// Inbound message details, returned by runtime APIs. +#[derive(Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq)] +pub struct InboundMessageDetails { + /// Computed message dispatch weight. + /// + /// Runtime API guarantees that it will match the value, returned by + /// `target_chain::MessageDispatch::dispatch_weight`. This means that if the runtime + /// has failed to decode the message, it will be zero - that's because `undecodable` + /// message cannot be dispatched. + pub dispatch_weight: Weight, +} + +/// Unrewarded relayer entry stored in the inbound lane data. +/// +/// This struct represents a continuous range of messages that have been delivered by the same +/// relayer and whose confirmations are still pending. +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct UnrewardedRelayer { + /// Identifier of the relayer. + pub relayer: RelayerId, + /// Messages range, delivered by this relayer. + pub messages: DeliveredMessages, +} + +/// Received messages with their dispatch result. +#[derive(Clone, Default, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct ReceivedMessages { + /// Id of the lane which is receiving messages. + pub lane: LaneId, + /// Result of messages which we tried to dispatch + pub receive_results: Vec<(MessageNonce, Result)>, + /// Messages which were skipped and never dispatched + pub skipped_for_not_enough_weight: Vec, +} + +impl ReceivedMessages { + pub fn new(lane: LaneId, receive_results: Vec<(MessageNonce, Result)>) -> Self { + ReceivedMessages { lane, receive_results, skipped_for_not_enough_weight: Vec::new() } + } + + pub fn push(&mut self, message: MessageNonce, result: Result) { + self.receive_results.push((message, result)); + } + + pub fn push_skipped_for_not_enough_weight(&mut self, message: MessageNonce) { + self.skipped_for_not_enough_weight.push(message); + } +} + +/// Result of single message receival. +#[derive(RuntimeDebug, Encode, Decode, PartialEq, Eq, Clone, TypeInfo)] +pub enum ReceivalResult { + /// Message has been received and dispatched. Note that we don't care whether dispatch has + /// been successful or not - in both case message falls into this category. + /// + /// The message dispatch result is also returned. + Dispatched(MessageDispatchResult), + /// Message has invalid nonce and lane has rejected to accept this message. + InvalidNonce, + /// There are too many unrewarded relayer entries at the lane. + TooManyUnrewardedRelayers, + /// There are too many unconfirmed messages at the lane. + TooManyUnconfirmedMessages, +} + +/// Delivered messages with their dispatch result. +#[derive(Clone, Default, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct DeliveredMessages { + /// Nonce of the first message that has been delivered (inclusive). + pub begin: MessageNonce, + /// Nonce of the last message that has been delivered (inclusive). + pub end: MessageNonce, +} + +impl DeliveredMessages { + /// Create new `DeliveredMessages` struct that confirms delivery of single nonce with given + /// dispatch result. + pub fn new(nonce: MessageNonce) -> Self { + DeliveredMessages { begin: nonce, end: nonce } + } + + /// Return total count of delivered messages. + pub fn total_messages(&self) -> MessageNonce { + if self.end >= self.begin { + self.end - self.begin + 1 + } else { + 0 + } + } + + /// Note new dispatched message. + pub fn note_dispatched_message(&mut self) { + self.end += 1; + } + + /// Returns true if delivered messages contain message with given nonce. + pub fn contains_message(&self, nonce: MessageNonce) -> bool { + (self.begin..=self.end).contains(&nonce) + } +} + +/// Gist of `InboundLaneData::relayers` field used by runtime APIs. +#[derive(Clone, Default, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct UnrewardedRelayersState { + /// Number of entries in the `InboundLaneData::relayers` set. + pub unrewarded_relayer_entries: MessageNonce, + /// Number of messages in the oldest entry of `InboundLaneData::relayers`. This is the + /// minimal number of reward proofs required to push out this entry from the set. + pub messages_in_oldest_entry: MessageNonce, + /// Total number of messages in the relayers vector. + pub total_messages: MessageNonce, + /// Nonce of the latest message that has been delivered to the target chain. + /// + /// This corresponds to the result of the `InboundLaneData::last_delivered_nonce` call + /// at the bridged chain. + pub last_delivered_nonce: MessageNonce, +} + +/// Outbound lane data. +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct OutboundLaneData { + /// Nonce of the oldest message that we haven't yet pruned. May point to not-yet-generated + /// message if all sent messages are already pruned. + pub oldest_unpruned_nonce: MessageNonce, + /// Nonce of the latest message, received by bridged chain. + pub latest_received_nonce: MessageNonce, + /// Nonce of the latest message, generated by us. + pub latest_generated_nonce: MessageNonce, +} + +impl Default for OutboundLaneData { + fn default() -> Self { + OutboundLaneData { + // it is 1 because we're pruning everything in [oldest_unpruned_nonce; + // latest_received_nonce] + oldest_unpruned_nonce: 1, + latest_received_nonce: 0, + latest_generated_nonce: 0, + } + } +} + +/// Returns total number of messages in the `InboundLaneData::relayers` vector. +/// +/// Returns `None` if there are more messages that `MessageNonce` may fit (i.e. `MessageNonce + 1`). +pub fn total_unrewarded_messages( + relayers: &VecDeque>, +) -> Option { + match (relayers.front(), relayers.back()) { + (Some(front), Some(back)) => { + if let Some(difference) = back.messages.end.checked_sub(front.messages.begin) { + difference.checked_add(1) + } else { + Some(0) + } + }, + _ => Some(0), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn total_unrewarded_messages_does_not_overflow() { + assert_eq!( + total_unrewarded_messages( + &vec![ + UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(0) }, + UnrewardedRelayer { + relayer: 2, + messages: DeliveredMessages::new(MessageNonce::MAX) + }, + ] + .into_iter() + .collect() + ), + None, + ); + } + + #[test] + fn inbound_lane_data_returns_correct_hint() { + let test_cases = vec![ + // single relayer, multiple messages + (1, 128u8), + // multiple relayers, single message per relayer + (128u8, 128u8), + // several messages per relayer + (13u8, 128u8), + ]; + for (relayer_entries, messages_count) in test_cases { + let expected_size = InboundLaneData::::encoded_size_hint(relayer_entries as _); + let actual_size = InboundLaneData { + relayers: (1u8..=relayer_entries) + .map(|i| UnrewardedRelayer { + relayer: i, + messages: DeliveredMessages::new(i as _), + }) + .collect(), + last_confirmed_nonce: messages_count as _, + } + .encode() + .len(); + let difference = (expected_size.unwrap() as f64 - actual_size as f64).abs(); + assert!( + difference / (std::cmp::min(actual_size, expected_size.unwrap()) as f64) < 0.1, + "Too large difference between actual ({}) and expected ({:?}) inbound lane data size. Test case: {}+{}", + actual_size, + expected_size, + relayer_entries, + messages_count, + ); + } + } + + #[test] + fn contains_result_works() { + let delivered_messages = DeliveredMessages { begin: 100, end: 150 }; + + assert!(!delivered_messages.contains_message(99)); + assert!(delivered_messages.contains_message(100)); + assert!(delivered_messages.contains_message(150)); + assert!(!delivered_messages.contains_message(151)); + } +} diff --git a/primitives/messages/src/source_chain.rs b/primitives/messages/src/source_chain.rs new file mode 100644 index 00000000000..1cac449ee63 --- /dev/null +++ b/primitives/messages/src/source_chain.rs @@ -0,0 +1,217 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives of messages module, that are used on the source chain. + +use crate::{InboundLaneData, LaneId, MessageNonce, OutboundLaneData}; + +use crate::UnrewardedRelayer; +use bp_runtime::Size; +use frame_support::{weights::Weight, Parameter, RuntimeDebug}; +use sp_std::{ + collections::{btree_map::BTreeMap, vec_deque::VecDeque}, + fmt::Debug, + ops::RangeInclusive, +}; + +/// Number of messages, delivered by relayers. +pub type RelayersRewards = BTreeMap; + +/// Target chain API. Used by source chain to verify target chain proofs. +/// +/// All implementations of this trait should only work with finalized data that +/// can't change. Wrong implementation may lead to invalid lane states (i.e. lane +/// that's stuck) and/or processing messages without paying fees. +/// +/// The `Payload` type here means the payload of the message that is sent from the +/// source chain to the target chain. The `AccountId` type here means the account +/// type used by the source chain. +pub trait TargetHeaderChain { + /// Error type. + type Error: Debug + Into<&'static str>; + + /// Proof that messages have been received by target chain. + type MessagesDeliveryProof: Parameter + Size; + + /// Verify message payload before we accept it. + /// + /// **CAUTION**: this is very important function. Incorrect implementation may lead + /// to stuck lanes and/or relayers loses. + /// + /// The proper implementation must ensure that the delivery-transaction with this + /// payload would (at least) be accepted into target chain transaction pool AND + /// eventually will be successfully mined. The most obvious incorrect implementation + /// example would be implementation for BTC chain that accepts payloads larger than + /// 1MB. BTC nodes aren't accepting transactions that are larger than 1MB, so relayer + /// will be unable to craft valid transaction => this (and all subsequent) messages will + /// never be delivered. + fn verify_message(payload: &Payload) -> Result<(), Self::Error>; + + /// Verify messages delivery proof and return lane && nonce of the latest received message. + fn verify_messages_delivery_proof( + proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error>; +} + +/// Lane message verifier. +/// +/// Runtime developer may implement any additional validation logic over message-lane mechanism. +/// E.g. if lanes should have some security (e.g. you can only accept Lane1 messages from +/// Submitter1, Lane2 messages for those who has submitted first message to this lane, disable +/// Lane3 until some block, ...), then it may be built using this verifier. +/// +/// Any fee requirements should also be enforced here. +pub trait LaneMessageVerifier { + /// Error type. + type Error: Debug + Into<&'static str>; + + /// Verify message payload and return Ok(()) if message is valid and allowed to be sent over the + /// lane. + fn verify_message( + submitter: &SenderOrigin, + lane: &LaneId, + outbound_data: &OutboundLaneData, + payload: &Payload, + ) -> Result<(), Self::Error>; +} + +/// Message delivery payment. It is called as a part of submit-message transaction. Transaction +/// submitter is paying (in source chain tokens/assets) for: +/// +/// 1) submit-message-transaction-fee itself. This fee is not included in the +/// `delivery_and_dispatch_fee` and is withheld by the regular transaction payment mechanism; +/// 2) message-delivery-transaction-fee. It is submitted to the target node by relayer; +/// 3) message-dispatch fee. It is paid by relayer for processing message by target chain; +/// 4) message-receiving-delivery-transaction-fee. It is submitted to the source node +/// by relayer. +pub trait MessageDeliveryAndDispatchPayment { + /// Error type. + type Error: Debug + Into<&'static str>; + + /// Pay rewards for delivering messages to the given relayers. + /// + /// The implementation may also choose to pay reward to the `confirmation_relayer`, which is + /// a relayer that has submitted delivery confirmation transaction. + fn pay_relayers_rewards( + lane_id: LaneId, + messages_relayers: VecDeque>, + confirmation_relayer: &AccountId, + received_range: &RangeInclusive, + ); +} + +impl MessageDeliveryAndDispatchPayment for () { + type Error = &'static str; + + fn pay_relayers_rewards( + _lane_id: LaneId, + _messages_relayers: VecDeque>, + _confirmation_relayer: &AccountId, + _received_range: &RangeInclusive, + ) { + } +} + +/// Send message artifacts. +#[derive(Eq, RuntimeDebug, PartialEq)] +pub struct SendMessageArtifacts { + /// Nonce of the message. + pub nonce: MessageNonce, + /// Actual weight of send message call. + pub weight: Weight, +} + +/// Messages bridge API to be used from other pallets. +pub trait MessagesBridge { + /// Error type. + type Error: Debug; + + /// Send message over the bridge. + /// + /// Returns unique message nonce or error if send has failed. + fn send_message( + sender: SenderOrigin, + lane: LaneId, + message: Payload, + ) -> Result; +} + +/// Bridge that does nothing when message is being sent. +#[derive(Eq, RuntimeDebug, PartialEq)] +pub struct NoopMessagesBridge; + +impl MessagesBridge for NoopMessagesBridge { + type Error = &'static str; + + fn send_message( + _sender: SenderOrigin, + _lane: LaneId, + _message: Payload, + ) -> Result { + Ok(SendMessageArtifacts { nonce: 0, weight: Weight::zero() }) + } +} + +/// Structure that may be used in place of `TargetHeaderChain`, `LaneMessageVerifier` and +/// `MessageDeliveryAndDispatchPayment` on chains, where outbound messages are forbidden. +pub struct ForbidOutboundMessages; + +/// Error message that is used in `ForbidOutboundMessages` implementation. +const ALL_OUTBOUND_MESSAGES_REJECTED: &str = + "This chain is configured to reject all outbound messages"; + +impl TargetHeaderChain for ForbidOutboundMessages { + type Error = &'static str; + + type MessagesDeliveryProof = (); + + fn verify_message(_payload: &Payload) -> Result<(), Self::Error> { + Err(ALL_OUTBOUND_MESSAGES_REJECTED) + } + + fn verify_messages_delivery_proof( + _proof: Self::MessagesDeliveryProof, + ) -> Result<(LaneId, InboundLaneData), Self::Error> { + Err(ALL_OUTBOUND_MESSAGES_REJECTED) + } +} + +impl LaneMessageVerifier for ForbidOutboundMessages { + type Error = &'static str; + + fn verify_message( + _submitter: &SenderOrigin, + _lane: &LaneId, + _outbound_data: &OutboundLaneData, + _payload: &Payload, + ) -> Result<(), Self::Error> { + Err(ALL_OUTBOUND_MESSAGES_REJECTED) + } +} + +impl MessageDeliveryAndDispatchPayment + for ForbidOutboundMessages +{ + type Error = &'static str; + + fn pay_relayers_rewards( + _lane_id: LaneId, + _messages_relayers: VecDeque>, + _confirmation_relayer: &AccountId, + _received_range: &RangeInclusive, + ) { + } +} diff --git a/primitives/messages/src/storage_keys.rs b/primitives/messages/src/storage_keys.rs new file mode 100644 index 00000000000..4db11edec34 --- /dev/null +++ b/primitives/messages/src/storage_keys.rs @@ -0,0 +1,128 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Storage keys of bridge messages pallet. + +/// Name of the `OPERATING_MODE_VALUE_NAME` storage value. +pub const OPERATING_MODE_VALUE_NAME: &str = "PalletOperatingMode"; +/// Name of the `OutboundMessages` storage map. +pub const OUTBOUND_MESSAGES_MAP_NAME: &str = "OutboundMessages"; +/// Name of the `OutboundLanes` storage map. +pub const OUTBOUND_LANES_MAP_NAME: &str = "OutboundLanes"; +/// Name of the `InboundLanes` storage map. +pub const INBOUND_LANES_MAP_NAME: &str = "InboundLanes"; + +use crate::{LaneId, MessageKey, MessageNonce}; + +use codec::Encode; +use frame_support::Blake2_128Concat; +use sp_core::storage::StorageKey; + +/// Storage key of the `PalletOperatingMode` value in the runtime storage. +pub fn operating_mode_key(pallet_prefix: &str) -> StorageKey { + StorageKey( + bp_runtime::storage_value_final_key( + pallet_prefix.as_bytes(), + OPERATING_MODE_VALUE_NAME.as_bytes(), + ) + .to_vec(), + ) +} + +/// Storage key of the outbound message in the runtime storage. +pub fn message_key(pallet_prefix: &str, lane: &LaneId, nonce: MessageNonce) -> StorageKey { + bp_runtime::storage_map_final_key::( + pallet_prefix, + OUTBOUND_MESSAGES_MAP_NAME, + &MessageKey { lane_id: *lane, nonce }.encode(), + ) +} + +/// Storage key of the outbound message lane state in the runtime storage. +pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { + bp_runtime::storage_map_final_key::( + pallet_prefix, + OUTBOUND_LANES_MAP_NAME, + lane, + ) +} + +/// Storage key of the inbound message lane state in the runtime storage. +pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { + bp_runtime::storage_map_final_key::( + pallet_prefix, + INBOUND_LANES_MAP_NAME, + lane, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn operating_mode_key_computed_properly() { + // If this test fails, then something has been changed in module storage that is possibly + // breaking all existing message relays. + let storage_key = operating_mode_key("BridgeMessages").0; + assert_eq!( + storage_key, + hex!("dd16c784ebd3390a9bc0357c7511ed010f4cf0917788d791142ff6c1f216e7b3").to_vec(), + "Unexpected storage key: {}", + hex::encode(&storage_key), + ); + } + + #[test] + fn storage_message_key_computed_properly() { + // If this test fails, then something has been changed in module storage that is breaking + // all previously crafted messages proofs. + let storage_key = message_key("BridgeMessages", b"test", 42).0; + assert_eq!( + storage_key, + hex!("dd16c784ebd3390a9bc0357c7511ed018a395e6242c6813b196ca31ed0547ea79446af0e09063bd4a7874aef8a997cec746573742a00000000000000").to_vec(), + "Unexpected storage key: {}", + hex::encode(&storage_key), + ); + } + + #[test] + fn outbound_lane_data_key_computed_properly() { + // If this test fails, then something has been changed in module storage that is breaking + // all previously crafted outbound lane state proofs. + let storage_key = outbound_lane_data_key("BridgeMessages", b"test").0; + assert_eq!( + storage_key, + hex!("dd16c784ebd3390a9bc0357c7511ed0196c246acb9b55077390e3ca723a0ca1f44a8995dd50b6657a037a7839304535b74657374").to_vec(), + "Unexpected storage key: {}", + hex::encode(&storage_key), + ); + } + + #[test] + fn inbound_lane_data_key_computed_properly() { + // If this test fails, then something has been changed in module storage that is breaking + // all previously crafted inbound lane state proofs. + let storage_key = inbound_lane_data_key("BridgeMessages", b"test").0; + assert_eq!( + storage_key, + hex!("dd16c784ebd3390a9bc0357c7511ed01e5f83cf83f2127eb47afdc35d6e43fab44a8995dd50b6657a037a7839304535b74657374").to_vec(), + "Unexpected storage key: {}", + hex::encode(&storage_key), + ); + } +} diff --git a/primitives/messages/src/target_chain.rs b/primitives/messages/src/target_chain.rs new file mode 100644 index 00000000000..6487508588d --- /dev/null +++ b/primitives/messages/src/target_chain.rs @@ -0,0 +1,163 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives of messages module, that are used on the target chain. + +use crate::{LaneId, Message, MessageKey, MessagePayload, OutboundLaneData}; + +use bp_runtime::{messages::MessageDispatchResult, Size}; +use codec::{Decode, Encode, Error as CodecError}; +use frame_support::{weights::Weight, Parameter, RuntimeDebug}; +use scale_info::TypeInfo; +use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, prelude::*}; + +/// Proved messages from the source chain. +pub type ProvedMessages = BTreeMap>; + +/// Proved messages from single lane of the source chain. +#[derive(RuntimeDebug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +pub struct ProvedLaneMessages { + /// Optional outbound lane state. + pub lane_state: Option, + /// Messages sent through this lane. + pub messages: Vec, +} + +/// Message data with decoded dispatch payload. +#[derive(RuntimeDebug)] +pub struct DispatchMessageData { + /// Result of dispatch payload decoding. + pub payload: Result, +} + +/// Message with decoded dispatch payload. +#[derive(RuntimeDebug)] +pub struct DispatchMessage { + /// Message key. + pub key: MessageKey, + /// Message data with decoded dispatch payload. + pub data: DispatchMessageData, +} + +/// Source chain API. Used by target chain, to verify source chain proofs. +/// +/// All implementations of this trait should only work with finalized data that +/// can't change. Wrong implementation may lead to invalid lane states (i.e. lane +/// that's stuck) and/or processing messages without paying fees. +pub trait SourceHeaderChain { + /// Error type. + type Error: Debug + Into<&'static str>; + + /// Proof that messages are sent from source chain. This may also include proof + /// of corresponding outbound lane states. + type MessagesProof: Parameter + Size; + + /// Verify messages proof and return proved messages. + /// + /// Returns error if either proof is incorrect, or the number of messages in the proof + /// is not matching the `messages_count`. + /// + /// Messages vector is required to be sorted by nonce within each lane. Out-of-order + /// messages will be rejected. + /// + /// The `messages_count` argument verification (sane limits) is supposed to be made + /// outside this function. This function only verifies that the proof declares exactly + /// `messages_count` messages. + fn verify_messages_proof( + proof: Self::MessagesProof, + messages_count: u32, + ) -> Result, Self::Error>; +} + +/// Called when inbound message is received. +pub trait MessageDispatch { + /// Decoded message payload type. Valid message may contain invalid payload. In this case + /// message is delivered, but dispatch fails. Therefore, two separate types of payload + /// (opaque `MessagePayload` used in delivery and this `DispatchPayload` used in dispatch). + type DispatchPayload: Decode; + + /// Estimate dispatch weight. + /// + /// This function must return correct upper bound of dispatch weight. The return value + /// of this function is expected to match return value of the corresponding + /// `FromInboundLaneApi::message_details().dispatch_weight` call. + fn dispatch_weight(message: &mut DispatchMessage) -> Weight; + + /// Called when inbound message is received. + /// + /// It is up to the implementers of this trait to determine whether the message + /// is invalid (i.e. improperly encoded, has too large weight, ...) or not. + /// + /// If your configuration allows paying dispatch fee at the target chain, then + /// it must be paid inside this method to the `relayer_account`. + fn dispatch( + relayer_account: &AccountId, + message: DispatchMessage, + ) -> MessageDispatchResult; +} + +impl Default for ProvedLaneMessages { + fn default() -> Self { + ProvedLaneMessages { lane_state: None, messages: Vec::new() } + } +} + +impl From for DispatchMessage { + fn from(message: Message) -> Self { + DispatchMessage { key: message.key, data: message.payload.into() } + } +} + +impl From for DispatchMessageData { + fn from(payload: MessagePayload) -> Self { + DispatchMessageData { payload: DispatchPayload::decode(&mut &payload[..]) } + } +} + +/// Structure that may be used in place of `SourceHeaderChain` and `MessageDispatch` on chains, +/// where inbound messages are forbidden. +pub struct ForbidInboundMessages; + +/// Error message that is used in `ForbidOutboundMessages` implementation. +const ALL_INBOUND_MESSAGES_REJECTED: &str = + "This chain is configured to reject all inbound messages"; + +impl SourceHeaderChain for ForbidInboundMessages { + type Error = &'static str; + type MessagesProof = (); + + fn verify_messages_proof( + _proof: Self::MessagesProof, + _messages_count: u32, + ) -> Result, Self::Error> { + Err(ALL_INBOUND_MESSAGES_REJECTED) + } +} + +impl MessageDispatch for ForbidInboundMessages { + type DispatchPayload = (); + + fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + Weight::MAX + } + + fn dispatch(_: &AccountId, _: DispatchMessage) -> MessageDispatchResult { + MessageDispatchResult { + unspent_weight: Weight::zero(), + dispatch_fee_paid_during_dispatch: false, + } + } +} diff --git a/primitives/parachains/Cargo.toml b/primitives/parachains/Cargo.toml new file mode 100644 index 00000000000..0aa2adfead1 --- /dev/null +++ b/primitives/parachains/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bp-parachains" +description = "Primitives of parachains module." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +# Bridge dependencies + +bp-polkadot-core = { path = "../polkadot-core", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-polkadot-core/std", + "bp-runtime/std", + "codec/std", + "frame-support/std", + "scale-info/std", + "sp-core/std", +] diff --git a/primitives/parachains/src/lib.rs b/primitives/parachains/src/lib.rs new file mode 100644 index 00000000000..f2edebf8a22 --- /dev/null +++ b/primitives/parachains/src/lib.rs @@ -0,0 +1,90 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives of parachains module. + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_polkadot_core::{ + parachains::{ParaHash, ParaHead, ParaId}, + BlockNumber as RelayBlockNumber, +}; +use bp_runtime::{StorageDoubleMapKeyProvider, StorageMapKeyProvider}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{Blake2_128Concat, RuntimeDebug, Twox64Concat}; +use scale_info::TypeInfo; +use sp_core::storage::StorageKey; + +/// Best known parachain head hash. +#[derive(Clone, Decode, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub struct BestParaHeadHash { + /// Number of relay block where this head has been read. + /// + /// Parachain head is opaque to relay chain. So we can't simply decode it as a header of + /// parachains and call `block_number()` on it. Instead, we're using the fact that parachain + /// head is always built on top of previous head (because it is blockchain) and relay chain + /// always imports parachain heads in order. What it means for us is that at any given + /// **finalized** relay block `B`, head of parachain will be ancestor (or the same) of all + /// parachain heads available at descendants of `B`. + pub at_relay_block_number: RelayBlockNumber, + /// Hash of parachain head. + pub head_hash: ParaHash, +} + +/// Best known parachain head as it is stored in the runtime storage. +#[derive(Decode, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub struct ParaInfo { + /// Best known parachain head hash. + pub best_head_hash: BestParaHeadHash, + /// Current ring buffer position for this parachain. + pub next_imported_hash_position: u32, +} + +/// Returns runtime storage key of given parachain head at the source chain. +/// +/// The head is stored by the `paras` pallet in the `Heads` map. +pub fn parachain_head_storage_key_at_source( + paras_pallet_name: &str, + para_id: ParaId, +) -> StorageKey { + bp_runtime::storage_map_final_key::(paras_pallet_name, "Heads", ¶_id.encode()) +} + +/// Can be use to access the runtime storage key of the parachains info at the target chain. +/// +/// The info is stored by the `pallet-bridge-parachains` pallet in the `ParasInfo` map. +pub struct ParasInfoKeyProvider; +impl StorageMapKeyProvider for ParasInfoKeyProvider { + const MAP_NAME: &'static str = "ParasInfo"; + + type Hasher = Blake2_128Concat; + type Key = ParaId; + type Value = ParaInfo; +} + +/// Can be use to access the runtime storage key of the parachain head at the target chain. +/// +/// The head is stored by the `pallet-bridge-parachains` pallet in the `ImportedParaHeads` map. +pub struct ImportedParaHeadsKeyProvider; +impl StorageDoubleMapKeyProvider for ImportedParaHeadsKeyProvider { + const MAP_NAME: &'static str = "ImportedParaHeads"; + + type Hasher1 = Blake2_128Concat; + type Key1 = ParaId; + type Hasher2 = Blake2_128Concat; + type Key2 = ParaHash; + type Value = ParaHead; +} diff --git a/primitives/polkadot-core/Cargo.toml b/primitives/polkadot-core/Cargo.toml new file mode 100644 index 00000000000..a9db53a8bf0 --- /dev/null +++ b/primitives/polkadot-core/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "bp-polkadot-core" +description = "Primitives of Polkadot-like runtime." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } +parity-util-mem = { version = "0.12.0", optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0", optional = true, features = ["derive"] } + +# Bridge Dependencies + +bp-messages = { path = "../messages", default-features = false } +bp-runtime = { path = "../runtime", default-features = false } + +# Substrate Based Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +hex = "0.4" + +[features] +default = ["std"] +std = [ + "bp-messages/std", + "bp-runtime/std", + "frame-support/std", + "frame-system/std", + "codec/std", + "parity-util-mem", + "scale-info/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/primitives/polkadot-core/src/lib.rs b/primitives/polkadot-core/src/lib.rs new file mode 100644 index 00000000000..878c3dc38eb --- /dev/null +++ b/primitives/polkadot-core/src/lib.rs @@ -0,0 +1,385 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_messages::MessageNonce; +use bp_runtime::{Chain, EncodedOrDecodedCall}; +use codec::Compact; +use frame_support::{ + dispatch::{DispatchClass, Dispatchable}, + parameter_types, + weights::{ + constants::{BlockExecutionWeight, WEIGHT_PER_SECOND}, + Weight, + }, + Blake2_128Concat, RuntimeDebug, StorageHasher, Twox128, +}; +use frame_system::limits; +use scale_info::{StaticTypeInfo, TypeInfo}; +use sp_core::Hasher as HasherT; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, DispatchInfoOf, IdentifyAccount, Verify}, + transaction_validity::TransactionValidityError, + MultiAddress, MultiSignature, OpaqueExtrinsic, +}; +use sp_std::prelude::Vec; + +// Re-export's to avoid extra substrate dependencies in chain-specific crates. +pub use frame_support::{weights::constants::ExtrinsicBaseWeight, Parameter}; +pub use sp_runtime::{traits::Convert, Perbill}; + +pub mod parachains; + +/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at +/// Polkadot-like chain. This mostly depends on number of entries in the storage trie. +/// Some reserve is reserved to account future chain growth. +/// +/// To compute this value, we've synced Kusama chain blocks [0; 6545733] to see if there were +/// any significant changes of the storage proof size (NO): +/// +/// - at block 3072 the storage proof size overhead was 579 bytes; +/// - at block 2479616 it was 578 bytes; +/// - at block 4118528 it was 711 bytes; +/// - at block 6540800 it was 779 bytes. +/// +/// The number of storage entries at the block 6546170 was 351207 and number of trie nodes in +/// the storage proof was 5 (log(16, 351207) ~ 4.6). +/// +/// So the assumption is that the storage proof size overhead won't be larger than 1024 in the +/// nearest future. If it'll ever break this barrier, then we'll need to update this constant +/// at next runtime upgrade. +pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; + +/// All Polkadot-like chains allow normal extrinsics to fill block up to 75 percent. +/// +/// This is a copy-paste from the Polkadot repo's `polkadot-runtime-common` crate. +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +/// All Polkadot-like chains allow 2 seconds of compute with a 6-second average block time. +/// +/// This is a copy-paste from the Polkadot repo's `polkadot-runtime-common` crate. +// TODO: https://github.com/paritytech/parity-bridges-common/issues/1543 - remove `set_proof_size` +pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND.set_proof_size(1_000).saturating_mul(2); + +/// All Polkadot-like chains assume that an on-initialize consumes 1 percent of the weight on +/// average, hence a single extrinsic will not be allowed to consume more than +/// `AvailableBlockRatio - 1 percent`. +/// +/// This is a copy-paste from the Polkadot repo's `polkadot-runtime-common` crate. +pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(1); + +parameter_types! { + /// All Polkadot-like chains have maximal block size set to 5MB. + /// + /// This is a copy-paste from the Polkadot repo's `polkadot-runtime-common` crate. + pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio( + 5 * 1024 * 1024, + NORMAL_DISPATCH_RATIO, + ); + /// All Polkadot-like chains have the same block weights. + /// + /// This is a copy-paste from the Polkadot repo's `polkadot-runtime-common` crate. + pub BlockWeights: limits::BlockWeights = limits::BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have an extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT, + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); +} + +// TODO [#78] may need to be updated after https://github.com/paritytech/parity-bridges-common/issues/78 +/// Maximal number of messages in single delivery transaction. +pub const MAX_MESSAGES_IN_DELIVERY_TRANSACTION: MessageNonce = 128; + +/// Maximal number of unrewarded relayer entries at inbound lane. +pub const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 128; + +// TODO [#438] should be selected keeping in mind: +// finality delay on both chains + reward payout cost + messages throughput. +/// Maximal number of unconfirmed messages at inbound lane. +pub const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 8192; + +/// Maximal number of bytes, included in the signed Polkadot-like transaction apart from the encoded +/// call itself. +/// +/// Can be computed by subtracting encoded call size from raw transaction size. +pub const TX_EXTRA_BYTES: u32 = 256; + +/// Re-export `time_units` to make usage easier. +pub use time_units::*; + +/// Human readable time units defined in terms of number of blocks. +pub mod time_units { + use super::BlockNumber; + + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + + pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; +} + +/// Block number type used in Polkadot-like chains. +pub type BlockNumber = u32; + +/// Hash type used in Polkadot-like chains. +pub type Hash = ::Out; + +/// Account Index (a.k.a. nonce). +pub type Index = u32; + +/// Hashing type. +pub type Hashing = BlakeTwo256; + +/// The type of object that can produce hashes on Polkadot-like chains. +pub type Hasher = BlakeTwo256; + +/// The header type used by Polkadot-like chains. +pub type Header = generic::Header; + +/// Signature type used by Polkadot-like chains. +pub type Signature = MultiSignature; + +/// Public key of account on Polkadot-like chains. +pub type AccountPublic = ::Signer; + +/// Id of account on Polkadot-like chains. +pub type AccountId = ::AccountId; + +/// Address of account on Polkadot-like chains. +pub type AccountAddress = MultiAddress; + +/// Index of a transaction on the Polkadot-like chains. +pub type Nonce = u32; + +/// Block type of Polkadot-like chains. +pub type Block = generic::Block; + +/// Polkadot-like block signed with a Justification. +pub type SignedBlock = generic::SignedBlock; + +/// The balance of an account on Polkadot-like chain. +pub type Balance = u128; + +/// Unchecked Extrinsic type. +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic< + AccountAddress, + EncodedOrDecodedCall, + Signature, + SignedExtensions, +>; + +/// Account address, used by the Polkadot-like chain. +pub type Address = MultiAddress; + +/// A type of the data encoded as part of the transaction. +pub type SignedExtra = + ((), (), (), (), sp_runtime::generic::Era, Compact, (), Compact); + +/// Parameters which are part of the payload used to produce transaction signature, +/// but don't end up in the transaction itself (i.e. inherent part of the runtime). +pub type AdditionalSigned = ((), u32, u32, Hash, Hash, (), (), ()); + +/// A simplified version of signed extensions meant for producing signed transactions +/// and signed payload in the client code. +#[derive(PartialEq, Eq, Clone, RuntimeDebug, TypeInfo)] +pub struct SignedExtensions { + encode_payload: SignedExtra, + // It may be set to `None` if extensions are decoded. We are never reconstructing transactions + // (and it makes no sense to do that) => decoded version of `SignedExtensions` is only used to + // read fields of `encode_payload`. And when resigning transaction, we're reconstructing + // `SignedExtensions` from the scratch. + additional_signed: Option, + _data: sp_std::marker::PhantomData, +} + +impl codec::Encode for SignedExtensions { + fn using_encoded R>(&self, f: F) -> R { + self.encode_payload.using_encoded(f) + } +} + +impl codec::Decode for SignedExtensions { + fn decode(input: &mut I) -> Result { + SignedExtra::decode(input).map(|encode_payload| SignedExtensions { + encode_payload, + additional_signed: None, + _data: Default::default(), + }) + } +} + +impl SignedExtensions { + pub fn new( + spec_version: u32, + transaction_version: u32, + era: bp_runtime::TransactionEraOf, + genesis_hash: Hash, + nonce: Nonce, + tip: Balance, + ) -> Self { + Self { + encode_payload: ( + (), // non-zero sender + (), // spec version + (), // tx version + (), // genesis + era.frame_era(), // era + nonce.into(), // nonce (compact encoding) + (), // Check weight + tip.into(), // transaction payment / tip (compact encoding) + ), + additional_signed: Some(( + (), + spec_version, + transaction_version, + genesis_hash, + era.signed_payload(genesis_hash), + (), + (), + (), + )), + _data: Default::default(), + } + } +} + +impl SignedExtensions { + /// Return signer nonce, used to craft transaction. + pub fn nonce(&self) -> Nonce { + self.encode_payload.5.into() + } + + /// Return transaction tip. + pub fn tip(&self) -> Balance { + self.encode_payload.7.into() + } +} + +impl sp_runtime::traits::SignedExtension for SignedExtensions +where + Call: codec::Codec + sp_std::fmt::Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + Call: Dispatchable, +{ + const IDENTIFIER: &'static str = "Not needed."; + + type AccountId = AccountId; + type Call = Call; + type AdditionalSigned = AdditionalSigned; + type Pre = (); + + fn additional_signed( + &self, + ) -> Result { + // we shall not ever see this error in relay, because we are never signing decoded + // transactions. Instead we're constructing and signing new transactions. So the error code + // is kinda random here + self.additional_signed + .ok_or(frame_support::unsigned::TransactionValidityError::Unknown( + frame_support::unsigned::UnknownTransaction::Custom(0xFF), + )) + } + + fn pre_dispatch( + self, + _who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result { + Ok(()) + } +} + +/// Polkadot-like chain. +#[derive(RuntimeDebug)] +pub struct PolkadotLike; + +impl Chain for PolkadotLike { + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; + + type AccountId = AccountId; + type Balance = Balance; + type Index = Index; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + *BlockLength::get().max.get(DispatchClass::Normal) + } + + fn max_extrinsic_weight() -> Weight { + BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or(Weight::MAX) + } +} + +/// Return a storage key for account data. +/// +/// This is based on FRAME storage-generation code from Substrate: +/// [link](https://github.com/paritytech/substrate/blob/c939ceba381b6313462d47334f775e128ea4e95d/frame/support/src/storage/generator/map.rs#L74) +/// The equivalent command to invoke in case full `Runtime` is known is this: +/// `let key = frame_system::Account::::storage_map_final_key(&account_id);` +pub fn account_info_storage_key(id: &AccountId) -> Vec { + let module_prefix_hashed = Twox128::hash(b"System"); + let storage_prefix_hashed = Twox128::hash(b"Account"); + let key_hashed = codec::Encode::using_encoded(id, Blake2_128Concat::hash); + + let mut final_key = Vec::with_capacity( + module_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len(), + ); + + final_key.extend_from_slice(&module_prefix_hashed[..]); + final_key.extend_from_slice(&storage_prefix_hashed[..]); + final_key.extend_from_slice(&key_hashed); + + final_key +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_generate_storage_key() { + let acc = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ] + .into(); + let key = account_info_storage_key(&acc); + assert_eq!(hex::encode(key), "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da92dccd599abfe1920a1cff8a7358231430102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); + } +} diff --git a/primitives/polkadot-core/src/parachains.rs b/primitives/polkadot-core/src/parachains.rs new file mode 100644 index 00000000000..51fcd59cae1 --- /dev/null +++ b/primitives/polkadot-core/src/parachains.rs @@ -0,0 +1,100 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives of polkadot-like chains, that are related to parachains functionality. +//! +//! Even though this (bridges) repository references polkadot repository, we can't +//! reference polkadot crates from pallets. That's because bridges repository is +//! included in the polkadot repository and included pallets are used by polkadot +//! chains. Having pallets that are referencing polkadot, would mean that there may +//! be two versions of polkadot crates included in the runtime. Which is bad. + +use bp_runtime::Size; +use codec::{CompactAs, Decode, Encode, MaxEncodedLen}; +use frame_support::RuntimeDebug; +use scale_info::TypeInfo; +use sp_core::Hasher; +use sp_std::vec::Vec; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "std")] +use parity_util_mem::MallocSizeOf; + +/// Parachain id. +/// +/// This is an equivalent of the `polkadot_parachain::Id`, which is a compact-encoded `u32`. +#[derive( + Clone, + CompactAs, + Copy, + Decode, + Default, + Encode, + Eq, + Hash, + MaxEncodedLen, + Ord, + PartialEq, + PartialOrd, + RuntimeDebug, + TypeInfo, +)] +pub struct ParaId(pub u32); + +impl From for ParaId { + fn from(id: u32) -> Self { + ParaId(id) + } +} + +/// Parachain head. +/// +/// This is an equivalent of the `polkadot_parachain::HeadData`. +/// +/// The parachain head means (at least in Cumulus) a SCALE-encoded parachain header. Keep in mind +/// that in Polkadot it is twice-encoded (so `header.encode().encode()`). We'll also do it to keep +/// it binary-compatible (implies hash-compatibility) with other parachain pallets. +#[derive( + PartialEq, Eq, Clone, PartialOrd, Ord, Encode, Decode, RuntimeDebug, TypeInfo, Default, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash, MallocSizeOf))] +pub struct ParaHead(pub Vec); + +impl ParaHead { + /// Returns the hash of this head data. + pub fn hash(&self) -> crate::Hash { + sp_runtime::traits::BlakeTwo256::hash(&self.0) + } +} + +/// Parachain head hash. +pub type ParaHash = crate::Hash; + +/// Parachain head hasher. +pub type ParaHasher = crate::Hasher; + +/// Raw storage proof of parachain heads, stored in polkadot-like chain runtime. +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct ParaHeadsProof(pub Vec>); + +impl Size for ParaHeadsProof { + fn size(&self) -> u32 { + u32::try_from(self.0.iter().fold(0usize, |sum, node| sum.saturating_add(node.len()))) + .unwrap_or(u32::MAX) + } +} diff --git a/primitives/relayers/Cargo.toml b/primitives/relayers/Cargo.toml new file mode 100644 index 00000000000..908412477c2 --- /dev/null +++ b/primitives/relayers/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "bp-relayers" +description = "Primitives of relayers module." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +hex = "0.4" +hex-literal = "0.3" + +[features] +default = ["std"] +std = [ + "frame-support/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/primitives/relayers/src/lib.rs b/primitives/relayers/src/lib.rs new file mode 100644 index 00000000000..d1c82d612e2 --- /dev/null +++ b/primitives/relayers/src/lib.rs @@ -0,0 +1,45 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives of messages module. + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::{fmt::Debug, marker::PhantomData}; + +/// Reward payment procedure. +pub trait PaymentProcedure { + /// Error that may be returned by the procedure. + type Error: Debug; + + /// Pay reward to the relayer. + fn pay_reward(relayer: &Relayer, reward: Reward) -> Result<(), Self::Error>; +} + +/// Reward payment procedure that is simply minting given amount of tokens. +pub struct MintReward(PhantomData<(T, Relayer)>); + +impl PaymentProcedure for MintReward +where + T: frame_support::traits::fungible::Mutate, +{ + type Error = sp_runtime::DispatchError; + + fn pay_reward(relayer: &Relayer, reward: T::Balance) -> Result<(), Self::Error> { + T::mint_into(relayer, reward) + } +} diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml new file mode 100644 index 00000000000..79f2b9fe03c --- /dev/null +++ b/primitives/runtime/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "bp-runtime" +description = "Primitives that may be used at (bridges) runtime level." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +hash-db = { version = "0.15.2", default-features = false } +num-traits = { version = "0.2", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0", optional = true, features = ["derive"] } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +trie-db = { version = "0.24.0", default-features = false } + +[dev-dependencies] +hex-literal = "0.3" + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "hash-db/std", + "num-traits/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-state-machine/std", + "sp-trie/std", + "trie-db/std", +] diff --git a/primitives/runtime/src/chain.rs b/primitives/runtime/src/chain.rs new file mode 100644 index 00000000000..d4a8d5aa020 --- /dev/null +++ b/primitives/runtime/src/chain.rs @@ -0,0 +1,347 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::HeaderIdProvider; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{weights::Weight, Parameter}; +use num_traits::{AsPrimitive, Bounded, CheckedSub, Saturating, SaturatingAdd, Zero}; +use sp_runtime::{ + traits::{ + AtLeast32Bit, AtLeast32BitUnsigned, Hash as HashT, Header as HeaderT, MaybeDisplay, + MaybeMallocSizeOf, MaybeSerialize, MaybeSerializeDeserialize, Member, SimpleBitOps, Verify, + }, + FixedPointOperand, +}; +use sp_std::{convert::TryFrom, fmt::Debug, hash::Hash, str::FromStr, vec, vec::Vec}; + +/// Chain call, that is either SCALE-encoded, or decoded. +#[derive(Debug, Clone, PartialEq)] +pub enum EncodedOrDecodedCall { + /// The call that is SCALE-encoded. + /// + /// This variant is used when we the chain runtime is not bundled with the relay, but + /// we still need the represent call in some RPC calls or transactions. + Encoded(Vec), + /// The decoded call. + Decoded(ChainCall), +} + +impl EncodedOrDecodedCall { + /// Returns decoded call. + pub fn to_decoded(&self) -> Result { + match self { + Self::Encoded(ref encoded_call) => + ChainCall::decode(&mut &encoded_call[..]).map_err(Into::into), + Self::Decoded(ref decoded_call) => Ok(decoded_call.clone()), + } + } + + /// Converts self to decoded call. + pub fn into_decoded(self) -> Result { + match self { + Self::Encoded(encoded_call) => + ChainCall::decode(&mut &encoded_call[..]).map_err(Into::into), + Self::Decoded(decoded_call) => Ok(decoded_call), + } + } +} + +impl From for EncodedOrDecodedCall { + fn from(call: ChainCall) -> EncodedOrDecodedCall { + EncodedOrDecodedCall::Decoded(call) + } +} + +impl Decode for EncodedOrDecodedCall { + fn decode(input: &mut I) -> Result { + // having encoded version is better than decoded, because decoding isn't required + // everywhere and for mocked calls it may lead to **unneeded** errors + match input.remaining_len()? { + Some(remaining_len) => { + let mut encoded_call = vec![0u8; remaining_len]; + input.read(&mut encoded_call)?; + Ok(EncodedOrDecodedCall::Encoded(encoded_call)) + }, + None => Ok(EncodedOrDecodedCall::Decoded(ChainCall::decode(input)?)), + } + } +} + +impl Encode for EncodedOrDecodedCall { + fn encode(&self) -> Vec { + match *self { + Self::Encoded(ref encoded_call) => encoded_call.clone(), + Self::Decoded(ref decoded_call) => decoded_call.encode(), + } + } +} + +/// Minimal Substrate-based chain representation that may be used from no_std environment. +pub trait Chain: Send + Sync + 'static { + /// A type that fulfills the abstract idea of what a Substrate block number is. + // Constraits come from the associated Number type of `sp_runtime::traits::Header` + // See here for more info: + // https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Number + // + // Note that the `AsPrimitive` trait is required by the GRANDPA justification + // verifier, and is not usually part of a Substrate Header's Number type. + type BlockNumber: Parameter + + Member + + MaybeSerializeDeserialize + + Hash + + Copy + + Default + + MaybeDisplay + + AtLeast32BitUnsigned + + FromStr + + MaybeMallocSizeOf + + AsPrimitive + + Default + + Saturating + + MaxEncodedLen + // original `sp_runtime::traits::Header::BlockNumber` doesn't have this trait, but + // `sp_runtime::generic::Era` requires block number -> `u64` conversion. + + Into; + + /// A type that fulfills the abstract idea of what a Substrate hash is. + // Constraits come from the associated Hash type of `sp_runtime::traits::Header` + // See here for more info: + // https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Hash + type Hash: Parameter + + Member + + MaybeSerializeDeserialize + + Hash + + Ord + + Copy + + MaybeDisplay + + Default + + SimpleBitOps + + AsRef<[u8]> + + AsMut<[u8]> + + MaybeMallocSizeOf + + MaxEncodedLen; + + /// A type that fulfills the abstract idea of what a Substrate hasher (a type + /// that produces hashes) is. + // Constraits come from the associated Hashing type of `sp_runtime::traits::Header` + // See here for more info: + // https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Hashing + type Hasher: HashT; + + /// A type that fulfills the abstract idea of what a Substrate header is. + // See here for more info: + // https://crates.parity.io/sp_runtime/traits/trait.Header.html + type Header: Parameter + + HeaderT + + HeaderIdProvider + + MaybeSerializeDeserialize; + + /// The user account identifier type for the runtime. + type AccountId: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + MaybeDisplay + + Ord + + MaxEncodedLen; + /// Balance of an account in native tokens. + /// + /// The chain may support multiple tokens, but this particular type is for token that is used + /// to pay for transaction dispatch, to reward different relayers (headers, messages), etc. + type Balance: AtLeast32BitUnsigned + + FixedPointOperand + + Parameter + + Member + + MaybeSerializeDeserialize + + Clone + + Copy + + Bounded + + CheckedSub + + PartialOrd + + SaturatingAdd + + Zero + + TryFrom + + MaxEncodedLen; + /// Index of a transaction used by the chain. + type Index: Parameter + + Member + + MaybeSerialize + + Debug + + Default + + MaybeDisplay + + MaybeSerializeDeserialize + + AtLeast32Bit + + Copy + + MaxEncodedLen; + /// Signature type, used on this chain. + type Signature: Parameter + Verify; + + /// Get the maximum size (in bytes) of a Normal extrinsic at this chain. + fn max_extrinsic_size() -> u32; + /// Get the maximum weight (compute time) that a Normal extrinsic at this chain can use. + fn max_extrinsic_weight() -> Weight; +} + +/// Minimal parachain representation that may be used from no_std environment. +pub trait Parachain: Chain { + /// Parachain identifier. + const PARACHAIN_ID: u32; +} + +/// Block number used by the chain. +pub type BlockNumberOf = ::BlockNumber; + +/// Hash type used by the chain. +pub type HashOf = ::Hash; + +/// Hasher type used by the chain. +pub type HasherOf = ::Hasher; + +/// Header type used by the chain. +pub type HeaderOf = ::Header; + +/// Account id type used by the chain. +pub type AccountIdOf = ::AccountId; + +/// Balance type used by the chain. +pub type BalanceOf = ::Balance; + +/// Transaction index type used by the chain. +pub type IndexOf = ::Index; + +/// Signature type used by the chain. +pub type SignatureOf = ::Signature; + +/// Account public type used by the chain. +pub type AccountPublicOf = as Verify>::Signer; + +/// Transaction era used by the chain. +pub type TransactionEraOf = crate::TransactionEra, HashOf>; + +/// Convenience macro that declares bridge finality runtime apis and related constants for a chain. +/// This includes: +/// - chain-specific bridge runtime APIs: +/// - `FinalityApi` +/// - constants that are stringified names of runtime API methods: +/// - `BEST_FINALIZED__HEADER_METHOD` +/// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`). +#[macro_export] +macro_rules! decl_bridge_finality_runtime_apis { + ($chain: ident) => { + bp_runtime::paste::item! { + mod [<$chain _finality_api>] { + use super::*; + + /// Name of the `FinalityApi::best_finalized` runtime method. + pub const []: &str = + stringify!([<$chain:camel FinalityApi_best_finalized>]); + + sp_api::decl_runtime_apis! { + /// API for querying information about the finalized chain headers. + /// + /// This API is implemented by runtimes that are receiving messages from this chain, not by this + /// chain's runtime itself. + pub trait [<$chain:camel FinalityApi>] { + /// Returns number and hash of the best finalized header known to the bridge module. + fn best_finalized() -> Option>; + } + } + } + + pub use [<$chain _finality_api>]::*; + } + }; +} + +/// Convenience macro that declares bridge messages runtime apis and related constants for a chain. +/// This includes: +/// - chain-specific bridge runtime APIs: +/// - `ToOutboundLaneApi` +/// - `FromInboundLaneApi` +/// - constants that are stringified names of runtime API methods: +/// - `TO__ESTIMATE_MESSAGE_FEE_METHOD` +/// - `TO__MESSAGE_DETAILS_METHOD` +/// - `FROM__MESSAGE_DETAILS_METHOD`, +/// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`). +#[macro_export] +macro_rules! decl_bridge_messages_runtime_apis { + ($chain: ident) => { + bp_runtime::paste::item! { + mod [<$chain _messages_api>] { + use super::*; + + /// Name of the `ToOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime + /// method. + pub const []: &str = + stringify!([]); + /// Name of the `ToOutboundLaneApi::message_details` runtime method. + pub const []: &str = + stringify!([]); + + /// Name of the `FromInboundLaneApi::message_details` runtime method. + pub const []: &str = + stringify!([]); + + sp_api::decl_runtime_apis! { + /// Outbound message lane API for messages that are sent to this chain. + /// + /// This API is implemented by runtimes that are receiving messages from this chain, not by this + /// chain's runtime itself. + pub trait [] { + /// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all + /// messages in given inclusive range. + /// + /// If some (or all) messages are missing from the storage, they'll also will + /// be missing from the resulting vector. The vector is ordered by the nonce. + fn message_details( + lane: LaneId, + begin: MessageNonce, + end: MessageNonce, + ) -> Vec; + } + + /// Inbound message lane API for messages sent by this chain. + /// + /// This API is implemented by runtimes that are receiving messages from this chain, not by this + /// chain's runtime itself. + /// + /// Entries of the resulting vector are matching entries of the `messages` vector. Entries of the + /// `messages` vector may (and need to) be read using `ToOutboundLaneApi::message_details`. + pub trait [] { + /// Return details of given inbound messages. + fn message_details( + lane: LaneId, + messages: Vec<(MessagePayload, OutboundMessageDetails)>, + ) -> Vec; + } + } + } + + pub use [<$chain _messages_api>]::*; + } + }; +} + +/// Convenience macro that declares bridge finality runtime apis, bridge messages runtime apis +/// and related constants for a chain. +/// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`). +#[macro_export] +macro_rules! decl_bridge_runtime_apis { + ($chain: ident) => { + bp_runtime::decl_bridge_finality_runtime_apis!($chain); + bp_runtime::decl_bridge_messages_runtime_apis!($chain); + }; +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs new file mode 100644 index 00000000000..75d75c2beff --- /dev/null +++ b/primitives/runtime/src/lib.rs @@ -0,0 +1,508 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives that may be used at (bridges) runtime level. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use frame_support::{ + log, pallet_prelude::DispatchResult, weights::Weight, PalletError, RuntimeDebug, StorageHasher, + StorageValue, +}; +use frame_system::RawOrigin; +use scale_info::TypeInfo; +use sp_core::{hash::H256, storage::StorageKey}; +use sp_io::hashing::blake2_256; +use sp_runtime::traits::{BadOrigin, Header as HeaderT}; +use sp_std::{convert::TryFrom, fmt::Debug, vec, vec::Vec}; + +pub use chain::{ + AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain, EncodedOrDecodedCall, HashOf, + HasherOf, HeaderOf, IndexOf, Parachain, SignatureOf, TransactionEraOf, +}; +pub use frame_support::storage::storage_prefix as storage_value_final_key; +use num_traits::{CheckedSub, One}; +use sp_runtime::transaction_validity::TransactionValidity; +pub use storage_proof::{ + record_all_keys as record_all_trie_keys, Error as StorageProofError, + ProofSize as StorageProofSize, StorageProofChecker, +}; +pub use storage_types::BoundedStorageValue; + +#[cfg(feature = "std")] +pub use storage_proof::craft_valid_storage_proof; + +pub mod messages; + +mod chain; +mod storage_proof; +mod storage_types; + +// Re-export macro to aviod include paste dependency everywhere +pub use sp_runtime::paste; + +/// Use this when something must be shared among all instances. +pub const NO_INSTANCE_ID: ChainId = [0, 0, 0, 0]; + +/// Bridge-with-Rialto instance id. +pub const RIALTO_CHAIN_ID: ChainId = *b"rlto"; + +/// Bridge-with-RialtoParachain instance id. +pub const RIALTO_PARACHAIN_CHAIN_ID: ChainId = *b"rlpa"; + +/// Bridge-with-Millau instance id. +pub const MILLAU_CHAIN_ID: ChainId = *b"mlau"; + +/// Bridge-with-Polkadot instance id. +pub const POLKADOT_CHAIN_ID: ChainId = *b"pdot"; + +/// Bridge-with-Kusama instance id. +pub const KUSAMA_CHAIN_ID: ChainId = *b"ksma"; + +/// Bridge-with-Rococo instance id. +pub const ROCOCO_CHAIN_ID: ChainId = *b"roco"; + +/// Bridge-with-Wococo instance id. +pub const WOCOCO_CHAIN_ID: ChainId = *b"woco"; + +/// Bridge-with-BridgeHubRococo instance id. +pub const BRIDGE_HUB_ROCOCO_CHAIN_ID: ChainId = *b"bhro"; + +/// Bridge-with-BridgeHubWococo instance id. +pub const BRIDGE_HUB_WOCOCO_CHAIN_ID: ChainId = *b"bhwo"; + +/// Call-dispatch module prefix. +pub const CALL_DISPATCH_MODULE_PREFIX: &[u8] = b"pallet-bridge/dispatch"; + +/// A unique prefix for entropy when generating cross-chain account IDs. +pub const ACCOUNT_DERIVATION_PREFIX: &[u8] = b"pallet-bridge/account-derivation/account"; + +/// A unique prefix for entropy when generating a cross-chain account ID for the Root account. +pub const ROOT_ACCOUNT_DERIVATION_PREFIX: &[u8] = b"pallet-bridge/account-derivation/root"; + +/// Generic header Id. +#[derive( + RuntimeDebug, Default, Clone, Encode, Decode, Copy, Eq, Hash, PartialEq, PartialOrd, Ord, +)] +pub struct HeaderId(pub Number, pub Hash); + +/// Generic header id provider. +pub trait HeaderIdProvider { + // Get the header id. + fn id(&self) -> HeaderId; + + // Get the header id for the parent block. + fn parent_id(&self) -> Option>; +} + +impl HeaderIdProvider
for Header { + fn id(&self) -> HeaderId { + HeaderId(*self.number(), self.hash()) + } + + fn parent_id(&self) -> Option> { + self.number() + .checked_sub(&One::one()) + .map(|parent_number| HeaderId(parent_number, *self.parent_hash())) + } +} + +/// Unique identifier of the chain. +/// +/// In addition to its main function (identifying the chain), this type may also be used to +/// identify module instance. We have a bunch of pallets that may be used in different bridges. E.g. +/// messages pallet may be deployed twice in the same runtime to bridge ThisChain with Chain1 and +/// Chain2. Sometimes we need to be able to identify deployed instance dynamically. This type may be +/// used for that. +pub type ChainId = [u8; 4]; + +/// Type of accounts on the source chain. +pub enum SourceAccount { + /// An account that belongs to Root (privileged origin). + Root, + /// A non-privileged account. + /// + /// The embedded account ID may or may not have a private key depending on the "owner" of the + /// account (private key, pallet, proxy, etc.). + Account(T), +} + +/// Derive an account ID from a foreign account ID. +/// +/// This function returns an encoded Blake2 hash. It is the responsibility of the caller to ensure +/// this can be successfully decoded into an AccountId. +/// +/// The `bridge_id` is used to provide extra entropy when producing account IDs. This helps prevent +/// AccountId collisions between different bridges on a single target chain. +/// +/// Note: If the same `bridge_id` is used across different chains (for example, if one source chain +/// is bridged to multiple target chains), then all the derived accounts would be the same across +/// the different chains. This could negatively impact users' privacy across chains. +pub fn derive_account_id(bridge_id: ChainId, id: SourceAccount) -> H256 +where + AccountId: Encode, +{ + match id { + SourceAccount::Root => + (ROOT_ACCOUNT_DERIVATION_PREFIX, bridge_id).using_encoded(blake2_256), + SourceAccount::Account(id) => + (ACCOUNT_DERIVATION_PREFIX, bridge_id, id).using_encoded(blake2_256), + } + .into() +} + +/// Anything that has size. +pub trait Size { + /// Return size of this object (in bytes). + fn size(&self) -> u32; +} + +impl Size for () { + fn size(&self) -> u32 { + 0 + } +} + +impl Size for Vec { + fn size(&self) -> u32 { + self.len() as _ + } +} + +/// Pre-computed size. +pub struct PreComputedSize(pub usize); + +impl Size for PreComputedSize { + fn size(&self) -> u32 { + u32::try_from(self.0).unwrap_or(u32::MAX) + } +} + +/// Era of specific transaction. +#[derive(RuntimeDebug, Clone, Copy, PartialEq)] +pub enum TransactionEra { + /// Transaction is immortal. + Immortal, + /// Transaction is valid for a given number of blocks, starting from given block. + Mortal(HeaderId, u32), +} + +impl, BlockHash: Copy> TransactionEra { + /// Prepare transaction era, based on mortality period and current best block number. + pub fn new( + best_block_id: HeaderId, + mortality_period: Option, + ) -> Self { + mortality_period + .map(|mortality_period| TransactionEra::Mortal(best_block_id, mortality_period)) + .unwrap_or(TransactionEra::Immortal) + } + + /// Create new immortal transaction era. + pub fn immortal() -> Self { + TransactionEra::Immortal + } + + /// Returns mortality period if transaction is mortal. + pub fn mortality_period(&self) -> Option { + match *self { + TransactionEra::Immortal => None, + TransactionEra::Mortal(_, period) => Some(period), + } + } + + /// Returns era that is used by FRAME-based runtimes. + pub fn frame_era(&self) -> sp_runtime::generic::Era { + match *self { + TransactionEra::Immortal => sp_runtime::generic::Era::immortal(), + TransactionEra::Mortal(header_id, period) => + sp_runtime::generic::Era::mortal(period as _, header_id.0.into()), + } + } + + /// Returns header hash that needs to be included in the signature payload. + pub fn signed_payload(&self, genesis_hash: BlockHash) -> BlockHash { + match *self { + TransactionEra::Immortal => genesis_hash, + TransactionEra::Mortal(header_id, _) => header_id.1, + } + } +} + +/// This is a copy of the +/// `frame_support::storage::generator::StorageMap::storage_map_final_key` for maps based +/// on selected hasher. +/// +/// We're using it because to call `storage_map_final_key` directly, we need access to the runtime +/// and pallet instance, which (sometimes) is impossible. +pub fn storage_map_final_key( + pallet_prefix: &str, + map_name: &str, + key: &[u8], +) -> StorageKey { + let key_hashed = H::hash(key); + let pallet_prefix_hashed = frame_support::Twox128::hash(pallet_prefix.as_bytes()); + let storage_prefix_hashed = frame_support::Twox128::hash(map_name.as_bytes()); + + let mut final_key = Vec::with_capacity( + pallet_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.as_ref().len(), + ); + + final_key.extend_from_slice(&pallet_prefix_hashed[..]); + final_key.extend_from_slice(&storage_prefix_hashed[..]); + final_key.extend_from_slice(key_hashed.as_ref()); + + StorageKey(final_key) +} + +/// This is how a storage key of storage parameter (`parameter_types! { storage Param: bool = false; +/// }`) is computed. +/// +/// Copied from `frame_support::parameter_types` macro. +pub fn storage_parameter_key(parameter_name: &str) -> StorageKey { + let mut buffer = Vec::with_capacity(1 + parameter_name.len() + 1); + buffer.push(b':'); + buffer.extend_from_slice(parameter_name.as_bytes()); + buffer.push(b':'); + StorageKey(sp_io::hashing::twox_128(&buffer).to_vec()) +} + +/// This is how a storage key of storage value is computed. +/// +/// Copied from `frame_support::storage::storage_prefix`. +pub fn storage_value_key(pallet_prefix: &str, value_name: &str) -> StorageKey { + let pallet_hash = sp_io::hashing::twox_128(pallet_prefix.as_bytes()); + let storage_hash = sp_io::hashing::twox_128(value_name.as_bytes()); + + let mut final_key = vec![0u8; 32]; + final_key[..16].copy_from_slice(&pallet_hash); + final_key[16..].copy_from_slice(&storage_hash); + + StorageKey(final_key) +} + +/// Can be use to access the runtime storage key of a `StorageMap`. +pub trait StorageMapKeyProvider { + /// The name of the variable that holds the `StorageMap`. + const MAP_NAME: &'static str; + + /// The same as `StorageMap::Hasher1`. + type Hasher: StorageHasher; + /// The same as `StorageMap::Key1`. + type Key: FullCodec; + /// The same as `StorageMap::Value`. + type Value: FullCodec; + + /// This is a copy of the + /// `frame_support::storage::generator::StorageMap::storage_map_final_key`. + /// + /// We're using it because to call `storage_map_final_key` directly, we need access + /// to the runtime and pallet instance, which (sometimes) is impossible. + fn final_key(pallet_prefix: &str, key: &Self::Key) -> StorageKey { + storage_map_final_key::(pallet_prefix, Self::MAP_NAME, &key.encode()) + } +} + +/// Can be use to access the runtime storage key of a `StorageDoubleMap`. +pub trait StorageDoubleMapKeyProvider { + /// The name of the variable that holds the `StorageDoubleMap`. + const MAP_NAME: &'static str; + + /// The same as `StorageDoubleMap::Hasher1`. + type Hasher1: StorageHasher; + /// The same as `StorageDoubleMap::Key1`. + type Key1: FullCodec; + /// The same as `StorageDoubleMap::Hasher2`. + type Hasher2: StorageHasher; + /// The same as `StorageDoubleMap::Key2`. + type Key2: FullCodec; + /// The same as `StorageDoubleMap::Value`. + type Value: FullCodec; + + /// This is a copy of the + /// `frame_support::storage::generator::StorageDoubleMap::storage_double_map_final_key`. + /// + /// We're using it because to call `storage_double_map_final_key` directly, we need access + /// to the runtime and pallet instance, which (sometimes) is impossible. + fn final_key(pallet_prefix: &str, key1: &Self::Key1, key2: &Self::Key2) -> StorageKey { + let key1_hashed = Self::Hasher1::hash(&key1.encode()); + let key2_hashed = Self::Hasher2::hash(&key2.encode()); + let pallet_prefix_hashed = frame_support::Twox128::hash(pallet_prefix.as_bytes()); + let storage_prefix_hashed = frame_support::Twox128::hash(Self::MAP_NAME.as_bytes()); + + let mut final_key = Vec::with_capacity( + pallet_prefix_hashed.len() + + storage_prefix_hashed.len() + + key1_hashed.as_ref().len() + + key2_hashed.as_ref().len(), + ); + + final_key.extend_from_slice(&pallet_prefix_hashed[..]); + final_key.extend_from_slice(&storage_prefix_hashed[..]); + final_key.extend_from_slice(key1_hashed.as_ref()); + final_key.extend_from_slice(key2_hashed.as_ref()); + + StorageKey(final_key) + } +} + +/// Error generated by the `OwnedBridgeModule` trait. +#[derive(Encode, Decode, TypeInfo, PalletError)] +pub enum OwnedBridgeModuleError { + /// All pallet operations are halted. + Halted, +} + +/// Operating mode for a bridge module. +pub trait OperatingMode: Send + Copy + Debug + FullCodec { + // Returns true if the bridge module is halted. + fn is_halted(&self) -> bool; +} + +/// Basic operating modes for a bridges module (Normal/Halted). +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub enum BasicOperatingMode { + /// Normal mode, when all operations are allowed. + Normal, + /// The pallet is halted. All operations (except operating mode change) are prohibited. + Halted, +} + +impl Default for BasicOperatingMode { + fn default() -> Self { + Self::Normal + } +} + +impl OperatingMode for BasicOperatingMode { + fn is_halted(&self) -> bool { + *self == BasicOperatingMode::Halted + } +} + +/// Bridge module that has owner and operating mode +pub trait OwnedBridgeModule { + /// The target that will be used when publishing logs related to this module. + const LOG_TARGET: &'static str; + + type OwnerStorage: StorageValue>; + type OperatingMode: OperatingMode; + type OperatingModeStorage: StorageValue; + + /// Check if the module is halted. + fn is_halted() -> bool { + Self::OperatingModeStorage::get().is_halted() + } + + /// Ensure that the origin is either root, or `PalletOwner`. + fn ensure_owner_or_root(origin: T::RuntimeOrigin) -> Result<(), BadOrigin> { + match origin.into() { + Ok(RawOrigin::Root) => Ok(()), + Ok(RawOrigin::Signed(ref signer)) + if Self::OwnerStorage::get().as_ref() == Some(signer) => + Ok(()), + _ => Err(BadOrigin), + } + } + + /// Ensure that the module is not halted. + fn ensure_not_halted() -> Result<(), OwnedBridgeModuleError> { + match Self::is_halted() { + true => Err(OwnedBridgeModuleError::Halted), + false => Ok(()), + } + } + + /// Change the owner of the module. + fn set_owner(origin: T::RuntimeOrigin, maybe_owner: Option) -> DispatchResult { + Self::ensure_owner_or_root(origin)?; + match maybe_owner { + Some(owner) => { + Self::OwnerStorage::put(&owner); + log::info!(target: Self::LOG_TARGET, "Setting pallet Owner to: {:?}", owner); + }, + None => { + Self::OwnerStorage::kill(); + log::info!(target: Self::LOG_TARGET, "Removed Owner of pallet."); + }, + } + + Ok(()) + } + + /// Halt or resume all/some module operations. + fn set_operating_mode( + origin: T::RuntimeOrigin, + operating_mode: Self::OperatingMode, + ) -> DispatchResult { + Self::ensure_owner_or_root(origin)?; + Self::OperatingModeStorage::put(operating_mode); + log::info!(target: Self::LOG_TARGET, "Setting operating mode to {:?}.", operating_mode); + Ok(()) + } +} + +/// A trait for querying whether a runtime call is valid. +pub trait FilterCall { + /// Checks if a runtime call is valid. + fn validate(call: &Call) -> TransactionValidity; +} + +/// All extra operations with weights that we need in bridges. +pub trait WeightExtraOps { + /// Checked division of individual components of two weights. + /// + /// Divides components and returns minimal division result. Returns `None` if one + /// of `other` weight components is zero. + fn min_components_checked_div(&self, other: Weight) -> Option; +} + +impl WeightExtraOps for Weight { + fn min_components_checked_div(&self, other: Weight) -> Option { + Some(sp_std::cmp::min( + self.ref_time().checked_div(other.ref_time())?, + self.proof_size().checked_div(other.proof_size())?, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn storage_parameter_key_works() { + assert_eq!( + storage_parameter_key("MillauToRialtoConversionRate"), + StorageKey(hex_literal::hex!("58942375551bb0af1682f72786b59d04").to_vec()), + ); + } + + #[test] + fn storage_value_key_works() { + assert_eq!( + storage_value_key("PalletTransactionPayment", "NextFeeMultiplier"), + StorageKey( + hex_literal::hex!( + "f0e954dfcca51a255ab12c60c789256a3f2edf3bdf381debe331ab7446addfdc" + ) + .to_vec() + ), + ); + } +} diff --git a/primitives/runtime/src/messages.rs b/primitives/runtime/src/messages.rs new file mode 100644 index 00000000000..b2e8aacedfa --- /dev/null +++ b/primitives/runtime/src/messages.rs @@ -0,0 +1,37 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives that may be used by different message delivery and dispatch mechanisms. + +use codec::{Decode, Encode}; +use frame_support::{weights::Weight, RuntimeDebug}; +use scale_info::TypeInfo; + +/// Message dispatch result. +#[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)] +pub struct MessageDispatchResult { + /// Unspent dispatch weight. This weight that will be deducted from total delivery transaction + /// weight, thus reducing the transaction cost. This shall not be zero in (at least) two cases: + /// + /// 1) if message has been dispatched successfully, but post-dispatch weight is less than + /// the weight, declared by the message sender; + /// 2) if message has not been dispatched at all. + pub unspent_weight: Weight, + /// Whether the message dispatch fee has been paid during dispatch. This will be true if your + /// configuration supports pay-dispatch-fee-at-target-chain option and message sender has + /// enabled this option. + pub dispatch_fee_paid_during_dispatch: bool, +} diff --git a/primitives/runtime/src/storage_proof.rs b/primitives/runtime/src/storage_proof.rs new file mode 100644 index 00000000000..e1465d2fa16 --- /dev/null +++ b/primitives/runtime/src/storage_proof.rs @@ -0,0 +1,174 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Logic for checking Substrate storage proofs. + +use codec::Decode; +use hash_db::{HashDB, Hasher, EMPTY_PREFIX}; +use sp_runtime::RuntimeDebug; +use sp_std::{boxed::Box, vec::Vec}; +use sp_trie::{ + read_trie_value, LayoutV1, MemoryDB, Recorder, StorageProof, Trie, TrieConfiguration, + TrieDBBuilder, TrieError, TrieHash, +}; + +/// Storage proof size requirements. +/// +/// This is currently used by benchmarks when generating storage proofs. +#[derive(Clone, Copy, Debug)] +pub enum ProofSize { + /// The proof is expected to be minimal. If value size may be changed, then it is expected to + /// have given size. + Minimal(u32), + /// The proof is expected to have at least given size and grow by increasing number of trie + /// nodes included in the proof. + HasExtraNodes(u32), + /// The proof is expected to have at least given size and grow by increasing value that is + /// stored in the trie. + HasLargeLeaf(u32), +} + +/// This struct is used to read storage values from a subset of a Merklized database. The "proof" +/// is a subset of the nodes in the Merkle structure of the database, so that it provides +/// authentication against a known Merkle root as well as the values in the database themselves. +pub struct StorageProofChecker +where + H: Hasher, +{ + root: H::Out, + db: MemoryDB, +} + +impl StorageProofChecker +where + H: Hasher, +{ + /// Constructs a new storage proof checker. + /// + /// This returns an error if the given proof is invalid with respect to the given root. + pub fn new(root: H::Out, proof: StorageProof) -> Result { + let db = proof.into_memory_db(); + if !db.contains(&root, EMPTY_PREFIX) { + return Err(Error::StorageRootMismatch) + } + + let checker = StorageProofChecker { root, db }; + Ok(checker) + } + + /// Reads a value from the available subset of storage. If the value cannot be read due to an + /// incomplete or otherwise invalid proof, this function returns an error. + pub fn read_value(&self, key: &[u8]) -> Result>, Error> { + // LayoutV1 or LayoutV0 is identical for proof that only read values. + read_trie_value::, _>(&self.db, &self.root, key, None, None) + .map_err(|_| Error::StorageValueUnavailable) + } + + /// Reads and decodes a value from the available subset of storage. If the value cannot be read + /// due to an incomplete or otherwise invalid proof, this function returns an error. If value is + /// read, but decoding fails, this function returns an error. + pub fn read_and_decode_value(&self, key: &[u8]) -> Result, Error> { + self.read_value(key).and_then(|v| { + v.map(|v| T::decode(&mut &v[..]).map_err(Error::StorageValueDecodeFailed)) + .transpose() + }) + } +} + +#[derive(Eq, RuntimeDebug, PartialEq)] +pub enum Error { + StorageRootMismatch, + StorageValueUnavailable, + StorageValueDecodeFailed(codec::Error), +} + +/// Return valid storage proof and state root. +/// +/// NOTE: This should only be used for **testing**. +#[cfg(feature = "std")] +pub fn craft_valid_storage_proof() -> (sp_core::H256, StorageProof) { + use codec::Encode; + use sp_state_machine::{backend::Backend, prove_read, InMemoryBackend}; + + let state_version = sp_runtime::StateVersion::default(); + + // construct storage proof + let backend = >::from(( + vec![ + (None, vec![(b"key1".to_vec(), Some(b"value1".to_vec()))]), + (None, vec![(b"key2".to_vec(), Some(b"value2".to_vec()))]), + (None, vec![(b"key3".to_vec(), Some(b"value3".to_vec()))]), + (None, vec![(b"key4".to_vec(), Some((42u64, 42u32, 42u16, 42u8).encode()))]), + // Value is too big to fit in a branch node + (None, vec![(b"key11".to_vec(), Some(vec![0u8; 32]))]), + ], + state_version, + )); + let root = backend.storage_root(std::iter::empty(), state_version).0; + let proof = + prove_read(backend, &[&b"key1"[..], &b"key2"[..], &b"key4"[..], &b"key22"[..]]).unwrap(); + + (root, proof) +} + +/// Record all keys for a given root. +pub fn record_all_keys( + db: &DB, + root: &TrieHash, + recorder: &mut Recorder, +) -> Result<(), Box>> +where + DB: hash_db::HashDBRef, +{ + let trie = TrieDBBuilder::::new(db, root).with_recorder(recorder).build(); + for x in trie.iter()? { + let (key, _) = x?; + trie.get(&key)?; + } + + Ok(()) +} + +#[cfg(test)] +pub mod tests { + use super::*; + use codec::Encode; + + #[test] + fn storage_proof_check() { + let (root, proof) = craft_valid_storage_proof(); + + // check proof in runtime + let checker = + >::new(root, proof.clone()).unwrap(); + assert_eq!(checker.read_value(b"key1"), Ok(Some(b"value1".to_vec()))); + assert_eq!(checker.read_value(b"key2"), Ok(Some(b"value2".to_vec()))); + assert_eq!(checker.read_value(b"key4"), Ok(Some((42u64, 42u32, 42u16, 42u8).encode()))); + assert_eq!(checker.read_value(b"key11111"), Err(Error::StorageValueUnavailable)); + assert_eq!(checker.read_value(b"key22"), Ok(None)); + assert_eq!(checker.read_and_decode_value(b"key4"), Ok(Some((42u64, 42u32, 42u16, 42u8))),); + assert!(matches!( + checker.read_and_decode_value::<[u8; 64]>(b"key4"), + Err(Error::StorageValueDecodeFailed(_)), + )); + + // checking proof against invalid commitment fails + assert_eq!( + >::new(sp_core::H256::random(), proof).err(), + Some(Error::StorageRootMismatch) + ); + } +} diff --git a/primitives/runtime/src/storage_types.rs b/primitives/runtime/src/storage_types.rs new file mode 100644 index 00000000000..b37f779d00b --- /dev/null +++ b/primitives/runtime/src/storage_types.rs @@ -0,0 +1,90 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Wrapper for a runtime storage value that checks if value exceeds given maximum +//! during conversion. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{traits::Get, RuntimeDebug}; +use scale_info::{Type, TypeInfo}; +use sp_std::{marker::PhantomData, ops::Deref}; + +/// Error that is returned when the value size exceeds maximal configured size. +#[derive(RuntimeDebug)] +pub struct MaximalSizeExceededError { + /// Size of the value. + pub value_size: usize, + /// Maximal configured size. + pub maximal_size: usize, +} + +/// A bounded runtime storage value. +#[derive(Clone, Decode, Encode, Eq, PartialEq)] +pub struct BoundedStorageValue { + value: V, + _phantom: PhantomData, +} + +impl sp_std::fmt::Debug for BoundedStorageValue { + fn fmt(&self, fmt: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + self.value.fmt(fmt) + } +} + +impl, V: Encode> BoundedStorageValue { + /// Construct `BoundedStorageValue` from the underlying `value` with all required checks. + /// + /// Returns error if value size exceeds given bounds. + pub fn try_from_inner(value: V) -> Result { + // this conversion is heavy (since we do encoding here), so we may want to optimize it later + // (e.g. by introducing custom Encode implementation, and turning `BoundedStorageValue` into + // `enum BoundedStorageValue { Decoded(V), Encoded(Vec) }`) + let value_size = value.encoded_size(); + let maximal_size = B::get() as usize; + if value_size > maximal_size { + Err(MaximalSizeExceededError { value_size, maximal_size }) + } else { + Ok(BoundedStorageValue { value, _phantom: Default::default() }) + } + } + + /// Convert into the inner type + pub fn into_inner(self) -> V { + self.value + } +} + +impl Deref for BoundedStorageValue { + type Target = V; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl TypeInfo for BoundedStorageValue { + type Identity = Self; + + fn type_info() -> Type { + V::type_info() + } +} + +impl, V: Encode> MaxEncodedLen for BoundedStorageValue { + fn max_encoded_len() -> usize { + B::get() as usize + } +} diff --git a/primitives/test-utils/Cargo.toml b/primitives/test-utils/Cargo.toml new file mode 100644 index 00000000000..2bc77e632e5 --- /dev/null +++ b/primitives/test-utils/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bp-test-utils" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +bp-header-chain = { path = "../header-chain", default-features = false } +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"] } +finality-grandpa = { version = "0.16.0", default-features = false } +sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "bp-header-chain/std", + "codec/std", + "ed25519-dalek/std", + "finality-grandpa/std", + "sp-application-crypto/std", + "sp-finality-grandpa/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/primitives/test-utils/src/keyring.rs b/primitives/test-utils/src/keyring.rs new file mode 100644 index 00000000000..f827c729434 --- /dev/null +++ b/primitives/test-utils/src/keyring.rs @@ -0,0 +1,94 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Utilities for working with test accounts. + +use codec::Encode; +use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature}; +use finality_grandpa::voter_set::VoterSet; +use sp_finality_grandpa::{AuthorityId, AuthorityList, AuthorityWeight}; +use sp_runtime::RuntimeDebug; +use sp_std::prelude::*; + +/// Set of test accounts with friendly names. +pub const ALICE: Account = Account(0); +pub const BOB: Account = Account(1); +pub const CHARLIE: Account = Account(2); +pub const DAVE: Account = Account(3); +pub const EVE: Account = Account(4); +pub const FERDIE: Account = Account(5); + +/// A test account which can be used to sign messages. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct Account(pub u16); + +impl Account { + pub fn public(&self) -> PublicKey { + (&self.secret()).into() + } + + pub fn secret(&self) -> SecretKey { + let data = self.0.encode(); + let mut bytes = [0_u8; 32]; + bytes[0..data.len()].copy_from_slice(&data); + SecretKey::from_bytes(&bytes) + .expect("A static array of the correct length is a known good.") + } + + pub fn pair(&self) -> Keypair { + let mut pair: [u8; 64] = [0; 64]; + + let secret = self.secret(); + pair[..32].copy_from_slice(&secret.to_bytes()); + + let public = self.public(); + pair[32..].copy_from_slice(&public.to_bytes()); + + Keypair::from_bytes(&pair) + .expect("We expect the SecretKey to be good, so this must also be good.") + } + + pub fn sign(&self, msg: &[u8]) -> Signature { + use ed25519_dalek::Signer; + self.pair().sign(msg) + } +} + +impl From for AuthorityId { + fn from(p: Account) -> Self { + sp_application_crypto::UncheckedFrom::unchecked_from(p.public().to_bytes()) + } +} + +/// Get a valid set of voters for a Grandpa round. +pub fn voter_set() -> VoterSet { + VoterSet::new(authority_list()).unwrap() +} + +/// Convenience function to get a list of Grandpa authorities. +pub fn authority_list() -> AuthorityList { + test_keyring().iter().map(|(id, w)| (AuthorityId::from(*id), *w)).collect() +} + +/// Get the corresponding identities from the keyring for the "standard" authority set. +pub fn test_keyring() -> Vec<(Account, AuthorityWeight)> { + vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1)] +} + +/// Get a list of "unique" accounts. +pub fn accounts(len: u16) -> Vec { + (0..len).into_iter().map(Account).collect() +} diff --git a/primitives/test-utils/src/lib.rs b/primitives/test-utils/src/lib.rs new file mode 100644 index 00000000000..186d192014b --- /dev/null +++ b/primitives/test-utils/src/lib.rs @@ -0,0 +1,345 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Utilities for testing runtime code. + +#![cfg_attr(not(feature = "std"), no_std)] + +use bp_header_chain::justification::GrandpaJustification; +use codec::Encode; +use sp_finality_grandpa::{AuthorityId, AuthoritySignature, AuthorityWeight, SetId}; +use sp_runtime::traits::{Header as HeaderT, One, Zero}; +use sp_std::prelude::*; + +// Re-export all our test account utilities +pub use keyring::*; + +mod keyring; + +pub const TEST_GRANDPA_ROUND: u64 = 1; +pub const TEST_GRANDPA_SET_ID: SetId = 1; + +/// Configuration parameters when generating test GRANDPA justifications. +#[derive(Clone)] +pub struct JustificationGeneratorParams { + /// The header which we want to finalize. + pub header: H, + /// The GRANDPA round number for the current authority set. + pub round: u64, + /// The current authority set ID. + pub set_id: SetId, + /// The current GRANDPA authority set. + /// + /// The size of the set will determine the number of pre-commits in our justification. + pub authorities: Vec<(Account, AuthorityWeight)>, + /// The total number of precommit ancestors in the `votes_ancestries` field our justification. + /// + /// These may be distributed among many forks. + pub ancestors: u32, + /// The number of forks. + /// + /// Useful for creating a "worst-case" scenario in which each authority is on its own fork. + pub forks: u32, +} + +impl Default for JustificationGeneratorParams { + fn default() -> Self { + Self { + header: test_header(One::one()), + round: TEST_GRANDPA_ROUND, + set_id: TEST_GRANDPA_SET_ID, + authorities: test_keyring(), + ancestors: 2, + forks: 1, + } + } +} + +/// Make a valid GRANDPA justification with sensible defaults +pub fn make_default_justification(header: &H) -> GrandpaJustification { + let params = JustificationGeneratorParams:: { header: header.clone(), ..Default::default() }; + + make_justification_for_header(params) +} + +/// Generate justifications in a way where we are able to tune the number of pre-commits +/// and vote ancestries which are included in the justification. +/// +/// This is useful for benchmarkings where we want to generate valid justifications with +/// a specific number of pre-commits (tuned with the number of "authorities") and/or a specific +/// number of vote ancestries (tuned with the "votes" parameter). +/// +/// Note: This needs at least three authorities or else the verifier will complain about +/// being given an invalid commit. +pub fn make_justification_for_header( + params: JustificationGeneratorParams, +) -> GrandpaJustification { + let JustificationGeneratorParams { header, round, set_id, authorities, mut ancestors, forks } = + params; + let (target_hash, target_number) = (header.hash(), *header.number()); + let mut votes_ancestries = vec![]; + let mut precommits = vec![]; + + assert!(forks != 0, "Need at least one fork to have a chain.."); + assert!( + forks as usize <= authorities.len(), + "If we have more forks than authorities we can't create valid pre-commits for all the forks." + ); + + // Roughly, how many vote ancestries do we want per fork + let target_depth = (ancestors + forks - 1) / forks; + + let mut unsigned_precommits = vec![]; + for i in 0..forks { + let depth = if ancestors >= target_depth { + ancestors -= target_depth; + target_depth + } else { + ancestors + }; + + // Note: Adding 1 to account for the target header + let chain = generate_chain(i, depth + 1, &header); + + // We don't include our finality target header in the vote ancestries + for child in &chain[1..] { + votes_ancestries.push(child.clone()); + } + + // The header we need to use when pre-commiting is the one at the highest height + // on our chain. + let precommit_candidate = chain.last().map(|h| (h.hash(), *h.number())).unwrap(); + unsigned_precommits.push(precommit_candidate); + } + + for (i, (id, _weight)) in authorities.iter().enumerate() { + // Assign authorities to sign pre-commits in a round-robin fashion + let target = unsigned_precommits[i % forks as usize]; + let precommit = signed_precommit::(id, target, round, set_id); + + precommits.push(precommit); + } + + GrandpaJustification { + round, + commit: finality_grandpa::Commit { target_hash, target_number, precommits }, + votes_ancestries, + } +} + +fn generate_chain(fork_id: u32, depth: u32, ancestor: &H) -> Vec { + let mut headers = vec![ancestor.clone()]; + + for i in 1..depth { + let parent = &headers[(i - 1) as usize]; + let (hash, num) = (parent.hash(), *parent.number()); + + let mut header = test_header::(num + One::one()); + header.set_parent_hash(hash); + + // Modifying the digest so headers at the same height but in different forks have different + // hashes + header.digest_mut().logs.push(sp_runtime::DigestItem::Other(fork_id.encode())); + + headers.push(header); + } + + headers +} + +/// Create signed precommit with given target. +pub fn signed_precommit( + signer: &Account, + target: (H::Hash, H::Number), + round: u64, + set_id: SetId, +) -> finality_grandpa::SignedPrecommit { + let precommit = finality_grandpa::Precommit { target_hash: target.0, target_number: target.1 }; + + let encoded = sp_finality_grandpa::localized_payload( + round, + set_id, + &finality_grandpa::Message::Precommit(precommit.clone()), + ); + + let signature = signer.sign(&encoded); + let raw_signature: Vec = signature.to_bytes().into(); + + // Need to wrap our signature and id types that they match what our `SignedPrecommit` is + // expecting + let signature = AuthoritySignature::try_from(raw_signature).expect( + "We know our Keypair is good, + so our signature must also be good.", + ); + let id = (*signer).into(); + + finality_grandpa::SignedPrecommit { precommit, signature, id } +} + +/// Get a header for testing. +/// +/// The correct parent hash will be used if given a non-zero header. +pub fn test_header(number: H::Number) -> H { + let default = |num| { + H::new(num, Default::default(), Default::default(), Default::default(), Default::default()) + }; + + let mut header = default(number); + if number != Zero::zero() { + let parent_hash = default(number - One::one()).hash(); + header.set_parent_hash(parent_hash); + } + + header +} + +/// Convenience function for generating a Header ID at a given block number. +pub fn header_id(index: u8) -> (H::Hash, H::Number) { + (test_header::(index.into()).hash(), index.into()) +} + +#[macro_export] +/// Adds methods for testing the `set_owner()` and `set_operating_mode()` for a pallet. +/// Some values are hardcoded like: +/// - `run_test()` +/// - `Pallet::` +/// - `PalletOwner::` +/// - `PalletOperatingMode::` +/// While this is not ideal, all the pallets use the same names, so it works for the moment. +/// We can revisit this in the future if anything changes. +macro_rules! generate_owned_bridge_module_tests { + ($normal_operating_mode: expr, $halted_operating_mode: expr) => { + #[test] + fn test_set_owner() { + run_test(|| { + PalletOwner::::put(1); + + // The root should be able to change the owner. + assert_ok!(Pallet::::set_owner(RuntimeOrigin::root(), Some(2))); + assert_eq!(PalletOwner::::get(), Some(2)); + + // The owner should be able to change the owner. + assert_ok!(Pallet::::set_owner(RuntimeOrigin::signed(2), Some(3))); + assert_eq!(PalletOwner::::get(), Some(3)); + + // Other users shouldn't be able to change the owner. + assert_noop!( + Pallet::::set_owner(RuntimeOrigin::signed(1), Some(4)), + DispatchError::BadOrigin + ); + assert_eq!(PalletOwner::::get(), Some(3)); + }); + } + + #[test] + fn test_set_operating_mode() { + run_test(|| { + PalletOwner::::put(1); + PalletOperatingMode::::put($normal_operating_mode); + + // The root should be able to halt the pallet. + assert_ok!(Pallet::::set_operating_mode( + RuntimeOrigin::root(), + $halted_operating_mode + )); + assert_eq!(PalletOperatingMode::::get(), $halted_operating_mode); + // The root should be able to resume the pallet. + assert_ok!(Pallet::::set_operating_mode( + RuntimeOrigin::root(), + $normal_operating_mode + )); + assert_eq!(PalletOperatingMode::::get(), $normal_operating_mode); + + // The owner should be able to halt the pallet. + assert_ok!(Pallet::::set_operating_mode( + RuntimeOrigin::signed(1), + $halted_operating_mode + )); + assert_eq!(PalletOperatingMode::::get(), $halted_operating_mode); + // The owner should be able to resume the pallet. + assert_ok!(Pallet::::set_operating_mode( + RuntimeOrigin::signed(1), + $normal_operating_mode + )); + assert_eq!(PalletOperatingMode::::get(), $normal_operating_mode); + + // Other users shouldn't be able to halt the pallet. + assert_noop!( + Pallet::::set_operating_mode( + RuntimeOrigin::signed(2), + $halted_operating_mode + ), + DispatchError::BadOrigin + ); + assert_eq!(PalletOperatingMode::::get(), $normal_operating_mode); + // Other users shouldn't be able to resume the pallet. + PalletOperatingMode::::put($halted_operating_mode); + assert_noop!( + Pallet::::set_operating_mode( + RuntimeOrigin::signed(2), + $normal_operating_mode + ), + DispatchError::BadOrigin + ); + assert_eq!(PalletOperatingMode::::get(), $halted_operating_mode); + }); + } + }; +} + +#[cfg(test)] +mod tests { + use codec::Encode; + use sp_application_crypto::sp_core::{hexdisplay, hexdisplay::HexDisplay}; + use xcm::VersionedXcm; + + fn print_xcm(xcm: &VersionedXcm) { + println!("-----------------"); + println!("xcm (plain): {:?}", xcm); + println!("xcm (bytes): {:?}", xcm.encode()); + println!("xcm (hex): {:?}", hexdisplay::HexDisplay::from(&xcm.encode())); + } + + fn as_hex(xcm: &VersionedXcm) -> String { + HexDisplay::from(&xcm.encode()).to_string() + } + + pub type RuntimeCall = (); + + #[test] + fn generate_versioned_xcm_message_hex_bytes() { + let xcm: xcm::v2::Xcm = xcm::v2::Xcm(vec![xcm::v2::Instruction::Trap(43)]); + let xcm: VersionedXcm = From::from(xcm); + print_xcm(&xcm); + assert_eq!("020419ac", format!("{}", as_hex(&xcm))); + + let xcm: xcm::v3::Xcm = vec![xcm::v3::Instruction::Trap(43)].into(); + let xcm: VersionedXcm = From::from(xcm); + print_xcm(&xcm); + assert_eq!("030419ac", format!("{}", as_hex(&xcm))); + + let xcm: xcm::v3::Xcm = vec![ + xcm::v3::Instruction::ClearError, + xcm::v3::Instruction::ClearTopic, + xcm::v3::Instruction::ClearTransactStatus, + xcm::v3::Instruction::Trap(43), + ] + .into(); + let xcm: VersionedXcm = From::from(xcm); + print_xcm(&xcm); + assert_eq!("0310172c2319ac", format!("{}", as_hex(&xcm))); + } +} diff --git a/relays/bin-substrate/Cargo.toml b/relays/bin-substrate/Cargo.toml new file mode 100644 index 00000000000..9ae6a98868e --- /dev/null +++ b/relays/bin-substrate/Cargo.toml @@ -0,0 +1,75 @@ +[package] +name = "substrate-relay" +version = "1.0.1" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +anyhow = "1.0" +async-std = "1.9.0" +async-trait = "0.1" +codec = { package = "parity-scale-codec", version = "3.1.5" } +futures = "0.3.12" +hex = "0.4" +log = "0.4.17" +num-format = "0.4" +num-traits = "0.2" +structopt = "0.3" +strum = { version = "0.21.0", features = ["derive"] } + +# Bridge dependencies + +bp-bridge-hub-rococo = { path = "../../primitives/chain-bridge-hub-rococo" } +bp-bridge-hub-wococo = { path = "../../primitives/chain-bridge-hub-wococo" } +bp-header-chain = { path = "../../primitives/header-chain" } +bp-messages = { path = "../../primitives/messages" } +bp-millau = { path = "../../primitives/chain-millau" } +bp-polkadot-core = { path = "../../primitives/polkadot-core" } +bp-rialto = { path = "../../primitives/chain-rialto" } +bp-rialto-parachain = { path = "../../primitives/chain-rialto-parachain" } +bp-rococo = { path = "../../primitives/chain-rococo" } +bp-runtime = { path = "../../primitives/runtime" } +bp-westend = { path = "../../primitives/chain-westend" } +bp-wococo = { path = "../../primitives/chain-wococo" } +bridge-runtime-common = { path = "../../bin/runtime-common" } +messages-relay = { path = "../messages" } +millau-runtime = { path = "../../bin/millau/runtime" } +pallet-bridge-messages = { path = "../../modules/messages" } +pallet-bridge-parachains = { path = "../../modules/parachains" } +parachains-relay = { path = "../parachains" } +relay-millau-client = { path = "../client-millau" } +relay-rialto-client = { path = "../client-rialto" } +relay-rialto-parachain-client = { path = "../client-rialto-parachain" } +relay-bridge-hub-rococo-client = { path = "../client-bridge-hub-rococo" } +relay-bridge-hub-wococo-client = { path = "../client-bridge-hub-wococo" } +relay-rococo-client = { path = "../client-rococo" } +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } +relay-westend-client = { path = "../client-westend" } +relay-wococo-client = { path = "../client-wococo" } +rialto-parachain-runtime = { path = "../../bin/rialto-parachain/runtime" } +rialto-runtime = { path = "../../bin/rialto/runtime" } +substrate-relay-helper = { path = "../lib-substrate-relay" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" } + +# Polkadot Dependencies +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "master" } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" } +polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", branch = "master" } +polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "master" } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false } + + +[dev-dependencies] +bp-test-utils = { path = "../../primitives/test-utils" } +hex-literal = "0.3" +sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" } +tempfile = "3.2" +finality-grandpa = { version = "0.16.0" } diff --git a/relays/bin-substrate/src/chains/bridge_hub_rococo_messages_to_bridge_hub_wococo.rs b/relays/bin-substrate/src/chains/bridge_hub_rococo_messages_to_bridge_hub_wococo.rs new file mode 100644 index 00000000000..78ef00f6d62 --- /dev/null +++ b/relays/bin-substrate/src/chains/bridge_hub_rococo_messages_to_bridge_hub_wococo.rs @@ -0,0 +1,64 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! BridgeHubRococo-to-BridgeHubWococo messages sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, MessagesCliBridge}; +use bp_messages::Weight; +use relay_bridge_hub_rococo_client::BridgeHubRococo; +use relay_bridge_hub_wococo_client::BridgeHubWococo; +use substrate_relay_helper::messages_lane::SubstrateMessageLane; + +pub struct BridgeHubRococoToBridgeHubWococoMessagesCliBridge {} + +impl CliBridgeBase for BridgeHubRococoToBridgeHubWococoMessagesCliBridge { + type Source = BridgeHubRococo; + type Target = BridgeHubWococo; +} + +impl MessagesCliBridge for BridgeHubRococoToBridgeHubWococoMessagesCliBridge { + const ESTIMATE_MESSAGE_FEE_METHOD: &'static str = + "TODO: not needed now, used for send_message and estimate_fee CLI"; + type MessagesLane = BridgeHubRococoMessagesToBridgeHubWococoMessageLane; +} + +substrate_relay_helper::generate_mocked_receive_message_proof_call_builder!( + BridgeHubRococoMessagesToBridgeHubWococoMessageLane, + BridgeHubRococoMessagesToBridgeHubWococoMessageLaneReceiveMessagesProofCallBuilder, + relay_bridge_hub_wococo_client::runtime::Call::BridgeRococoMessages, + relay_bridge_hub_wococo_client::runtime::BridgeRococoMessagesCall::receive_messages_proof +); + +substrate_relay_helper::generate_mocked_receive_message_delivery_proof_call_builder!( + BridgeHubRococoMessagesToBridgeHubWococoMessageLane, + BridgeHubRococoMessagesToBridgeHubWococoMessageLaneReceiveMessagesDeliveryProofCallBuilder, + relay_bridge_hub_rococo_client::runtime::Call::BridgeWococoMessages, + relay_bridge_hub_rococo_client::runtime::BridgeWococoMessagesCall::receive_messages_delivery_proof +); + +/// Description of BridgeHubRococo -> BridgeHubWococo messages bridge. +#[derive(Clone, Debug)] +pub struct BridgeHubRococoMessagesToBridgeHubWococoMessageLane; + +impl SubstrateMessageLane for BridgeHubRococoMessagesToBridgeHubWococoMessageLane { + type SourceChain = BridgeHubRococo; + type TargetChain = BridgeHubWococo; + + type ReceiveMessagesProofCallBuilder = + BridgeHubRococoMessagesToBridgeHubWococoMessageLaneReceiveMessagesProofCallBuilder; + type ReceiveMessagesDeliveryProofCallBuilder = + BridgeHubRococoMessagesToBridgeHubWococoMessageLaneReceiveMessagesDeliveryProofCallBuilder; +} diff --git a/relays/bin-substrate/src/chains/bridge_hub_wococo_messages_to_bridge_hub_rococo.rs b/relays/bin-substrate/src/chains/bridge_hub_wococo_messages_to_bridge_hub_rococo.rs new file mode 100644 index 00000000000..51e1020e749 --- /dev/null +++ b/relays/bin-substrate/src/chains/bridge_hub_wococo_messages_to_bridge_hub_rococo.rs @@ -0,0 +1,64 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! BridgeHubWococo-to-BridgeHubRococo messages sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, MessagesCliBridge}; +use bp_messages::Weight; +use relay_bridge_hub_rococo_client::BridgeHubRococo; +use relay_bridge_hub_wococo_client::BridgeHubWococo; +use substrate_relay_helper::messages_lane::SubstrateMessageLane; + +pub struct BridgeHubWococoToBridgeHubRococoMessagesCliBridge {} + +impl CliBridgeBase for BridgeHubWococoToBridgeHubRococoMessagesCliBridge { + type Source = BridgeHubWococo; + type Target = BridgeHubRococo; +} + +impl MessagesCliBridge for BridgeHubWococoToBridgeHubRococoMessagesCliBridge { + const ESTIMATE_MESSAGE_FEE_METHOD: &'static str = + "TODO: not needed now, used for send_message and estimate_fee CLI"; + type MessagesLane = BridgeHubWococoMessagesToBridgeHubRococoMessageLane; +} + +substrate_relay_helper::generate_mocked_receive_message_proof_call_builder!( + BridgeHubWococoMessagesToBridgeHubRococoMessageLane, + BridgeHubWococoMessagesToBridgeHubRococoMessageLaneReceiveMessagesProofCallBuilder, + relay_bridge_hub_rococo_client::runtime::Call::BridgeWococoMessages, + relay_bridge_hub_rococo_client::runtime::BridgeWococoMessagesCall::receive_messages_proof +); + +substrate_relay_helper::generate_mocked_receive_message_delivery_proof_call_builder!( + BridgeHubWococoMessagesToBridgeHubRococoMessageLane, + BridgeHubWococoMessagesToBridgeHubRococoMessageLaneReceiveMessagesDeliveryProofCallBuilder, + relay_bridge_hub_wococo_client::runtime::Call::BridgeRococoMessages, + relay_bridge_hub_wococo_client::runtime::BridgeRococoMessagesCall::receive_messages_delivery_proof +); + +/// Description of BridgeHubWococo -> BridgeHubRococo messages bridge. +#[derive(Clone, Debug)] +pub struct BridgeHubWococoMessagesToBridgeHubRococoMessageLane; + +impl SubstrateMessageLane for BridgeHubWococoMessagesToBridgeHubRococoMessageLane { + type SourceChain = BridgeHubWococo; + type TargetChain = BridgeHubRococo; + + type ReceiveMessagesProofCallBuilder = + BridgeHubWococoMessagesToBridgeHubRococoMessageLaneReceiveMessagesProofCallBuilder; + type ReceiveMessagesDeliveryProofCallBuilder = + BridgeHubWococoMessagesToBridgeHubRococoMessageLaneReceiveMessagesDeliveryProofCallBuilder; +} diff --git a/relays/bin-substrate/src/chains/millau.rs b/relays/bin-substrate/src/chains/millau.rs new file mode 100644 index 00000000000..705755506f7 --- /dev/null +++ b/relays/bin-substrate/src/chains/millau.rs @@ -0,0 +1,63 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Millau chain specification for CLI. + +use crate::cli::{bridge, encode_message::CliEncodeMessage, CliChain}; +use bp_rialto_parachain::RIALTO_PARACHAIN_ID; +use bp_runtime::EncodedOrDecodedCall; +use relay_millau_client::Millau; +use sp_version::RuntimeVersion; +use xcm::latest::prelude::*; + +impl CliEncodeMessage for Millau { + fn encode_send_xcm( + message: xcm::VersionedXcm<()>, + bridge_instance_index: u8, + ) -> anyhow::Result> { + let dest = match bridge_instance_index { + bridge::MILLAU_TO_RIALTO_INDEX => + (Parent, X1(GlobalConsensus(millau_runtime::xcm_config::RialtoNetwork::get()))), + bridge::MILLAU_TO_RIALTO_PARACHAIN_INDEX => ( + Parent, + X2( + GlobalConsensus(millau_runtime::xcm_config::RialtoNetwork::get()), + Parachain(RIALTO_PARACHAIN_ID), + ), + ), + _ => anyhow::bail!( + "Unsupported target bridge pallet with instance index: {}", + bridge_instance_index + ), + }; + + Ok(millau_runtime::RuntimeCall::XcmPallet(millau_runtime::XcmCall::send { + dest: Box::new(dest.into()), + message: Box::new(message), + }) + .into()) + } +} + +impl CliChain for Millau { + const RUNTIME_VERSION: Option = Some(millau_runtime::VERSION); + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + millau_runtime::SS58Prefix::get() as u16 + } +} diff --git a/relays/bin-substrate/src/chains/millau_headers_to_rialto.rs b/relays/bin-substrate/src/chains/millau_headers_to_rialto.rs new file mode 100644 index 00000000000..e0dbabd86b4 --- /dev/null +++ b/relays/bin-substrate/src/chains/millau_headers_to_rialto.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Millau-to-Rialto headers sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, MessagesCliBridge, RelayToRelayHeadersCliBridge}; +use substrate_relay_helper::finality::{ + engine::Grandpa as GrandpaFinalityEngine, DirectSubmitGrandpaFinalityProofCallBuilder, + SubstrateFinalitySyncPipeline, +}; + +/// Description of Millau -> Rialto finalized headers bridge. +#[derive(Clone, Debug)] +pub struct MillauFinalityToRialto; + +impl SubstrateFinalitySyncPipeline for MillauFinalityToRialto { + type SourceChain = relay_millau_client::Millau; + type TargetChain = relay_rialto_client::Rialto; + + type FinalityEngine = GrandpaFinalityEngine; + type SubmitFinalityProofCallBuilder = DirectSubmitGrandpaFinalityProofCallBuilder< + Self, + rialto_runtime::Runtime, + rialto_runtime::MillauGrandpaInstance, + >; +} + +//// `Millau` to `Rialto` bridge definition. +pub struct MillauToRialtoCliBridge {} + +impl CliBridgeBase for MillauToRialtoCliBridge { + type Source = relay_millau_client::Millau; + type Target = relay_rialto_client::Rialto; +} + +impl RelayToRelayHeadersCliBridge for MillauToRialtoCliBridge { + type Finality = MillauFinalityToRialto; +} + +impl MessagesCliBridge for MillauToRialtoCliBridge { + const ESTIMATE_MESSAGE_FEE_METHOD: &'static str = + bp_rialto::TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD; + type MessagesLane = crate::chains::millau_messages_to_rialto::MillauMessagesToRialto; +} diff --git a/relays/bin-substrate/src/chains/millau_headers_to_rialto_parachain.rs b/relays/bin-substrate/src/chains/millau_headers_to_rialto_parachain.rs new file mode 100644 index 00000000000..cdb3999db6e --- /dev/null +++ b/relays/bin-substrate/src/chains/millau_headers_to_rialto_parachain.rs @@ -0,0 +1,76 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Millau-to-RialtoParachain headers sync entrypoint. + +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Millau-to-RialtoParachain headers sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, MessagesCliBridge, RelayToRelayHeadersCliBridge}; +use substrate_relay_helper::finality::{ + engine::Grandpa as GrandpaFinalityEngine, DirectSubmitGrandpaFinalityProofCallBuilder, + SubstrateFinalitySyncPipeline, +}; + +/// Description of Millau -> Rialto finalized headers bridge. +#[derive(Clone, Debug)] +pub struct MillauFinalityToRialtoParachain; + +impl SubstrateFinalitySyncPipeline for MillauFinalityToRialtoParachain { + type SourceChain = relay_millau_client::Millau; + type TargetChain = relay_rialto_parachain_client::RialtoParachain; + + type FinalityEngine = GrandpaFinalityEngine; + type SubmitFinalityProofCallBuilder = DirectSubmitGrandpaFinalityProofCallBuilder< + Self, + rialto_parachain_runtime::Runtime, + rialto_parachain_runtime::MillauGrandpaInstance, + >; +} + +//// `Millau` to `RialtoParachain` bridge definition. +pub struct MillauToRialtoParachainCliBridge {} + +impl CliBridgeBase for MillauToRialtoParachainCliBridge { + type Source = relay_millau_client::Millau; + type Target = relay_rialto_parachain_client::RialtoParachain; +} + +impl RelayToRelayHeadersCliBridge for MillauToRialtoParachainCliBridge { + type Finality = MillauFinalityToRialtoParachain; +} + +impl MessagesCliBridge for MillauToRialtoParachainCliBridge { + const ESTIMATE_MESSAGE_FEE_METHOD: &'static str = + bp_rialto_parachain::TO_RIALTO_PARACHAIN_ESTIMATE_MESSAGE_FEE_METHOD; + type MessagesLane = + crate::chains::millau_messages_to_rialto_parachain::MillauMessagesToRialtoParachain; +} diff --git a/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs b/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs new file mode 100644 index 00000000000..b9920db53d8 --- /dev/null +++ b/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs @@ -0,0 +1,44 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Millau-to-Rialto messages sync entrypoint. + +use relay_millau_client::Millau; +use relay_rialto_client::Rialto; +use substrate_relay_helper::messages_lane::{ + DirectReceiveMessagesDeliveryProofCallBuilder, DirectReceiveMessagesProofCallBuilder, + SubstrateMessageLane, +}; + +/// Description of Millau -> Rialto messages bridge. +#[derive(Clone, Debug)] +pub struct MillauMessagesToRialto; + +impl SubstrateMessageLane for MillauMessagesToRialto { + type SourceChain = Millau; + type TargetChain = Rialto; + + type ReceiveMessagesProofCallBuilder = DirectReceiveMessagesProofCallBuilder< + Self, + rialto_runtime::Runtime, + rialto_runtime::WithMillauMessagesInstance, + >; + type ReceiveMessagesDeliveryProofCallBuilder = DirectReceiveMessagesDeliveryProofCallBuilder< + Self, + millau_runtime::Runtime, + millau_runtime::WithRialtoMessagesInstance, + >; +} diff --git a/relays/bin-substrate/src/chains/millau_messages_to_rialto_parachain.rs b/relays/bin-substrate/src/chains/millau_messages_to_rialto_parachain.rs new file mode 100644 index 00000000000..70cb887fa35 --- /dev/null +++ b/relays/bin-substrate/src/chains/millau_messages_to_rialto_parachain.rs @@ -0,0 +1,44 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Millau-to-RialtoParachain messages sync entrypoint. + +use relay_millau_client::Millau; +use relay_rialto_parachain_client::RialtoParachain; +use substrate_relay_helper::messages_lane::{ + DirectReceiveMessagesDeliveryProofCallBuilder, DirectReceiveMessagesProofCallBuilder, + SubstrateMessageLane, +}; + +/// Description of Millau -> RialtoParachain messages bridge. +#[derive(Clone, Debug)] +pub struct MillauMessagesToRialtoParachain; + +impl SubstrateMessageLane for MillauMessagesToRialtoParachain { + type SourceChain = Millau; + type TargetChain = RialtoParachain; + + type ReceiveMessagesProofCallBuilder = DirectReceiveMessagesProofCallBuilder< + Self, + rialto_parachain_runtime::Runtime, + rialto_parachain_runtime::WithMillauMessagesInstance, + >; + type ReceiveMessagesDeliveryProofCallBuilder = DirectReceiveMessagesDeliveryProofCallBuilder< + Self, + millau_runtime::Runtime, + millau_runtime::WithRialtoParachainMessagesInstance, + >; +} diff --git a/relays/bin-substrate/src/chains/mod.rs b/relays/bin-substrate/src/chains/mod.rs new file mode 100644 index 00000000000..7caba7561dd --- /dev/null +++ b/relays/bin-substrate/src/chains/mod.rs @@ -0,0 +1,125 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Chain-specific relayer configuration. + +pub mod bridge_hub_rococo_messages_to_bridge_hub_wococo; +pub mod bridge_hub_wococo_messages_to_bridge_hub_rococo; +pub mod millau_headers_to_rialto; +pub mod millau_headers_to_rialto_parachain; +pub mod millau_messages_to_rialto; +pub mod millau_messages_to_rialto_parachain; +pub mod rialto_headers_to_millau; +pub mod rialto_messages_to_millau; +pub mod rialto_parachain_messages_to_millau; +pub mod rialto_parachains_to_millau; +pub mod rococo_headers_to_bridge_hub_wococo; +pub mod rococo_parachains_to_bridge_hub_wococo; +pub mod westend_headers_to_millau; +pub mod westend_parachains_to_millau; +pub mod wococo_headers_to_bridge_hub_rococo; +pub mod wococo_parachains_to_bridge_hub_rococo; + +mod millau; +mod rialto; +mod rialto_parachain; +mod rococo; +mod westend; +mod wococo; + +#[cfg(test)] +mod tests { + use crate::cli::encode_message; + use bp_messages::source_chain::TargetHeaderChain; + use bp_runtime::Chain as _; + use codec::Encode; + use relay_millau_client::Millau; + use relay_rialto_client::Rialto; + use relay_substrate_client::{ChainWithTransactions, SignParam, UnsignedTransaction}; + + #[test] + fn maximal_rialto_to_millau_message_size_is_computed_correctly() { + use rialto_runtime::millau_messages::Millau; + + let maximal_message_size = encode_message::compute_maximal_message_size( + bp_rialto::Rialto::max_extrinsic_size(), + bp_millau::Millau::max_extrinsic_size(), + ); + + let message = vec![42; maximal_message_size as _]; + assert_eq!(Millau::verify_message(&message), Ok(())); + + let message = vec![42; (maximal_message_size + 1) as _]; + assert!(Millau::verify_message(&message).is_err()); + } + + #[test] + fn maximal_size_remark_to_rialto_is_generated_correctly() { + assert!( + bridge_runtime_common::messages::target::maximal_incoming_message_size( + bp_rialto::Rialto::max_extrinsic_size() + ) > bp_millau::Millau::max_extrinsic_size(), + "We can't actually send maximal messages to Rialto from Millau, because Millau extrinsics can't be that large", + ) + } + #[test] + fn rialto_tx_extra_bytes_constant_is_correct() { + let rialto_call = rialto_runtime::RuntimeCall::System(rialto_runtime::SystemCall::remark { + remark: vec![], + }); + let rialto_tx = Rialto::sign_transaction( + SignParam { + spec_version: 1, + transaction_version: 1, + genesis_hash: Default::default(), + signer: sp_keyring::AccountKeyring::Alice.pair(), + }, + UnsignedTransaction::new(rialto_call.clone().into(), 0), + ) + .unwrap(); + let extra_bytes_in_transaction = rialto_tx.encode().len() - rialto_call.encode().len(); + assert!( + bp_rialto::TX_EXTRA_BYTES as usize >= extra_bytes_in_transaction, + "Hardcoded number of extra bytes in Rialto transaction {} is lower than actual value: {}", + bp_rialto::TX_EXTRA_BYTES, + extra_bytes_in_transaction, + ); + } + + #[test] + fn millau_tx_extra_bytes_constant_is_correct() { + let millau_call = millau_runtime::RuntimeCall::System(millau_runtime::SystemCall::remark { + remark: vec![], + }); + let millau_tx = Millau::sign_transaction( + SignParam { + spec_version: 0, + transaction_version: 0, + genesis_hash: Default::default(), + signer: sp_keyring::AccountKeyring::Alice.pair(), + }, + UnsignedTransaction::new(millau_call.clone().into(), 0), + ) + .unwrap(); + let extra_bytes_in_transaction = millau_tx.encode().len() - millau_call.encode().len(); + assert!( + bp_millau::TX_EXTRA_BYTES as usize >= extra_bytes_in_transaction, + "Hardcoded number of extra bytes in Millau transaction {} is lower than actual value: {}", + bp_millau::TX_EXTRA_BYTES, + extra_bytes_in_transaction, + ); + } +} diff --git a/relays/bin-substrate/src/chains/rialto.rs b/relays/bin-substrate/src/chains/rialto.rs new file mode 100644 index 00000000000..83bccf626b9 --- /dev/null +++ b/relays/bin-substrate/src/chains/rialto.rs @@ -0,0 +1,55 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rialto chain specification for CLI. + +use crate::cli::{bridge, encode_message::CliEncodeMessage, CliChain}; +use bp_runtime::EncodedOrDecodedCall; +use relay_rialto_client::Rialto; +use sp_version::RuntimeVersion; +use xcm::latest::prelude::*; + +impl CliEncodeMessage for Rialto { + fn encode_send_xcm( + message: xcm::VersionedXcm<()>, + bridge_instance_index: u8, + ) -> anyhow::Result> { + let dest = match bridge_instance_index { + bridge::RIALTO_TO_MILLAU_INDEX => + (Parent, X1(GlobalConsensus(rialto_runtime::xcm_config::MillauNetwork::get()))), + _ => anyhow::bail!( + "Unsupported target bridge pallet with instance index: {}", + bridge_instance_index + ), + }; + + Ok(rialto_runtime::RuntimeCall::XcmPallet(rialto_runtime::XcmCall::send { + dest: Box::new(dest.into()), + message: Box::new(message), + }) + .into()) + } +} + +impl CliChain for Rialto { + const RUNTIME_VERSION: Option = Some(rialto_runtime::VERSION); + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + rialto_runtime::SS58Prefix::get() as u16 + } +} diff --git a/relays/bin-substrate/src/chains/rialto_headers_to_millau.rs b/relays/bin-substrate/src/chains/rialto_headers_to_millau.rs new file mode 100644 index 00000000000..b1ab4a8537b --- /dev/null +++ b/relays/bin-substrate/src/chains/rialto_headers_to_millau.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rialto-to-Millau headers sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, MessagesCliBridge, RelayToRelayHeadersCliBridge}; +use substrate_relay_helper::finality::{ + engine::Grandpa as GrandpaFinalityEngine, DirectSubmitGrandpaFinalityProofCallBuilder, + SubstrateFinalitySyncPipeline, +}; + +/// Description of Millau -> Rialto finalized headers bridge. +#[derive(Clone, Debug)] +pub struct RialtoFinalityToMillau; + +impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau { + type SourceChain = relay_rialto_client::Rialto; + type TargetChain = relay_millau_client::Millau; + + type FinalityEngine = GrandpaFinalityEngine; + type SubmitFinalityProofCallBuilder = DirectSubmitGrandpaFinalityProofCallBuilder< + Self, + millau_runtime::Runtime, + millau_runtime::RialtoGrandpaInstance, + >; +} + +//// `Rialto` to `Millau` bridge definition. +pub struct RialtoToMillauCliBridge {} + +impl CliBridgeBase for RialtoToMillauCliBridge { + type Source = relay_rialto_client::Rialto; + type Target = relay_millau_client::Millau; +} + +impl RelayToRelayHeadersCliBridge for RialtoToMillauCliBridge { + type Finality = RialtoFinalityToMillau; +} + +impl MessagesCliBridge for RialtoToMillauCliBridge { + const ESTIMATE_MESSAGE_FEE_METHOD: &'static str = + bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD; + type MessagesLane = crate::chains::rialto_messages_to_millau::RialtoMessagesToMillau; +} diff --git a/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs b/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs new file mode 100644 index 00000000000..80b6b9fdbc6 --- /dev/null +++ b/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs @@ -0,0 +1,44 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rialto-to-Millau messages sync entrypoint. + +use relay_millau_client::Millau; +use relay_rialto_client::Rialto; +use substrate_relay_helper::messages_lane::{ + DirectReceiveMessagesDeliveryProofCallBuilder, DirectReceiveMessagesProofCallBuilder, + SubstrateMessageLane, +}; + +/// Description of Rialto -> Millau messages bridge. +#[derive(Clone, Debug)] +pub struct RialtoMessagesToMillau; + +impl SubstrateMessageLane for RialtoMessagesToMillau { + type SourceChain = Rialto; + type TargetChain = Millau; + + type ReceiveMessagesProofCallBuilder = DirectReceiveMessagesProofCallBuilder< + Self, + millau_runtime::Runtime, + millau_runtime::WithRialtoMessagesInstance, + >; + type ReceiveMessagesDeliveryProofCallBuilder = DirectReceiveMessagesDeliveryProofCallBuilder< + Self, + rialto_runtime::Runtime, + rialto_runtime::WithMillauMessagesInstance, + >; +} diff --git a/relays/bin-substrate/src/chains/rialto_parachain.rs b/relays/bin-substrate/src/chains/rialto_parachain.rs new file mode 100644 index 00000000000..a480dc3eebb --- /dev/null +++ b/relays/bin-substrate/src/chains/rialto_parachain.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rialto parachain specification for CLI. + +use crate::cli::{bridge, encode_message::CliEncodeMessage, CliChain}; +use bp_runtime::EncodedOrDecodedCall; +use relay_rialto_parachain_client::RialtoParachain; +use sp_version::RuntimeVersion; +use xcm::latest::prelude::*; + +impl CliEncodeMessage for RialtoParachain { + fn encode_send_xcm( + message: xcm::VersionedXcm<()>, + bridge_instance_index: u8, + ) -> anyhow::Result> { + let dest = match bridge_instance_index { + bridge::RIALTO_PARACHAIN_TO_MILLAU_INDEX => + (Parent, X1(GlobalConsensus(rialto_parachain_runtime::MillauNetwork::get()))), + _ => anyhow::bail!( + "Unsupported target bridge pallet with instance index: {}", + bridge_instance_index + ), + }; + + Ok(rialto_parachain_runtime::RuntimeCall::PolkadotXcm( + rialto_parachain_runtime::XcmCall::send { + dest: Box::new(dest.into()), + message: Box::new(message), + }, + ) + .into()) + } +} + +impl CliChain for RialtoParachain { + const RUNTIME_VERSION: Option = Some(rialto_parachain_runtime::VERSION); + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + rialto_parachain_runtime::SS58Prefix::get() as u16 + } +} diff --git a/relays/bin-substrate/src/chains/rialto_parachain_messages_to_millau.rs b/relays/bin-substrate/src/chains/rialto_parachain_messages_to_millau.rs new file mode 100644 index 00000000000..5cca26105b8 --- /dev/null +++ b/relays/bin-substrate/src/chains/rialto_parachain_messages_to_millau.rs @@ -0,0 +1,44 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! RialtoParachain-to-Millau messages sync entrypoint. + +use relay_millau_client::Millau; +use relay_rialto_parachain_client::RialtoParachain; +use substrate_relay_helper::messages_lane::{ + DirectReceiveMessagesDeliveryProofCallBuilder, DirectReceiveMessagesProofCallBuilder, + SubstrateMessageLane, +}; + +/// Description of RialtoParachain -> Millau messages bridge. +#[derive(Clone, Debug)] +pub struct RialtoParachainMessagesToMillau; + +impl SubstrateMessageLane for RialtoParachainMessagesToMillau { + type SourceChain = RialtoParachain; + type TargetChain = Millau; + + type ReceiveMessagesProofCallBuilder = DirectReceiveMessagesProofCallBuilder< + Self, + millau_runtime::Runtime, + millau_runtime::WithRialtoParachainMessagesInstance, + >; + type ReceiveMessagesDeliveryProofCallBuilder = DirectReceiveMessagesDeliveryProofCallBuilder< + Self, + rialto_parachain_runtime::Runtime, + rialto_parachain_runtime::WithMillauMessagesInstance, + >; +} diff --git a/relays/bin-substrate/src/chains/rialto_parachains_to_millau.rs b/relays/bin-substrate/src/chains/rialto_parachains_to_millau.rs new file mode 100644 index 00000000000..911d4399389 --- /dev/null +++ b/relays/bin-substrate/src/chains/rialto_parachains_to_millau.rs @@ -0,0 +1,74 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rialto-to-Millau parachains sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, MessagesCliBridge, ParachainToRelayHeadersCliBridge}; +use parachains_relay::ParachainsPipeline; +use relay_millau_client::Millau; +use relay_rialto_client::Rialto; +use relay_rialto_parachain_client::RialtoParachain; +use substrate_relay_helper::parachains::{ + DirectSubmitParachainHeadsCallBuilder, SubstrateParachainsPipeline, +}; + +/// Rialto-to-Millau parachains sync description. +#[derive(Clone, Debug)] +pub struct RialtoParachainsToMillau; + +impl ParachainsPipeline for RialtoParachainsToMillau { + type SourceChain = Rialto; + type TargetChain = Millau; +} + +impl SubstrateParachainsPipeline for RialtoParachainsToMillau { + type SourceParachain = RialtoParachain; + type SourceRelayChain = Rialto; + type TargetChain = Millau; + + type SubmitParachainHeadsCallBuilder = RialtoParachainsToMillauSubmitParachainHeadsCallBuilder; + + const SOURCE_PARACHAIN_PARA_ID: u32 = bp_rialto_parachain::RIALTO_PARACHAIN_ID; +} + +/// `submit_parachain_heads` call builder for Rialto-to-Millau parachains sync pipeline. +pub type RialtoParachainsToMillauSubmitParachainHeadsCallBuilder = + DirectSubmitParachainHeadsCallBuilder< + RialtoParachainsToMillau, + millau_runtime::Runtime, + millau_runtime::WithRialtoParachainsInstance, + >; + +//// `RialtoParachain` to `Millau` bridge definition. +pub struct RialtoParachainToMillauCliBridge {} + +impl CliBridgeBase for RialtoParachainToMillauCliBridge { + type Source = RialtoParachain; + type Target = Millau; +} + +impl ParachainToRelayHeadersCliBridge for RialtoParachainToMillauCliBridge { + type SourceRelay = Rialto; + type ParachainFinality = RialtoParachainsToMillau; + type RelayFinality = crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau; +} + +impl MessagesCliBridge for RialtoParachainToMillauCliBridge { + const ESTIMATE_MESSAGE_FEE_METHOD: &'static str = + bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD; + type MessagesLane = + crate::chains::rialto_parachain_messages_to_millau::RialtoParachainMessagesToMillau; +} diff --git a/relays/bin-substrate/src/chains/rococo.rs b/relays/bin-substrate/src/chains/rococo.rs new file mode 100644 index 00000000000..6a99eab6cb3 --- /dev/null +++ b/relays/bin-substrate/src/chains/rococo.rs @@ -0,0 +1,42 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rococo + Rococo parachains specification for CLI. + +use crate::cli::CliChain; +use relay_bridge_hub_rococo_client::BridgeHubRococo; +use relay_rococo_client::Rococo; +use sp_version::RuntimeVersion; + +impl CliChain for Rococo { + const RUNTIME_VERSION: Option = None; + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + bp_rococo::SS58Prefix::get() as u16 + } +} + +impl CliChain for BridgeHubRococo { + const RUNTIME_VERSION: Option = None; + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + relay_bridge_hub_rococo_client::runtime::SS58Prefix::get() + } +} diff --git a/relays/bin-substrate/src/chains/rococo_headers_to_bridge_hub_wococo.rs b/relays/bin-substrate/src/chains/rococo_headers_to_bridge_hub_wococo.rs new file mode 100644 index 00000000000..2ec13614a34 --- /dev/null +++ b/relays/bin-substrate/src/chains/rococo_headers_to_bridge_hub_wococo.rs @@ -0,0 +1,53 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rococo-to-Wococo bridge hubs headers sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, RelayToRelayHeadersCliBridge}; +use substrate_relay_helper::finality::{ + engine::Grandpa as GrandpaFinalityEngine, SubstrateFinalitySyncPipeline, +}; + +/// Description of Rococo -> Wococo finalized headers bridge. +#[derive(Clone, Debug)] +pub struct RococoFinalityToBridgeHubWococo; + +substrate_relay_helper::generate_mocked_submit_finality_proof_call_builder!( + RococoFinalityToBridgeHubWococo, + RococoFinalityToBridgeHubWococoCallBuilder, + relay_bridge_hub_wococo_client::runtime::Call::BridgeRococoGrandpa, + relay_bridge_hub_wococo_client::runtime::BridgeGrandpaRococoCall::submit_finality_proof +); + +impl SubstrateFinalitySyncPipeline for RococoFinalityToBridgeHubWococo { + type SourceChain = relay_rococo_client::Rococo; + type TargetChain = relay_bridge_hub_wococo_client::BridgeHubWococo; + + type FinalityEngine = GrandpaFinalityEngine; + type SubmitFinalityProofCallBuilder = RococoFinalityToBridgeHubWococoCallBuilder; +} + +/// `Rococo` to BridgeHub `Wococo` bridge definition. +pub struct RococoToBridgeHubWococoCliBridge {} + +impl CliBridgeBase for RococoToBridgeHubWococoCliBridge { + type Source = relay_rococo_client::Rococo; + type Target = relay_bridge_hub_wococo_client::BridgeHubWococo; +} + +impl RelayToRelayHeadersCliBridge for RococoToBridgeHubWococoCliBridge { + type Finality = RococoFinalityToBridgeHubWococo; +} diff --git a/relays/bin-substrate/src/chains/rococo_parachains_to_bridge_hub_wococo.rs b/relays/bin-substrate/src/chains/rococo_parachains_to_bridge_hub_wococo.rs new file mode 100644 index 00000000000..509cbf70db1 --- /dev/null +++ b/relays/bin-substrate/src/chains/rococo_parachains_to_bridge_hub_wococo.rs @@ -0,0 +1,78 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Wococo-to-Rococo parachains sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, ParachainToRelayHeadersCliBridge}; +use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; +use parachains_relay::ParachainsPipeline; +use relay_substrate_client::{CallOf, HeaderIdOf}; +use substrate_relay_helper::parachains::{ + SubmitParachainHeadsCallBuilder, SubstrateParachainsPipeline, +}; + +/// BridgeHub-to-BridgeHub parachain sync description. +#[derive(Clone, Debug)] +pub struct BridgeHubRococoToBridgeHubWococo; + +impl ParachainsPipeline for BridgeHubRococoToBridgeHubWococo { + type SourceChain = relay_rococo_client::Rococo; + type TargetChain = relay_bridge_hub_wococo_client::BridgeHubWococo; +} + +impl SubstrateParachainsPipeline for BridgeHubRococoToBridgeHubWococo { + type SourceParachain = relay_bridge_hub_rococo_client::BridgeHubRococo; + type SourceRelayChain = relay_rococo_client::Rococo; + type TargetChain = relay_bridge_hub_wococo_client::BridgeHubWococo; + + type SubmitParachainHeadsCallBuilder = BridgeHubRococoToBridgeHubWococoCallBuilder; + + const SOURCE_PARACHAIN_PARA_ID: u32 = bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID; +} + +pub struct BridgeHubRococoToBridgeHubWococoCallBuilder; +impl SubmitParachainHeadsCallBuilder + for BridgeHubRococoToBridgeHubWococoCallBuilder +{ + fn build_submit_parachain_heads_call( + at_relay_block: HeaderIdOf, + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + ) -> CallOf { + relay_bridge_hub_wococo_client::runtime::Call::BridgeRococoParachain( + relay_bridge_hub_wococo_client::runtime::BridgeParachainCall::submit_parachain_heads( + (at_relay_block.0, at_relay_block.1), + parachains, + parachain_heads_proof, + ), + ) + } +} + +/// `BridgeHubParachain` to `BridgeHubParachain` bridge definition. +pub struct BridgeHubRococoToBridgeHubWococoCliBridge {} + +impl ParachainToRelayHeadersCliBridge for BridgeHubRococoToBridgeHubWococoCliBridge { + type SourceRelay = relay_rococo_client::Rococo; + type ParachainFinality = BridgeHubRococoToBridgeHubWococo; + type RelayFinality = + crate::chains::rococo_headers_to_bridge_hub_wococo::RococoFinalityToBridgeHubWococo; +} + +impl CliBridgeBase for BridgeHubRococoToBridgeHubWococoCliBridge { + type Source = relay_bridge_hub_rococo_client::BridgeHubRococo; + type Target = relay_bridge_hub_wococo_client::BridgeHubWococo; +} diff --git a/relays/bin-substrate/src/chains/westend.rs b/relays/bin-substrate/src/chains/westend.rs new file mode 100644 index 00000000000..1627bc015d3 --- /dev/null +++ b/relays/bin-substrate/src/chains/westend.rs @@ -0,0 +1,47 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Westend chain specification for CLI. + +use crate::cli::CliChain; +use relay_westend_client::{Westend, Westmint}; +use sp_version::RuntimeVersion; + +impl CliChain for Westend { + const RUNTIME_VERSION: Option = None; + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + sp_core::crypto::Ss58AddressFormat::from( + sp_core::crypto::Ss58AddressFormatRegistry::SubstrateAccount, + ) + .into() + } +} + +impl CliChain for Westmint { + const RUNTIME_VERSION: Option = None; + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + sp_core::crypto::Ss58AddressFormat::from( + sp_core::crypto::Ss58AddressFormatRegistry::SubstrateAccount, + ) + .into() + } +} diff --git a/relays/bin-substrate/src/chains/westend_headers_to_millau.rs b/relays/bin-substrate/src/chains/westend_headers_to_millau.rs new file mode 100644 index 00000000000..2a253756c2b --- /dev/null +++ b/relays/bin-substrate/src/chains/westend_headers_to_millau.rs @@ -0,0 +1,51 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Westend-to-Millau headers sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, RelayToRelayHeadersCliBridge}; +use substrate_relay_helper::finality::{ + engine::Grandpa as GrandpaFinalityEngine, DirectSubmitGrandpaFinalityProofCallBuilder, + SubstrateFinalitySyncPipeline, +}; + +/// Description of Westend -> Millau finalized headers bridge. +#[derive(Clone, Debug)] +pub struct WestendFinalityToMillau; + +impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau { + type SourceChain = relay_westend_client::Westend; + type TargetChain = relay_millau_client::Millau; + + type FinalityEngine = GrandpaFinalityEngine; + type SubmitFinalityProofCallBuilder = DirectSubmitGrandpaFinalityProofCallBuilder< + Self, + millau_runtime::Runtime, + millau_runtime::WestendGrandpaInstance, + >; +} + +//// `Westend` to `Millau` bridge definition. +pub struct WestendToMillauCliBridge {} + +impl CliBridgeBase for WestendToMillauCliBridge { + type Source = relay_westend_client::Westend; + type Target = relay_millau_client::Millau; +} + +impl RelayToRelayHeadersCliBridge for WestendToMillauCliBridge { + type Finality = WestendFinalityToMillau; +} diff --git a/relays/bin-substrate/src/chains/westend_parachains_to_millau.rs b/relays/bin-substrate/src/chains/westend_parachains_to_millau.rs new file mode 100644 index 00000000000..73409e65569 --- /dev/null +++ b/relays/bin-substrate/src/chains/westend_parachains_to_millau.rs @@ -0,0 +1,66 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Westend-to-Millau parachains sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, ParachainToRelayHeadersCliBridge}; +use parachains_relay::ParachainsPipeline; +use relay_millau_client::Millau; +use relay_westend_client::{Westend, Westmint}; +use substrate_relay_helper::parachains::{ + DirectSubmitParachainHeadsCallBuilder, SubstrateParachainsPipeline, +}; + +/// Westend-to-Millau parachains sync description. +#[derive(Clone, Debug)] +pub struct WestendParachainsToMillau; + +impl ParachainsPipeline for WestendParachainsToMillau { + type SourceChain = Westend; + type TargetChain = Millau; +} + +impl SubstrateParachainsPipeline for WestendParachainsToMillau { + type SourceParachain = Westmint; + type SourceRelayChain = Westend; + type TargetChain = Millau; + + type SubmitParachainHeadsCallBuilder = WestendParachainsToMillauSubmitParachainHeadsCallBuilder; + + const SOURCE_PARACHAIN_PARA_ID: u32 = bp_westend::WESTMINT_PARACHAIN_ID; +} + +/// `submit_parachain_heads` call builder for Rialto-to-Millau parachains sync pipeline. +pub type WestendParachainsToMillauSubmitParachainHeadsCallBuilder = + DirectSubmitParachainHeadsCallBuilder< + WestendParachainsToMillau, + millau_runtime::Runtime, + millau_runtime::WithWestendParachainsInstance, + >; + +//// `WestendParachain` to `Millau` bridge definition. +pub struct WestmintToMillauCliBridge {} + +impl ParachainToRelayHeadersCliBridge for WestmintToMillauCliBridge { + type SourceRelay = Westend; + type ParachainFinality = WestendParachainsToMillau; + type RelayFinality = crate::chains::westend_headers_to_millau::WestendFinalityToMillau; +} + +impl CliBridgeBase for WestmintToMillauCliBridge { + type Source = Westmint; + type Target = Millau; +} diff --git a/relays/bin-substrate/src/chains/wococo.rs b/relays/bin-substrate/src/chains/wococo.rs new file mode 100644 index 00000000000..c44f3ac811f --- /dev/null +++ b/relays/bin-substrate/src/chains/wococo.rs @@ -0,0 +1,42 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Wococo + Wococo parachains specification for CLI. + +use crate::cli::CliChain; +use relay_bridge_hub_wococo_client::BridgeHubWococo; +use relay_wococo_client::Wococo; +use sp_version::RuntimeVersion; + +impl CliChain for Wococo { + const RUNTIME_VERSION: Option = None; + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + bp_wococo::SS58Prefix::get() as u16 + } +} + +impl CliChain for BridgeHubWococo { + const RUNTIME_VERSION: Option = None; + + type KeyPair = sp_core::sr25519::Pair; + + fn ss58_format() -> u16 { + relay_bridge_hub_wococo_client::runtime::SS58Prefix::get() + } +} diff --git a/relays/bin-substrate/src/chains/wococo_headers_to_bridge_hub_rococo.rs b/relays/bin-substrate/src/chains/wococo_headers_to_bridge_hub_rococo.rs new file mode 100644 index 00000000000..30d6af00cd3 --- /dev/null +++ b/relays/bin-substrate/src/chains/wococo_headers_to_bridge_hub_rococo.rs @@ -0,0 +1,53 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Wococo-to-Rococo bridge hubs headers sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, RelayToRelayHeadersCliBridge}; +use substrate_relay_helper::finality::{ + engine::Grandpa as GrandpaFinalityEngine, SubstrateFinalitySyncPipeline, +}; + +/// Description of Wococo -> Rococo finalized headers bridge. +#[derive(Clone, Debug)] +pub struct WococoFinalityToBridgeHubRococo; + +substrate_relay_helper::generate_mocked_submit_finality_proof_call_builder!( + WococoFinalityToBridgeHubRococo, + WococoFinalityToBridgeHubRococoCallBuilder, + relay_bridge_hub_rococo_client::runtime::Call::BridgeWococoGrandpa, + relay_bridge_hub_rococo_client::runtime::BridgeWococoGrandpaCall::submit_finality_proof +); + +impl SubstrateFinalitySyncPipeline for WococoFinalityToBridgeHubRococo { + type SourceChain = relay_wococo_client::Wococo; + type TargetChain = relay_bridge_hub_rococo_client::BridgeHubRococo; + + type FinalityEngine = GrandpaFinalityEngine; + type SubmitFinalityProofCallBuilder = WococoFinalityToBridgeHubRococoCallBuilder; +} + +/// `Wococo` to BridgeHub `Rococo` bridge definition. +pub struct WococoToBridgeHubRococoCliBridge {} + +impl CliBridgeBase for WococoToBridgeHubRococoCliBridge { + type Source = relay_wococo_client::Wococo; + type Target = relay_bridge_hub_rococo_client::BridgeHubRococo; +} + +impl RelayToRelayHeadersCliBridge for WococoToBridgeHubRococoCliBridge { + type Finality = WococoFinalityToBridgeHubRococo; +} diff --git a/relays/bin-substrate/src/chains/wococo_parachains_to_bridge_hub_rococo.rs b/relays/bin-substrate/src/chains/wococo_parachains_to_bridge_hub_rococo.rs new file mode 100644 index 00000000000..d4fbf095df4 --- /dev/null +++ b/relays/bin-substrate/src/chains/wococo_parachains_to_bridge_hub_rococo.rs @@ -0,0 +1,78 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Rococo-to-Wococo parachains sync entrypoint. + +use crate::cli::bridge::{CliBridgeBase, ParachainToRelayHeadersCliBridge}; +use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; +use parachains_relay::ParachainsPipeline; +use relay_substrate_client::{CallOf, HeaderIdOf}; +use substrate_relay_helper::parachains::{ + SubmitParachainHeadsCallBuilder, SubstrateParachainsPipeline, +}; + +/// BridgeHub-to-BridgeHub parachain sync description. +#[derive(Clone, Debug)] +pub struct BridgeHubWococoToBridgeHubRococo; + +impl ParachainsPipeline for BridgeHubWococoToBridgeHubRococo { + type SourceChain = relay_wococo_client::Wococo; + type TargetChain = relay_bridge_hub_rococo_client::BridgeHubRococo; +} + +impl SubstrateParachainsPipeline for BridgeHubWococoToBridgeHubRococo { + type SourceParachain = relay_bridge_hub_wococo_client::BridgeHubWococo; + type SourceRelayChain = relay_wococo_client::Wococo; + type TargetChain = relay_bridge_hub_rococo_client::BridgeHubRococo; + + type SubmitParachainHeadsCallBuilder = BridgeHubWococoToBridgeHubRococoCallBuilder; + + const SOURCE_PARACHAIN_PARA_ID: u32 = bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID; +} + +pub struct BridgeHubWococoToBridgeHubRococoCallBuilder; +impl SubmitParachainHeadsCallBuilder + for BridgeHubWococoToBridgeHubRococoCallBuilder +{ + fn build_submit_parachain_heads_call( + at_relay_block: HeaderIdOf, + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + ) -> CallOf { + relay_bridge_hub_rococo_client::runtime::Call::BridgeWococoParachain( + relay_bridge_hub_rococo_client::runtime::BridgeParachainCall::submit_parachain_heads( + (at_relay_block.0, at_relay_block.1), + parachains, + parachain_heads_proof, + ), + ) + } +} + +/// `BridgeHubParachain` to `BridgeHubParachain` bridge definition. +pub struct BridgeHubWococoToBridgeHubRococoCliBridge {} + +impl ParachainToRelayHeadersCliBridge for BridgeHubWococoToBridgeHubRococoCliBridge { + type SourceRelay = relay_wococo_client::Wococo; + type ParachainFinality = BridgeHubWococoToBridgeHubRococo; + type RelayFinality = + crate::chains::wococo_headers_to_bridge_hub_rococo::WococoFinalityToBridgeHubRococo; +} + +impl CliBridgeBase for BridgeHubWococoToBridgeHubRococoCliBridge { + type Source = relay_bridge_hub_wococo_client::BridgeHubWococo; + type Target = relay_bridge_hub_rococo_client::BridgeHubRococo; +} diff --git a/relays/bin-substrate/src/cli/bridge.rs b/relays/bin-substrate/src/cli/bridge.rs new file mode 100644 index 00000000000..ae2a36cd1d5 --- /dev/null +++ b/relays/bin-substrate/src/cli/bridge.rs @@ -0,0 +1,105 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::cli::CliChain; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use parachains_relay::ParachainsPipeline; +use relay_substrate_client::{AccountKeyPairOf, Chain, ChainWithTransactions, RelayChain}; +use strum::{EnumString, EnumVariantNames}; +use substrate_relay_helper::{ + finality::SubstrateFinalitySyncPipeline, messages_lane::SubstrateMessageLane, + parachains::SubstrateParachainsPipeline, +}; + +#[derive(Debug, PartialEq, Eq, EnumString, EnumVariantNames)] +#[strum(serialize_all = "kebab_case")] +/// Supported full bridges (headers + messages). +pub enum FullBridge { + MillauToRialto, + RialtoToMillau, + MillauToRialtoParachain, + RialtoParachainToMillau, + BridgeHubRococoToBridgeHubWococo, + BridgeHubWococoToBridgeHubRococo, +} + +impl FullBridge { + /// Return instance index of the bridge pallet in source runtime. + pub fn bridge_instance_index(&self) -> u8 { + match self { + Self::MillauToRialto => MILLAU_TO_RIALTO_INDEX, + Self::RialtoToMillau => RIALTO_TO_MILLAU_INDEX, + Self::MillauToRialtoParachain => MILLAU_TO_RIALTO_PARACHAIN_INDEX, + Self::RialtoParachainToMillau => RIALTO_PARACHAIN_TO_MILLAU_INDEX, + Self::BridgeHubRococoToBridgeHubWococo | Self::BridgeHubWococoToBridgeHubRococo => + unimplemented!("Relay doesn't support send-message subcommand on bridge hubs"), + } + } +} + +pub const RIALTO_TO_MILLAU_INDEX: u8 = 0; +pub const MILLAU_TO_RIALTO_INDEX: u8 = 0; +pub const MILLAU_TO_RIALTO_PARACHAIN_INDEX: u8 = 1; +pub const RIALTO_PARACHAIN_TO_MILLAU_INDEX: u8 = 0; + +/// Minimal bridge representation that can be used from the CLI. +/// It connects a source chain to a target chain. +pub trait CliBridgeBase: Sized { + /// The source chain. + type Source: Chain + CliChain; + /// The target chain. + type Target: ChainWithTransactions + CliChain>; +} + +/// Bridge representation that can be used from the CLI for relaying headers +/// from a relay chain to a relay chain. +pub trait RelayToRelayHeadersCliBridge: CliBridgeBase { + /// Finality proofs synchronization pipeline. + type Finality: SubstrateFinalitySyncPipeline< + SourceChain = Self::Source, + TargetChain = Self::Target, + >; +} + +/// Bridge representation that can be used from the CLI for relaying headers +/// from a parachain to a relay chain. +pub trait ParachainToRelayHeadersCliBridge: CliBridgeBase { + // The `CliBridgeBase` type represents the parachain in this situation. + // We need to add an extra type for the relay chain. + type SourceRelay: Chain + + CliChain + + RelayChain; + /// Finality proofs synchronization pipeline (source parachain -> target). + type ParachainFinality: SubstrateParachainsPipeline< + SourceRelayChain = Self::SourceRelay, + SourceParachain = Self::Source, + TargetChain = Self::Target, + > + ParachainsPipeline; + /// Finality proofs synchronization pipeline (source relay chain -> target). + type RelayFinality: SubstrateFinalitySyncPipeline< + SourceChain = Self::SourceRelay, + TargetChain = Self::Target, + >; +} + +/// Bridge representation that can be used from the CLI for relaying messages. +pub trait MessagesCliBridge: CliBridgeBase { + /// Name of the runtime method used to estimate the message dispatch and delivery fee for the + /// defined bridge. + const ESTIMATE_MESSAGE_FEE_METHOD: &'static str; + /// The Source -> Destination messages synchronization pipeline. + type MessagesLane: SubstrateMessageLane; +} diff --git a/relays/bin-substrate/src/cli/chain_schema.rs b/relays/bin-substrate/src/cli/chain_schema.rs new file mode 100644 index 00000000000..8023fd9b0f7 --- /dev/null +++ b/relays/bin-substrate/src/cli/chain_schema.rs @@ -0,0 +1,409 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use sp_core::Pair; +use structopt::StructOpt; +use strum::{EnumString, EnumVariantNames}; + +use crate::cli::CliChain; +pub use relay_substrate_client::ChainRuntimeVersion; +use substrate_relay_helper::TransactionParams; + +#[doc = "Runtime version params."] +#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy, EnumString, EnumVariantNames)] +pub enum RuntimeVersionType { + /// Auto query version from chain + Auto, + /// Custom `spec_version` and `transaction_version` + Custom, + /// Read version from bundle dependencies directly. + Bundle, +} + +/// Create chain-specific set of runtime version parameters. +#[macro_export] +macro_rules! declare_chain_runtime_version_params_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + bp_runtime::paste::item! { + #[doc = $chain " runtime version params."] + #[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy)] + pub struct [<$chain RuntimeVersionParams>] { + #[doc = "The type of runtime version for chain " $chain] + #[structopt(long, default_value = "Bundle")] + pub [<$chain_prefix _version_mode>]: RuntimeVersionType, + #[doc = "The custom sepc_version for chain " $chain] + #[structopt(long)] + pub [<$chain_prefix _spec_version>]: Option, + #[doc = "The custom transaction_version for chain " $chain] + #[structopt(long)] + pub [<$chain_prefix _transaction_version>]: Option, + } + + impl [<$chain RuntimeVersionParams>] { + /// Converts self into `ChainRuntimeVersion`. + pub fn into_runtime_version( + self, + bundle_runtime_version: Option, + ) -> anyhow::Result { + Ok(match self.[<$chain_prefix _version_mode>] { + RuntimeVersionType::Auto => ChainRuntimeVersion::Auto, + RuntimeVersionType::Custom => { + let except_spec_version = self.[<$chain_prefix _spec_version>] + .ok_or_else(|| anyhow::Error::msg(format!("The {}-spec-version is required when choose custom mode", stringify!($chain_prefix))))?; + let except_transaction_version = self.[<$chain_prefix _transaction_version>] + .ok_or_else(|| anyhow::Error::msg(format!("The {}-transaction-version is required when choose custom mode", stringify!($chain_prefix))))?; + ChainRuntimeVersion::Custom( + except_spec_version, + except_transaction_version + ) + }, + RuntimeVersionType::Bundle => match bundle_runtime_version { + Some(runtime_version) => ChainRuntimeVersion::Custom( + runtime_version.spec_version, + runtime_version.transaction_version + ), + None => ChainRuntimeVersion::Auto + }, + }) + } + } + } + }; +} + +/// Create chain-specific set of runtime version parameters. +#[macro_export] +macro_rules! declare_chain_connection_params_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + bp_runtime::paste::item! { + #[doc = $chain " connection params."] + #[derive(StructOpt, Debug, PartialEq, Eq, Clone)] + pub struct [<$chain ConnectionParams>] { + #[doc = "Connect to " $chain " node at given host."] + #[structopt(long, default_value = "127.0.0.1")] + pub [<$chain_prefix _host>]: String, + #[doc = "Connect to " $chain " node websocket server at given port."] + #[structopt(long, default_value = "9944")] + pub [<$chain_prefix _port>]: u16, + #[doc = "Use secure websocket connection."] + #[structopt(long)] + pub [<$chain_prefix _secure>]: bool, + #[doc = "Custom runtime version"] + #[structopt(flatten)] + pub [<$chain_prefix _runtime_version>]: [<$chain RuntimeVersionParams>], + } + + impl [<$chain ConnectionParams>] { + /// Convert connection params into Substrate client. + #[allow(dead_code)] + pub async fn into_client( + self, + ) -> anyhow::Result> { + let chain_runtime_version = self + .[<$chain_prefix _runtime_version>] + .into_runtime_version(Chain::RUNTIME_VERSION)?; + Ok(relay_substrate_client::Client::new(relay_substrate_client::ConnectionParams { + host: self.[<$chain_prefix _host>], + port: self.[<$chain_prefix _port>], + secure: self.[<$chain_prefix _secure>], + chain_runtime_version, + }) + .await + ) + } + } + } + }; +} + +/// Helper trait to override transaction parameters differently. +pub trait TransactionParamsProvider { + /// Returns `true` if transaction parameters are defined by this provider. + fn is_defined(&self) -> bool; + /// Returns transaction parameters. + fn transaction_params( + &self, + ) -> anyhow::Result>; + + /// Returns transaction parameters, defined by `self` provider or, if they're not defined, + /// defined by `other` provider. + fn transaction_params_or( + &self, + other: &T, + ) -> anyhow::Result> { + if self.is_defined() { + self.transaction_params::() + } else { + other.transaction_params::() + } + } +} + +/// Create chain-specific set of signing parameters. +#[macro_export] +macro_rules! declare_chain_signing_params_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + bp_runtime::paste::item! { + #[doc = $chain " signing params."] + #[derive(StructOpt, Debug, PartialEq, Eq, Clone)] + pub struct [<$chain SigningParams>] { + #[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."] + #[structopt(long)] + pub [<$chain_prefix _signer>]: Option, + #[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."] + #[structopt(long)] + pub [<$chain_prefix _signer_password>]: Option, + + #[doc = "Path to the file, that contains SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer option."] + #[structopt(long)] + pub [<$chain_prefix _signer_file>]: Option, + #[doc = "Path to the file, that password for the SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer_password option."] + #[structopt(long)] + pub [<$chain_prefix _signer_password_file>]: Option, + + #[doc = "Transactions mortality period, in blocks. MUST be a power of two in [4; 65536] range. MAY NOT be larger than `BlockHashCount` parameter of the chain system module."] + #[structopt(long)] + pub [<$chain_prefix _transactions_mortality>]: Option, + } + + impl [<$chain SigningParams>] { + /// Return transactions mortality. + #[allow(dead_code)] + pub fn transactions_mortality(&self) -> anyhow::Result> { + self.[<$chain_prefix _transactions_mortality>] + .map(|transactions_mortality| { + if !(4..=65536).contains(&transactions_mortality) + || !transactions_mortality.is_power_of_two() + { + Err(anyhow::format_err!( + "Transactions mortality {} is not a power of two in a [4; 65536] range", + transactions_mortality, + )) + } else { + Ok(transactions_mortality) + } + }) + .transpose() + } + + /// Parse signing params into chain-specific KeyPair. + #[allow(dead_code)] + pub fn to_keypair(&self) -> anyhow::Result { + let suri = match (self.[<$chain_prefix _signer>].as_ref(), self.[<$chain_prefix _signer_file>].as_ref()) { + (Some(suri), _) => suri.to_owned(), + (None, Some(suri_file)) => std::fs::read_to_string(suri_file) + .map_err(|err| anyhow::format_err!( + "Failed to read SURI from file {:?}: {}", + suri_file, + err, + ))?, + (None, None) => return Err(anyhow::format_err!( + "One of options must be specified: '{}' or '{}'", + stringify!([<$chain_prefix _signer>]), + stringify!([<$chain_prefix _signer_file>]), + )), + }; + + let suri_password = match ( + self.[<$chain_prefix _signer_password>].as_ref(), + self.[<$chain_prefix _signer_password_file>].as_ref(), + ) { + (Some(suri_password), _) => Some(suri_password.to_owned()), + (None, Some(suri_password_file)) => std::fs::read_to_string(suri_password_file) + .map(Some) + .map_err(|err| anyhow::format_err!( + "Failed to read SURI password from file {:?}: {}", + suri_password_file, + err, + ))?, + _ => None, + }; + + use sp_core::crypto::Pair; + + Chain::KeyPair::from_string( + &suri, + suri_password.as_deref() + ).map_err(|e| anyhow::format_err!("{:?}", e)) + } + } + + #[allow(dead_code)] + impl TransactionParamsProvider for [<$chain SigningParams>] { + fn is_defined(&self) -> bool { + self.[<$chain_prefix _signer>].is_some() || self.[<$chain_prefix _signer_file>].is_some() + } + + fn transaction_params(&self) -> anyhow::Result> { + Ok(TransactionParams { + mortality: self.transactions_mortality()?, + signer: self.to_keypair::()?, + }) + } + } + } + }; +} + +/// Create chain-specific set of messages pallet owner signing parameters. +#[macro_export] +macro_rules! declare_chain_messages_pallet_owner_signing_params_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + bp_runtime::paste::item! { + #[doc = "Parameters required to sign transaction on behalf of owner of the messages pallet at " $chain "."] + #[derive(StructOpt, Debug, PartialEq, Eq)] + pub struct [<$chain MessagesPalletOwnerSigningParams>] { + #[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."] + #[structopt(long)] + pub [<$chain_prefix _messages_pallet_owner>]: Option, + #[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."] + #[structopt(long)] + pub [<$chain_prefix _messages_pallet_owner_password>]: Option, + } + + #[allow(dead_code)] + impl [<$chain MessagesPalletOwnerSigningParams>] { + /// Parse signing params into chain-specific KeyPair. + pub fn to_keypair(&self) -> anyhow::Result> { + let [<$chain_prefix _messages_pallet_owner>] = match self.[<$chain_prefix _messages_pallet_owner>] { + Some(ref messages_pallet_owner) => messages_pallet_owner, + None => return Ok(None), + }; + Chain::KeyPair::from_string( + [<$chain_prefix _messages_pallet_owner>], + self.[<$chain_prefix _messages_pallet_owner_password>].as_deref() + ).map_err(|e| anyhow::format_err!("{:?}", e)).map(Some) + } + } + } + }; +} + +/// Create chain-specific set of configuration objects: connection parameters, +/// signing parameters and bridge initialization parameters. +#[macro_export] +macro_rules! declare_chain_cli_schema { + ($chain:ident, $chain_prefix:ident) => { + $crate::declare_chain_runtime_version_params_cli_schema!($chain, $chain_prefix); + $crate::declare_chain_connection_params_cli_schema!($chain, $chain_prefix); + $crate::declare_chain_signing_params_cli_schema!($chain, $chain_prefix); + $crate::declare_chain_messages_pallet_owner_signing_params_cli_schema!( + $chain, + $chain_prefix + ); + }; +} + +declare_chain_cli_schema!(Source, source); +declare_chain_cli_schema!(Target, target); +declare_chain_cli_schema!(Relaychain, relaychain); +declare_chain_cli_schema!(Parachain, parachain); + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::Pair; + + #[test] + fn reads_suri_from_file() { + const ALICE: &str = "//Alice"; + const BOB: &str = "//Bob"; + const ALICE_PASSWORD: &str = "alice_password"; + const BOB_PASSWORD: &str = "bob_password"; + + let alice: sp_core::sr25519::Pair = Pair::from_string(ALICE, Some(ALICE_PASSWORD)).unwrap(); + let bob: sp_core::sr25519::Pair = Pair::from_string(BOB, Some(BOB_PASSWORD)).unwrap(); + let bob_with_alice_password = + sp_core::sr25519::Pair::from_string(BOB, Some(ALICE_PASSWORD)).unwrap(); + + let temp_dir = tempfile::tempdir().unwrap(); + let mut suri_file_path = temp_dir.path().to_path_buf(); + let mut password_file_path = temp_dir.path().to_path_buf(); + suri_file_path.push("suri"); + password_file_path.push("password"); + std::fs::write(&suri_file_path, BOB.as_bytes()).unwrap(); + std::fs::write(&password_file_path, BOB_PASSWORD.as_bytes()).unwrap(); + + // when both seed and password are read from file + assert_eq!( + TargetSigningParams { + target_signer: Some(ALICE.into()), + target_signer_password: Some(ALICE_PASSWORD.into()), + + target_signer_file: None, + target_signer_password_file: None, + + target_transactions_mortality: None, + } + .to_keypair::() + .map(|p| p.public()) + .map_err(drop), + Ok(alice.public()), + ); + + // when both seed and password are read from file + assert_eq!( + TargetSigningParams { + target_signer: None, + target_signer_password: None, + + target_signer_file: Some(suri_file_path.clone()), + target_signer_password_file: Some(password_file_path.clone()), + + target_transactions_mortality: None, + } + .to_keypair::() + .map(|p| p.public()) + .map_err(drop), + Ok(bob.public()), + ); + + // when password are is overriden by cli option + assert_eq!( + TargetSigningParams { + target_signer: None, + target_signer_password: Some(ALICE_PASSWORD.into()), + + target_signer_file: Some(suri_file_path.clone()), + target_signer_password_file: Some(password_file_path.clone()), + + target_transactions_mortality: None, + } + .to_keypair::() + .map(|p| p.public()) + .map_err(drop), + Ok(bob_with_alice_password.public()), + ); + + // when both seed and password are overriden by cli options + assert_eq!( + TargetSigningParams { + target_signer: Some(ALICE.into()), + target_signer_password: Some(ALICE_PASSWORD.into()), + + target_signer_file: Some(suri_file_path), + target_signer_password_file: Some(password_file_path), + + target_transactions_mortality: None, + } + .to_keypair::() + .map(|p| p.public()) + .map_err(drop), + Ok(alice.public()), + ); + } +} diff --git a/relays/bin-substrate/src/cli/encode_message.rs b/relays/bin-substrate/src/cli/encode_message.rs new file mode 100644 index 00000000000..c7ca5d51f79 --- /dev/null +++ b/relays/bin-substrate/src/cli/encode_message.rs @@ -0,0 +1,145 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::cli::{ExplicitOrMaximal, HexBytes}; +use bp_runtime::EncodedOrDecodedCall; +use codec::Encode; +use relay_substrate_client::Chain; +use structopt::StructOpt; + +/// All possible messages that may be delivered to generic Substrate chain. +/// +/// Note this enum may be used in the context of both Source (as part of `encode-call`) +/// and Target chain (as part of `encode-message/send-message`). +#[derive(StructOpt, Debug, PartialEq, Eq)] +pub enum Message { + /// Raw bytes for the message. + Raw { + /// Raw message bytes. + data: HexBytes, + }, + /// Message with given size. + Sized { + /// Sized of the message. + size: ExplicitOrMaximal, + }, +} + +/// Raw, SCALE-encoded message payload used in expected deployment. +pub type RawMessage = Vec; + +pub trait CliEncodeMessage: Chain { + /// Encode a send XCM call of the XCM pallet. + fn encode_send_xcm( + message: xcm::VersionedXcm<()>, + bridge_instance_index: u8, + ) -> anyhow::Result>; +} + +/// Encode message payload passed through CLI flags. +pub(crate) fn encode_message( + message: &Message, +) -> anyhow::Result { + Ok(match message { + Message::Raw { ref data } => data.0.clone(), + Message::Sized { ref size } => { + let expected_xcm_size = match *size { + ExplicitOrMaximal::Explicit(size) => size, + ExplicitOrMaximal::Maximal => compute_maximal_message_size( + Source::max_extrinsic_size(), + Target::max_extrinsic_size(), + ), + }; + + // there's no way to craft XCM of the given size - we'll be using `ExpectPallet` + // instruction, which has byte vector inside + let mut current_vec_size = expected_xcm_size; + let xcm = loop { + let xcm = xcm::VersionedXcm::<()>::V3( + vec![xcm::v3::Instruction::ExpectPallet { + index: 0, + name: vec![42; current_vec_size as usize], + module_name: vec![], + crate_major: 0, + min_crate_minor: 0, + }] + .into(), + ); + if xcm.encode().len() <= expected_xcm_size as usize { + break xcm + } + + current_vec_size -= 1; + }; + xcm.encode() + }, + }) +} + +/// Compute maximal message size, given max extrinsic size at source and target chains. +pub(crate) fn compute_maximal_message_size( + maximal_source_extrinsic_size: u32, + maximal_target_extrinsic_size: u32, +) -> u32 { + // assume that both signed extensions and other arguments fit 1KB + let service_tx_bytes_on_source_chain = 1024; + let maximal_source_extrinsic_size = + maximal_source_extrinsic_size - service_tx_bytes_on_source_chain; + let maximal_message_size = + bridge_runtime_common::messages::target::maximal_incoming_message_size( + maximal_target_extrinsic_size, + ); + if maximal_message_size > maximal_source_extrinsic_size { + maximal_source_extrinsic_size + } else { + maximal_message_size + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::send_message::decode_xcm; + use bp_runtime::Chain; + use relay_millau_client::Millau; + use relay_rialto_client::Rialto; + + #[test] + fn encode_explicit_size_message_works() { + let msg = encode_message::(&Message::Sized { + size: ExplicitOrMaximal::Explicit(100), + }) + .unwrap(); + assert_eq!(msg.len(), 100); + // check that it decodes to valid xcm + let _ = decode_xcm(msg).unwrap(); + } + + #[test] + fn encode_maximal_size_message_works() { + let maximal_size = compute_maximal_message_size( + Rialto::max_extrinsic_size(), + Millau::max_extrinsic_size(), + ); + + let msg = + encode_message::(&Message::Sized { size: ExplicitOrMaximal::Maximal }) + .unwrap(); + assert_eq!(msg.len(), maximal_size as usize); + // check that it decodes to valid xcm + let _ = decode_xcm(msg).unwrap(); + } +} diff --git a/relays/bin-substrate/src/cli/init_bridge.rs b/relays/bin-substrate/src/cli/init_bridge.rs new file mode 100644 index 00000000000..54a3c74a4b6 --- /dev/null +++ b/relays/bin-substrate/src/cli/init_bridge.rs @@ -0,0 +1,210 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use async_trait::async_trait; + +use crate::{ + chains::{ + millau_headers_to_rialto::MillauToRialtoCliBridge, + millau_headers_to_rialto_parachain::MillauToRialtoParachainCliBridge, + rialto_headers_to_millau::RialtoToMillauCliBridge, + rococo_headers_to_bridge_hub_wococo::RococoToBridgeHubWococoCliBridge, + westend_headers_to_millau::WestendToMillauCliBridge, + wococo_headers_to_bridge_hub_rococo::WococoToBridgeHubRococoCliBridge, + }, + cli::{bridge::CliBridgeBase, chain_schema::*}, +}; +use bp_runtime::Chain as ChainBase; +use relay_substrate_client::{AccountKeyPairOf, Chain, SignParam, UnsignedTransaction}; +use sp_core::Pair; +use structopt::StructOpt; +use strum::{EnumString, EnumVariantNames, VariantNames}; +use substrate_relay_helper::finality::engine::{Engine, Grandpa as GrandpaFinalityEngine}; + +/// Initialize bridge pallet. +#[derive(StructOpt)] +pub struct InitBridge { + /// A bridge instance to initialize. + #[structopt(possible_values = InitBridgeName::VARIANTS, case_insensitive = true)] + bridge: InitBridgeName, + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, +} + +#[derive(Debug, EnumString, EnumVariantNames)] +#[strum(serialize_all = "kebab_case")] +/// Bridge to initialize. +pub enum InitBridgeName { + MillauToRialto, + RialtoToMillau, + WestendToMillau, + MillauToRialtoParachain, + RococoToBridgeHubWococo, + WococoToBridgeHubRococo, +} + +#[async_trait] +trait BridgeInitializer: CliBridgeBase +where + ::AccountId: From< as Pair>::Public>, +{ + type Engine: Engine; + + /// Get the encoded call to init the bridge. + fn encode_init_bridge( + init_data: >::InitializationData, + ) -> ::Call; + + /// Initialize the bridge. + async fn init_bridge(data: InitBridge) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let target_client = data.target.into_client::().await?; + let target_sign = data.target_sign.to_keypair::()?; + + let (spec_version, transaction_version) = target_client.simple_runtime_version().await?; + substrate_relay_helper::finality::initialize::initialize::( + source_client, + target_client.clone(), + target_sign.public().into(), + SignParam { + spec_version, + transaction_version, + genesis_hash: *target_client.genesis_hash(), + signer: target_sign, + }, + move |transaction_nonce, initialization_data| { + Ok(UnsignedTransaction::new( + Self::encode_init_bridge(initialization_data).into(), + transaction_nonce, + )) + }, + ) + .await; + + Ok(()) + } +} + +impl BridgeInitializer for MillauToRialtoCliBridge { + type Engine = GrandpaFinalityEngine; + + fn encode_init_bridge( + init_data: >::InitializationData, + ) -> ::Call { + rialto_runtime::SudoCall::sudo { + call: Box::new(rialto_runtime::BridgeGrandpaCall::initialize { init_data }.into()), + } + .into() + } +} + +impl BridgeInitializer for MillauToRialtoParachainCliBridge { + type Engine = GrandpaFinalityEngine; + + fn encode_init_bridge( + init_data: >::InitializationData, + ) -> ::Call { + let initialize_call = rialto_parachain_runtime::BridgeGrandpaCall::< + rialto_parachain_runtime::Runtime, + rialto_parachain_runtime::MillauGrandpaInstance, + >::initialize { + init_data, + }; + rialto_parachain_runtime::SudoCall::sudo { call: Box::new(initialize_call.into()) }.into() + } +} + +impl BridgeInitializer for RialtoToMillauCliBridge { + type Engine = GrandpaFinalityEngine; + + fn encode_init_bridge( + init_data: >::InitializationData, + ) -> ::Call { + let initialize_call = millau_runtime::BridgeGrandpaCall::< + millau_runtime::Runtime, + millau_runtime::RialtoGrandpaInstance, + >::initialize { + init_data, + }; + millau_runtime::SudoCall::sudo { call: Box::new(initialize_call.into()) }.into() + } +} + +impl BridgeInitializer for WestendToMillauCliBridge { + type Engine = GrandpaFinalityEngine; + + fn encode_init_bridge( + init_data: >::InitializationData, + ) -> ::Call { + // at Westend -> Millau initialization we're not using sudo, because otherwise + // our deployments may fail, because we need to initialize both Rialto -> Millau + // and Westend -> Millau bridge. => since there's single possible sudo account, + // one of transaction may fail with duplicate nonce error + millau_runtime::BridgeGrandpaCall::< + millau_runtime::Runtime, + millau_runtime::WestendGrandpaInstance, + >::initialize { + init_data, + } + .into() + } +} + +impl BridgeInitializer for RococoToBridgeHubWococoCliBridge { + type Engine = GrandpaFinalityEngine; + + fn encode_init_bridge( + init_data: >::InitializationData, + ) -> ::Call { + relay_bridge_hub_wococo_client::runtime::Call::BridgeRococoGrandpa( + relay_bridge_hub_wococo_client::runtime::BridgeGrandpaRococoCall::initialize(init_data), + ) + } +} + +impl BridgeInitializer for WococoToBridgeHubRococoCliBridge { + type Engine = GrandpaFinalityEngine; + + fn encode_init_bridge( + init_data: >::InitializationData, + ) -> ::Call { + relay_bridge_hub_rococo_client::runtime::Call::BridgeWococoGrandpa( + relay_bridge_hub_rococo_client::runtime::BridgeWococoGrandpaCall::initialize(init_data), + ) + } +} + +impl InitBridge { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + match self.bridge { + InitBridgeName::MillauToRialto => MillauToRialtoCliBridge::init_bridge(self), + InitBridgeName::RialtoToMillau => RialtoToMillauCliBridge::init_bridge(self), + InitBridgeName::WestendToMillau => WestendToMillauCliBridge::init_bridge(self), + InitBridgeName::MillauToRialtoParachain => + MillauToRialtoParachainCliBridge::init_bridge(self), + InitBridgeName::RococoToBridgeHubWococo => + RococoToBridgeHubWococoCliBridge::init_bridge(self), + InitBridgeName::WococoToBridgeHubRococo => + WococoToBridgeHubRococoCliBridge::init_bridge(self), + } + .await + } +} diff --git a/relays/bin-substrate/src/cli/mod.rs b/relays/bin-substrate/src/cli/mod.rs new file mode 100644 index 00000000000..2086008bc95 --- /dev/null +++ b/relays/bin-substrate/src/cli/mod.rs @@ -0,0 +1,300 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Deal with CLI args of substrate-to-substrate relay. + +use std::convert::TryInto; + +use codec::{Decode, Encode}; +use structopt::{clap::arg_enum, StructOpt}; +use strum::{EnumString, EnumVariantNames}; + +use bp_messages::LaneId; + +pub(crate) mod bridge; +pub(crate) mod encode_message; +pub(crate) mod send_message; + +mod chain_schema; +mod init_bridge; +mod register_parachain; +mod relay_headers; +mod relay_headers_and_messages; +mod relay_messages; +mod relay_parachains; +mod resubmit_transactions; + +/// Parse relay CLI args. +pub fn parse_args() -> Command { + Command::from_args() +} + +/// Substrate-to-Substrate bridge utilities. +#[derive(StructOpt)] +#[structopt(about = "Substrate-to-Substrate relay")] +pub enum Command { + /// Start headers relay between two chains. + /// + /// The on-chain bridge component should have been already initialized with + /// `init-bridge` sub-command. + RelayHeaders(relay_headers::RelayHeaders), + /// Start messages relay between two chains. + /// + /// Ties up to `Messages` pallets on both chains and starts relaying messages. + /// Requires the header relay to be already running. + RelayMessages(relay_messages::RelayMessages), + /// Start headers and messages relay between two Substrate chains. + /// + /// This high-level relay internally starts four low-level relays: two `RelayHeaders` + /// and two `RelayMessages` relays. Headers are only relayed when they are required by + /// the message relays - i.e. when there are messages or confirmations that needs to be + /// relayed between chains. + RelayHeadersAndMessages(Box), + /// Initialize on-chain bridge pallet with current header data. + /// + /// Sends initialization transaction to bootstrap the bridge with current finalized block data. + InitBridge(init_bridge::InitBridge), + /// Send custom message over the bridge. + /// + /// Allows interacting with the bridge by sending messages over `Messages` component. + /// The message is being sent to the source chain, delivered to the target chain and dispatched + /// there. + SendMessage(send_message::SendMessage), + /// Resubmit transactions with increased tip if they are stalled. + ResubmitTransactions(resubmit_transactions::ResubmitTransactions), + /// Register parachain. + RegisterParachain(register_parachain::RegisterParachain), + /// + RelayParachains(relay_parachains::RelayParachains), +} + +impl Command { + // Initialize logger depending on the command. + fn init_logger(&self) { + use relay_utils::initialize::{initialize_logger, initialize_relay}; + + match self { + Self::RelayHeaders(_) | + Self::RelayMessages(_) | + Self::RelayHeadersAndMessages(_) | + Self::InitBridge(_) => { + initialize_relay(); + }, + _ => { + initialize_logger(false); + }, + } + } + + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + self.init_logger(); + match self { + Self::RelayHeaders(arg) => arg.run().await?, + Self::RelayMessages(arg) => arg.run().await?, + Self::RelayHeadersAndMessages(arg) => arg.run().await?, + Self::InitBridge(arg) => arg.run().await?, + Self::SendMessage(arg) => arg.run().await?, + Self::ResubmitTransactions(arg) => arg.run().await?, + Self::RegisterParachain(arg) => arg.run().await?, + Self::RelayParachains(arg) => arg.run().await?, + } + Ok(()) + } +} + +arg_enum! { + #[derive(Debug)] + /// The origin to use when dispatching the message on the target chain. + /// + /// - `Target` uses account existing on the target chain (requires target private key). + /// - `Origin` uses account derived from the source-chain account. + pub enum Origins { + Target, + Source, + } +} + +/// Generic balance type. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Balance(pub u128); + +impl std::fmt::Display for Balance { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + use num_format::{Locale, ToFormattedString}; + write!(fmt, "{}", self.0.to_formatted_string(&Locale::en)) + } +} + +impl std::str::FromStr for Balance { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse()?)) + } +} + +impl Balance { + /// Cast balance to `u64` type, panicking if it's too large. + pub fn cast(&self) -> u64 { + self.0.try_into().expect("Balance is too high for this chain.") + } +} + +// Bridge-supported network definition. +/// +/// Used to abstract away CLI commands. +pub trait CliChain: relay_substrate_client::Chain { + /// Current version of the chain runtime, known to relay. + /// + /// can be `None` if relay is not going to submit transactions to that chain. + const RUNTIME_VERSION: Option; + + /// Crypto KeyPair type used to send messages. + /// + /// In case of chains supporting multiple cryptos, pick one used by the CLI. + type KeyPair: sp_core::crypto::Pair; + + /// Numeric value of SS58 format. + fn ss58_format() -> u16; +} + +/// Lane id. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HexLaneId(pub LaneId); + +impl From for LaneId { + fn from(lane_id: HexLaneId) -> LaneId { + lane_id.0 + } +} + +impl std::str::FromStr for HexLaneId { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + let mut lane_id = LaneId::default(); + hex::decode_to_slice(s, &mut lane_id)?; + Ok(HexLaneId(lane_id)) + } +} + +/// Nicer formatting for raw bytes vectors. +#[derive(Default, Encode, Decode, PartialEq, Eq)] +pub struct HexBytes(pub Vec); + +impl std::str::FromStr for HexBytes { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + Ok(Self(hex::decode(s)?)) + } +} + +impl std::fmt::Debug for HexBytes { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "0x{self}") + } +} + +impl std::fmt::Display for HexBytes { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "{}", hex::encode(&self.0)) + } +} + +/// Prometheus metrics params. +#[derive(Clone, Debug, PartialEq, StructOpt)] +pub struct PrometheusParams { + /// Do not expose a Prometheus metric endpoint. + #[structopt(long)] + pub no_prometheus: bool, + /// Expose Prometheus endpoint at given interface. + #[structopt(long, default_value = "127.0.0.1")] + pub prometheus_host: String, + /// Expose Prometheus endpoint at given port. + #[structopt(long, default_value = "9616")] + pub prometheus_port: u16, +} + +impl From for relay_utils::metrics::MetricsParams { + fn from(cli_params: PrometheusParams) -> relay_utils::metrics::MetricsParams { + if !cli_params.no_prometheus { + Some(relay_utils::metrics::MetricsAddress { + host: cli_params.prometheus_host, + port: cli_params.prometheus_port, + }) + .into() + } else { + None.into() + } + } +} + +/// Either explicit or maximal allowed value. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExplicitOrMaximal { + /// User has explicitly specified argument value. + Explicit(V), + /// Maximal allowed value for this argument. + Maximal, +} + +impl std::str::FromStr for ExplicitOrMaximal +where + V::Err: std::fmt::Debug, +{ + type Err = String; + + fn from_str(s: &str) -> Result { + if s.to_lowercase() == "max" { + return Ok(ExplicitOrMaximal::Maximal) + } + + V::from_str(s) + .map(ExplicitOrMaximal::Explicit) + .map_err(|e| format!("Failed to parse '{e:?}'. Expected 'max' or explicit value")) + } +} + +#[doc = "Runtime version params."] +#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy, EnumString, EnumVariantNames)] +pub enum RuntimeVersionType { + /// Auto query version from chain + Auto, + /// Custom `spec_version` and `transaction_version` + Custom, + /// Read version from bundle dependencies directly. + Bundle, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hex_bytes_display_matches_from_str_for_clap() { + // given + let hex = HexBytes(vec![1, 2, 3, 4]); + let display = format!("{hex}"); + + // when + let hex2: HexBytes = display.parse().unwrap(); + + // then + assert_eq!(hex.0, hex2.0); + } +} diff --git a/relays/bin-substrate/src/cli/register_parachain.rs b/relays/bin-substrate/src/cli/register_parachain.rs new file mode 100644 index 00000000000..4febc4b04a8 --- /dev/null +++ b/relays/bin-substrate/src/cli/register_parachain.rs @@ -0,0 +1,359 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::cli::{chain_schema::*, Balance}; + +use codec::Encode; +use frame_support::Twox64Concat; +use num_traits::Zero; +use polkadot_parachain::primitives::{ + HeadData as ParaHeadData, Id as ParaId, ValidationCode as ParaValidationCode, +}; +use polkadot_runtime_common::{ + paras_registrar::Call as ParaRegistrarCall, slots::Call as ParaSlotsCall, +}; +use polkadot_runtime_parachains::paras::ParaLifecycle; +use relay_substrate_client::{AccountIdOf, CallOf, Chain, Client, SignParam, UnsignedTransaction}; +use relay_utils::{TrackedTransactionStatus, TransactionTracker}; +use rialto_runtime::SudoCall; +use sp_core::{ + storage::{well_known_keys::CODE, StorageKey}, + Pair, +}; +use structopt::StructOpt; +use strum::{EnumString, EnumVariantNames, VariantNames}; + +/// Name of the `NextFreeParaId` value in the `polkadot_runtime_common::paras_registrar` pallet. +const NEXT_FREE_PARA_ID_STORAGE_NAME: &str = "NextFreeParaId"; +/// Name of the `ParaLifecycles` map in the `polkadot_runtime_parachains::paras` pallet. +const PARAS_LIFECYCLES_STORAGE_NAME: &str = "ParaLifecycles"; + +/// Register parachain. +#[derive(StructOpt, Debug, PartialEq, Eq)] +pub struct RegisterParachain { + /// A parachain to register. + #[structopt(possible_values = Parachain::VARIANTS, case_insensitive = true)] + parachain: Parachain, + /// Parachain deposit. + #[structopt(long, default_value = "0")] + deposit: Balance, + /// Lease begin. + #[structopt(long, default_value = "0")] + lease_begin: u32, + /// Lease end. + #[structopt(long, default_value = "256")] + lease_end: u32, + #[structopt(flatten)] + relay_connection: RelaychainConnectionParams, + #[structopt(flatten)] + relay_sign: RelaychainSigningParams, + #[structopt(flatten)] + para_connection: ParachainConnectionParams, +} + +/// Parachain to register. +#[derive(Debug, EnumString, EnumVariantNames, PartialEq, Eq)] +#[strum(serialize_all = "kebab_case")] +pub enum Parachain { + RialtoParachain, +} + +macro_rules! select_bridge { + ($bridge: expr, $generic: tt) => { + match $bridge { + Parachain::RialtoParachain => { + type Relaychain = relay_rialto_client::Rialto; + type Parachain = relay_rialto_parachain_client::RialtoParachain; + + use bp_rialto::{PARAS_PALLET_NAME, PARAS_REGISTRAR_PALLET_NAME}; + + $generic + }, + } + }; +} + +impl RegisterParachain { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + select_bridge!(self.parachain, { + let relay_client = self.relay_connection.into_client::().await?; + let relay_sign = self.relay_sign.to_keypair::()?; + let para_client = self.para_connection.into_client::().await?; + + // hopefully we're the only actor that is registering parachain right now + // => read next parachain id + let para_id_key = bp_runtime::storage_value_final_key( + PARAS_REGISTRAR_PALLET_NAME.as_bytes(), + NEXT_FREE_PARA_ID_STORAGE_NAME.as_bytes(), + ); + let para_id: ParaId = relay_client + .storage_value(StorageKey(para_id_key.to_vec()), None) + .await? + .unwrap_or(polkadot_primitives::v2::LOWEST_PUBLIC_ID) + .max(polkadot_primitives::v2::LOWEST_PUBLIC_ID); + log::info!(target: "bridge", "Going to reserve parachain id: {:?}", para_id); + + // step 1: reserve a parachain id + let relay_genesis_hash = *relay_client.genesis_hash(); + let relay_sudo_account: AccountIdOf = relay_sign.public().into(); + let reserve_parachain_id_call: CallOf = + ParaRegistrarCall::reserve {}.into(); + let reserve_parachain_signer = relay_sign.clone(); + let (spec_version, transaction_version) = relay_client.simple_runtime_version().await?; + let reserve_result = relay_client + .submit_and_watch_signed_extrinsic( + relay_sudo_account.clone(), + SignParam:: { + spec_version, + transaction_version, + genesis_hash: relay_genesis_hash, + signer: reserve_parachain_signer, + }, + move |_, transaction_nonce| { + Ok(UnsignedTransaction::new( + reserve_parachain_id_call.into(), + transaction_nonce, + )) + }, + ) + .await? + .wait() + .await; + if reserve_result == TrackedTransactionStatus::Lost { + return Err(anyhow::format_err!( + "Failed to finalize `reserve-parachain-id` transaction" + )) + } + log::info!(target: "bridge", "Reserved parachain id: {:?}", para_id); + + // step 2: register parathread + let para_genesis_header = para_client.header_by_number(Zero::zero()).await?; + let para_code = para_client + .raw_storage_value(StorageKey(CODE.to_vec()), Some(para_genesis_header.hash())) + .await? + .ok_or_else(|| { + anyhow::format_err!("Cannot fetch validation code of {}", Parachain::NAME) + })? + .0; + log::info!( + target: "bridge", + "Going to register parachain {:?}: genesis len = {} code len = {}", + para_id, + para_genesis_header.encode().len(), + para_code.len(), + ); + let register_parathread_call: CallOf = ParaRegistrarCall::register { + id: para_id, + genesis_head: ParaHeadData(para_genesis_header.encode()), + validation_code: ParaValidationCode(para_code), + } + .into(); + let register_parathread_signer = relay_sign.clone(); + let register_result = relay_client + .submit_and_watch_signed_extrinsic( + relay_sudo_account.clone(), + SignParam:: { + spec_version, + transaction_version, + genesis_hash: relay_genesis_hash, + signer: register_parathread_signer, + }, + move |_, transaction_nonce| { + Ok(UnsignedTransaction::new( + register_parathread_call.into(), + transaction_nonce, + )) + }, + ) + .await? + .wait() + .await; + if register_result == TrackedTransactionStatus::Lost { + return Err(anyhow::format_err!( + "Failed to finalize `register-parathread` transaction" + )) + } + log::info!(target: "bridge", "Registered parachain: {:?}. Waiting for onboarding", para_id); + + // wait until parathread is onboarded + let para_state_key = bp_runtime::storage_map_final_key::( + PARAS_PALLET_NAME, + PARAS_LIFECYCLES_STORAGE_NAME, + ¶_id.encode(), + ); + wait_para_state( + &relay_client, + ¶_state_key.0, + &[ParaLifecycle::Onboarding, ParaLifecycle::Parathread], + ParaLifecycle::Parathread, + ) + .await?; + + // step 3: force parachain leases + let lease_begin = self.lease_begin; + let lease_end = self.lease_end; + let para_deposit = self.deposit.cast().into(); + log::info!( + target: "bridge", + "Going to force leases of parachain {:?}: [{}; {}]", + para_id, + lease_begin, + lease_end, + ); + let force_lease_call: CallOf = SudoCall::sudo { + call: Box::new( + ParaSlotsCall::force_lease { + para: para_id, + leaser: relay_sudo_account.clone(), + amount: para_deposit, + period_begin: lease_begin, + period_count: lease_end.saturating_sub(lease_begin).saturating_add(1), + } + .into(), + ), + } + .into(); + let force_lease_signer = relay_sign.clone(); + relay_client + .submit_signed_extrinsic( + relay_sudo_account, + SignParam:: { + spec_version, + transaction_version, + genesis_hash: relay_genesis_hash, + signer: force_lease_signer, + }, + move |_, transaction_nonce| { + Ok(UnsignedTransaction::new(force_lease_call.into(), transaction_nonce)) + }, + ) + .await?; + log::info!(target: "bridge", "Registered parachain leases: {:?}. Waiting for onboarding", para_id); + + // wait until parachain is onboarded + wait_para_state( + &relay_client, + ¶_state_key.0, + &[ + ParaLifecycle::Onboarding, + ParaLifecycle::UpgradingParathread, + ParaLifecycle::Parathread, + ], + ParaLifecycle::Parachain, + ) + .await?; + + Ok(()) + }) + } +} + +/// Wait until parachain state is changed. +async fn wait_para_state( + relay_client: &Client, + para_state_key: &[u8], + from_states: &[ParaLifecycle], + to_state: ParaLifecycle, +) -> anyhow::Result<()> { + loop { + let para_state: ParaLifecycle = relay_client + .storage_value(StorageKey(para_state_key.to_vec()), None) + .await? + .ok_or_else(|| { + anyhow::format_err!( + "Cannot fetch next free parachain lifecycle from the runtime storage of {}", + Relaychain::NAME, + ) + })?; + if para_state == to_state { + log::info!(target: "bridge", "Parachain state is now: {:?}", to_state); + return Ok(()) + } + if !from_states.contains(¶_state) { + return Err(anyhow::format_err!("Invalid parachain lifecycle: {:?}", para_state)) + } + + log::info!(target: "bridge", "Parachain state: {:?}. Waiting for {:?}", para_state, to_state); + async_std::task::sleep(Relaychain::AVERAGE_BLOCK_INTERVAL).await; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn register_rialto_parachain() { + let register_parachain = RegisterParachain::from_iter(vec![ + "register-parachain", + "rialto-parachain", + "--parachain-host", + "127.0.0.1", + "--parachain-port", + "11949", + "--relaychain-host", + "127.0.0.1", + "--relaychain-port", + "9944", + "--relaychain-signer", + "//Alice", + "--deposit", + "42", + "--lease-begin", + "100", + "--lease-end", + "200", + ]); + + assert_eq!( + register_parachain, + RegisterParachain { + parachain: Parachain::RialtoParachain, + deposit: Balance(42), + lease_begin: 100, + lease_end: 200, + relay_connection: RelaychainConnectionParams { + relaychain_host: "127.0.0.1".into(), + relaychain_port: 9944, + relaychain_secure: false, + relaychain_runtime_version: RelaychainRuntimeVersionParams { + relaychain_version_mode: RuntimeVersionType::Bundle, + relaychain_spec_version: None, + relaychain_transaction_version: None, + } + }, + relay_sign: RelaychainSigningParams { + relaychain_signer: Some("//Alice".into()), + relaychain_signer_password: None, + relaychain_signer_file: None, + relaychain_signer_password_file: None, + relaychain_transactions_mortality: None, + }, + para_connection: ParachainConnectionParams { + parachain_host: "127.0.0.1".into(), + parachain_port: 11949, + parachain_secure: false, + parachain_runtime_version: ParachainRuntimeVersionParams { + parachain_version_mode: RuntimeVersionType::Bundle, + parachain_spec_version: None, + parachain_transaction_version: None, + } + }, + } + ); + } +} diff --git a/relays/bin-substrate/src/cli/relay_headers.rs b/relays/bin-substrate/src/cli/relay_headers.rs new file mode 100644 index 00000000000..37b805d720b --- /dev/null +++ b/relays/bin-substrate/src/cli/relay_headers.rs @@ -0,0 +1,128 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use async_trait::async_trait; +use relay_substrate_client::{AccountIdOf, AccountKeyPairOf}; +use sp_core::Pair; +use structopt::StructOpt; +use strum::{EnumString, EnumVariantNames, VariantNames}; + +use crate::chains::{ + millau_headers_to_rialto::MillauToRialtoCliBridge, + millau_headers_to_rialto_parachain::MillauToRialtoParachainCliBridge, + rialto_headers_to_millau::RialtoToMillauCliBridge, + rococo_headers_to_bridge_hub_wococo::RococoToBridgeHubWococoCliBridge, + westend_headers_to_millau::WestendToMillauCliBridge, + wococo_headers_to_bridge_hub_rococo::WococoToBridgeHubRococoCliBridge, +}; +use relay_utils::metrics::{GlobalMetrics, StandaloneMetric}; +use substrate_relay_helper::finality::SubstrateFinalitySyncPipeline; + +use crate::cli::{bridge::*, chain_schema::*, PrometheusParams}; + +/// Start headers relayer process. +#[derive(StructOpt)] +pub struct RelayHeaders { + /// A bridge instance to relay headers for. + #[structopt(possible_values = RelayHeadersBridge::VARIANTS, case_insensitive = true)] + bridge: RelayHeadersBridge, + /// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set) + /// are relayed. + #[structopt(long)] + only_mandatory_headers: bool, + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + #[structopt(flatten)] + prometheus_params: PrometheusParams, +} + +#[derive(Debug, EnumString, EnumVariantNames)] +#[strum(serialize_all = "kebab_case")] +/// Headers relay bridge. +pub enum RelayHeadersBridge { + MillauToRialto, + RialtoToMillau, + WestendToMillau, + MillauToRialtoParachain, + RococoToBridgeHubWococo, + WococoToBridgeHubRococo, +} + +#[async_trait] +trait HeadersRelayer: RelayToRelayHeadersCliBridge +where + AccountIdOf: From< as Pair>::Public>, +{ + /// Relay headers. + async fn relay_headers(data: RelayHeaders) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let target_client = data.target.into_client::().await?; + let target_transactions_mortality = data.target_sign.target_transactions_mortality; + let target_sign = data.target_sign.to_keypair::()?; + + let metrics_params: relay_utils::metrics::MetricsParams = data.prometheus_params.into(); + GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?; + + let target_transactions_params = substrate_relay_helper::TransactionParams { + signer: target_sign, + mortality: target_transactions_mortality, + }; + Self::Finality::start_relay_guards( + &target_client, + &target_transactions_params, + target_client.can_start_version_guard(), + ) + .await?; + + substrate_relay_helper::finality::run::( + source_client, + target_client, + data.only_mandatory_headers, + target_transactions_params, + metrics_params, + ) + .await + } +} + +impl HeadersRelayer for MillauToRialtoCliBridge {} +impl HeadersRelayer for RialtoToMillauCliBridge {} +impl HeadersRelayer for WestendToMillauCliBridge {} +impl HeadersRelayer for MillauToRialtoParachainCliBridge {} +impl HeadersRelayer for RococoToBridgeHubWococoCliBridge {} +impl HeadersRelayer for WococoToBridgeHubRococoCliBridge {} + +impl RelayHeaders { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + match self.bridge { + RelayHeadersBridge::MillauToRialto => MillauToRialtoCliBridge::relay_headers(self), + RelayHeadersBridge::RialtoToMillau => RialtoToMillauCliBridge::relay_headers(self), + RelayHeadersBridge::WestendToMillau => WestendToMillauCliBridge::relay_headers(self), + RelayHeadersBridge::MillauToRialtoParachain => + MillauToRialtoParachainCliBridge::relay_headers(self), + RelayHeadersBridge::RococoToBridgeHubWococo => + RococoToBridgeHubWococoCliBridge::relay_headers(self), + RelayHeadersBridge::WococoToBridgeHubRococo => + WococoToBridgeHubRococoCliBridge::relay_headers(self), + } + .await + } +} diff --git a/relays/bin-substrate/src/cli/relay_headers_and_messages/mod.rs b/relays/bin-substrate/src/cli/relay_headers_and_messages/mod.rs new file mode 100644 index 00000000000..bdc92f984da --- /dev/null +++ b/relays/bin-substrate/src/cli/relay_headers_and_messages/mod.rs @@ -0,0 +1,682 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Complex 2-ways headers+messages relays support. +//! +//! To add new complex relay between `ChainA` and `ChainB`, you must: +//! +//! 1) ensure that there's a `declare_chain_cli_schema!(...)` for both chains. +//! 2) add `declare_chain_to_chain_bridge_schema!(...)` or +//! `declare_chain_to_parachain_bridge_schema` for the bridge. +//! 3) declare a new struct for the added bridge and implement the `Full2WayBridge` trait for it. + +#[macro_use] +mod parachain_to_parachain; +#[macro_use] +mod relay_to_relay; +#[macro_use] +mod relay_to_parachain; + +use async_trait::async_trait; +use std::{marker::PhantomData, sync::Arc}; +use structopt::StructOpt; + +use futures::{FutureExt, TryFutureExt}; +use relay_to_parachain::*; +use relay_to_relay::*; + +use crate::{ + chains::{ + millau_headers_to_rialto::MillauToRialtoCliBridge, + millau_headers_to_rialto_parachain::MillauToRialtoParachainCliBridge, + rialto_headers_to_millau::RialtoToMillauCliBridge, + rialto_parachains_to_millau::RialtoParachainToMillauCliBridge, + }, + cli::{ + bridge::{ + CliBridgeBase, MessagesCliBridge, ParachainToRelayHeadersCliBridge, + RelayToRelayHeadersCliBridge, + }, + chain_schema::*, + CliChain, HexLaneId, PrometheusParams, + }, + declare_chain_cli_schema, +}; +use bp_messages::LaneId; +use bp_runtime::{BalanceOf, BlockNumberOf}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, Chain, ChainWithBalances, ChainWithTransactions, Client, +}; +use relay_utils::metrics::MetricsParams; +use sp_core::Pair; +use substrate_relay_helper::{ + messages_lane::MessagesRelayParams, on_demand::OnDemandRelay, TaggedAccount, TransactionParams, +}; + +/// Parameters that have the same names across all bridges. +#[derive(Debug, PartialEq, StructOpt)] +pub struct HeadersAndMessagesSharedParams { + /// Hex-encoded lane identifiers that should be served by the complex relay. + #[structopt(long, default_value = "00000000")] + pub lane: Vec, + /// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set) + /// are relayed. + #[structopt(long)] + pub only_mandatory_headers: bool, + #[structopt(flatten)] + pub prometheus_params: PrometheusParams, +} + +/// Bridge parameters, shared by all bridge types. +pub struct Full2WayBridgeCommonParams< + Left: ChainWithTransactions + CliChain, + Right: ChainWithTransactions + CliChain, +> { + /// Shared parameters. + pub shared: HeadersAndMessagesSharedParams, + /// Parameters of the left chain. + pub left: BridgeEndCommonParams, + /// Parameters of the right chain. + pub right: BridgeEndCommonParams, + + /// Common metric parameters. + pub metrics_params: MetricsParams, +} + +impl + Full2WayBridgeCommonParams +{ + /// Creates new bridge parameters from its components. + pub fn new>( + shared: HeadersAndMessagesSharedParams, + left: BridgeEndCommonParams, + right: BridgeEndCommonParams, + ) -> anyhow::Result { + // Create metrics registry. + let metrics_params = shared.prometheus_params.clone().into(); + let metrics_params = relay_utils::relay_metrics(metrics_params).into_params(); + + Ok(Self { shared, left, right, metrics_params }) + } +} + +/// Parameters that are associated with one side of the bridge. +pub struct BridgeEndCommonParams { + /// Chain client. + pub client: Client, + /// Transactions signer. + pub sign: AccountKeyPairOf, + /// Transactions mortality. + pub transactions_mortality: Option, + /// Account that "owns" messages pallet. + pub messages_pallet_owner: Option>, + /// Accounts, which balances are exposed as metrics by the relay process. + pub accounts: Vec>>, +} + +/// All data of the bidirectional complex relay. +struct FullBridge< + 'a, + Source: ChainWithTransactions + CliChain, + Target: ChainWithTransactions + CliChain, + Bridge: MessagesCliBridge, +> { + source: &'a mut BridgeEndCommonParams, + target: &'a mut BridgeEndCommonParams, + metrics_params: &'a MetricsParams, + _phantom_data: PhantomData, +} + +impl< + 'a, + Source: ChainWithTransactions + CliChain, + Target: ChainWithTransactions + CliChain, + Bridge: MessagesCliBridge, + > FullBridge<'a, Source, Target, Bridge> +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom> + Into, +{ + /// Construct complex relay given it components. + fn new( + source: &'a mut BridgeEndCommonParams, + target: &'a mut BridgeEndCommonParams, + metrics_params: &'a MetricsParams, + ) -> Self { + Self { source, target, metrics_params, _phantom_data: Default::default() } + } + + /// Returns message relay parameters. + fn messages_relay_params( + &self, + source_to_target_headers_relay: Arc>>, + target_to_source_headers_relay: Arc>>, + lane_id: LaneId, + ) -> MessagesRelayParams { + MessagesRelayParams { + source_client: self.source.client.clone(), + source_transaction_params: TransactionParams { + signer: self.source.sign.clone(), + mortality: self.source.transactions_mortality, + }, + target_client: self.target.client.clone(), + target_transaction_params: TransactionParams { + signer: self.target.sign.clone(), + mortality: self.target.transactions_mortality, + }, + source_to_target_headers_relay: Some(source_to_target_headers_relay), + target_to_source_headers_relay: Some(target_to_source_headers_relay), + lane_id, + metrics_params: self.metrics_params.clone().disable(), + } + } +} + +// All supported chains. +declare_chain_cli_schema!(Millau, millau); +declare_chain_cli_schema!(Rialto, rialto); +declare_chain_cli_schema!(RialtoParachain, rialto_parachain); +// Means to override signers of different layer transactions. +declare_chain_cli_schema!(MillauHeadersToRialto, millau_headers_to_rialto); +declare_chain_cli_schema!(MillauHeadersToRialtoParachain, millau_headers_to_rialto_parachain); +declare_chain_cli_schema!(RialtoHeadersToMillau, rialto_headers_to_millau); +declare_chain_cli_schema!(RialtoParachainsToMillau, rialto_parachains_to_millau); +// All supported bridges. +declare_relay_to_relay_bridge_schema!(Millau, Rialto); +declare_relay_to_parachain_bridge_schema!(Millau, RialtoParachain, Rialto); + +/// Base portion of the bidirectional complex relay. +/// +/// This main purpose of extracting this trait is that in different relays the implementation +/// of `start_on_demand_headers_relayers` method will be different. But the number of +/// implementations is limited to relay <> relay, parachain <> relay and parachain <> parachain. +/// This trait allows us to reuse these implementations in different bridges. +#[async_trait] +trait Full2WayBridgeBase: Sized + Send + Sync { + /// The CLI params for the bridge. + type Params; + /// The left relay chain. + type Left: ChainWithTransactions + CliChain>; + /// The right destination chain (it can be a relay or a parachain). + type Right: ChainWithTransactions + CliChain>; + + /// Reference to common relay parameters. + fn common(&self) -> &Full2WayBridgeCommonParams; + + /// Mutable reference to common relay parameters. + fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams; + + /// Start on-demand headers relays. + async fn start_on_demand_headers_relayers( + &mut self, + ) -> anyhow::Result<( + Arc>>, + Arc>>, + )>; +} + +/// Bidirectional complex relay. +#[async_trait] +trait Full2WayBridge: Sized + Sync +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom> + Into, + BalanceOf: TryFrom> + Into, +{ + /// Base portion of the bidirectional complex relay. + type Base: Full2WayBridgeBase; + + /// The left relay chain. + type Left: ChainWithTransactions + + ChainWithBalances + + CliChain>; + /// The right relay chain. + type Right: ChainWithTransactions + + ChainWithBalances + + CliChain>; + + /// Left to Right bridge. + type L2R: MessagesCliBridge; + /// Right to Left bridge + type R2L: MessagesCliBridge; + + /// Construct new bridge. + fn new(params: ::Params) -> anyhow::Result; + + /// Reference to the base relay portion. + fn base(&self) -> &Self::Base; + + /// Mutable reference to the base relay portion. + fn mut_base(&mut self) -> &mut Self::Base; + + /// Creates and returns Left to Right complex relay. + fn left_to_right(&mut self) -> FullBridge { + let common = self.mut_base().mut_common(); + FullBridge::<_, _, Self::L2R>::new( + &mut common.left, + &mut common.right, + &common.metrics_params, + ) + } + + /// Creates and returns Right to Left complex relay. + fn right_to_left(&mut self) -> FullBridge { + let common = self.mut_base().mut_common(); + FullBridge::<_, _, Self::R2L>::new( + &mut common.right, + &mut common.left, + &common.metrics_params, + ) + } + + /// Start complex relay. + async fn run(&mut self) -> anyhow::Result<()> { + // Register standalone metrics. + { + let common = self.mut_base().mut_common(); + common.left.accounts.push(TaggedAccount::Messages { + id: common.left.sign.public().into(), + bridged_chain: Self::Right::NAME.to_string(), + }); + common.right.accounts.push(TaggedAccount::Messages { + id: common.right.sign.public().into(), + bridged_chain: Self::Left::NAME.to_string(), + }); + } + + // start on-demand header relays + let (left_to_right_on_demand_headers, right_to_left_on_demand_headers) = + self.mut_base().start_on_demand_headers_relayers().await?; + + // add balance-related metrics + { + let common = self.mut_base().mut_common(); + substrate_relay_helper::messages_metrics::add_relay_balances_metrics( + common.left.client.clone(), + &mut common.metrics_params, + &common.left.accounts, + ) + .await?; + substrate_relay_helper::messages_metrics::add_relay_balances_metrics( + common.right.client.clone(), + &mut common.metrics_params, + &common.right.accounts, + ) + .await?; + } + + let lanes = self.base().common().shared.lane.clone(); + // Need 2x capacity since we consider both directions for each lane + let mut message_relays = Vec::with_capacity(lanes.len() * 2); + for lane in lanes { + let lane = lane.into(); + + let left_to_right_messages = substrate_relay_helper::messages_lane::run::< + ::MessagesLane, + >(self.left_to_right().messages_relay_params( + left_to_right_on_demand_headers.clone(), + right_to_left_on_demand_headers.clone(), + lane, + )) + .map_err(|e| anyhow::format_err!("{}", e)) + .boxed(); + message_relays.push(left_to_right_messages); + + let right_to_left_messages = substrate_relay_helper::messages_lane::run::< + ::MessagesLane, + >(self.right_to_left().messages_relay_params( + right_to_left_on_demand_headers.clone(), + left_to_right_on_demand_headers.clone(), + lane, + )) + .map_err(|e| anyhow::format_err!("{}", e)) + .boxed(); + message_relays.push(right_to_left_messages); + } + + relay_utils::relay_metrics(self.base().common().metrics_params.clone()) + .expose() + .await + .map_err(|e| anyhow::format_err!("{}", e))?; + + futures::future::select_all(message_relays).await.0 + } +} + +/// Millau <> Rialto complex relay. +pub struct MillauRialtoFull2WayBridge { + base: ::Base, +} + +#[async_trait] +impl Full2WayBridge for MillauRialtoFull2WayBridge { + type Base = RelayToRelayBridge; + type Left = relay_millau_client::Millau; + type Right = relay_rialto_client::Rialto; + type L2R = MillauToRialtoCliBridge; + type R2L = RialtoToMillauCliBridge; + + fn new(base: Self::Base) -> anyhow::Result { + Ok(Self { base }) + } + + fn base(&self) -> &Self::Base { + &self.base + } + + fn mut_base(&mut self) -> &mut Self::Base { + &mut self.base + } +} + +/// Millau <> RialtoParachain complex relay. +pub struct MillauRialtoParachainFull2WayBridge { + base: ::Base, +} + +#[async_trait] +impl Full2WayBridge for MillauRialtoParachainFull2WayBridge { + type Base = RelayToParachainBridge; + type Left = relay_millau_client::Millau; + type Right = relay_rialto_parachain_client::RialtoParachain; + type L2R = MillauToRialtoParachainCliBridge; + type R2L = RialtoParachainToMillauCliBridge; + + fn new(base: Self::Base) -> anyhow::Result { + Ok(Self { base }) + } + + fn base(&self) -> &Self::Base { + &self.base + } + + fn mut_base(&mut self) -> &mut Self::Base { + &mut self.base + } +} + +/// Complex headers+messages relay. +#[derive(Debug, PartialEq, StructOpt)] +pub enum RelayHeadersAndMessages { + /// Millau <> Rialto relay. + MillauRialto(MillauRialtoHeadersAndMessages), + /// Millau <> RialtoParachain relay. + MillauRialtoParachain(MillauRialtoParachainHeadersAndMessages), +} + +impl RelayHeadersAndMessages { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + match self { + RelayHeadersAndMessages::MillauRialto(params) => + MillauRialtoFull2WayBridge::new(params.into_bridge().await?)?.run().await, + RelayHeadersAndMessages::MillauRialtoParachain(params) => + MillauRialtoParachainFull2WayBridge::new(params.into_bridge().await?)? + .run() + .await, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_parse_relay_to_relay_options() { + // when + let res = RelayHeadersAndMessages::from_iter(vec![ + "relay-headers-and-messages", + "millau-rialto", + "--millau-host", + "millau-node-alice", + "--millau-port", + "9944", + "--millau-signer", + "//Charlie", + "--millau-messages-pallet-owner", + "//RialtoMessagesOwner", + "--millau-transactions-mortality", + "64", + "--rialto-host", + "rialto-node-alice", + "--rialto-port", + "9944", + "--rialto-signer", + "//Charlie", + "--rialto-messages-pallet-owner", + "//MillauMessagesOwner", + "--rialto-transactions-mortality", + "64", + "--lane", + "00000000", + "--lane", + "73776170", + "--prometheus-host", + "0.0.0.0", + ]); + + // then + assert_eq!( + res, + RelayHeadersAndMessages::MillauRialto(MillauRialtoHeadersAndMessages { + shared: HeadersAndMessagesSharedParams { + lane: vec![ + HexLaneId([0x00, 0x00, 0x00, 0x00]), + HexLaneId([0x73, 0x77, 0x61, 0x70]) + ], + only_mandatory_headers: false, + prometheus_params: PrometheusParams { + no_prometheus: false, + prometheus_host: "0.0.0.0".into(), + prometheus_port: 9616, + }, + }, + left: MillauConnectionParams { + millau_host: "millau-node-alice".into(), + millau_port: 9944, + millau_secure: false, + millau_runtime_version: MillauRuntimeVersionParams { + millau_version_mode: RuntimeVersionType::Bundle, + millau_spec_version: None, + millau_transaction_version: None, + }, + }, + left_sign: MillauSigningParams { + millau_signer: Some("//Charlie".into()), + millau_signer_password: None, + millau_signer_file: None, + millau_signer_password_file: None, + millau_transactions_mortality: Some(64), + }, + left_messages_pallet_owner: MillauMessagesPalletOwnerSigningParams { + millau_messages_pallet_owner: Some("//RialtoMessagesOwner".into()), + millau_messages_pallet_owner_password: None, + }, + left_headers_to_right_sign_override: MillauHeadersToRialtoSigningParams { + millau_headers_to_rialto_signer: None, + millau_headers_to_rialto_signer_password: None, + millau_headers_to_rialto_signer_file: None, + millau_headers_to_rialto_signer_password_file: None, + millau_headers_to_rialto_transactions_mortality: None, + }, + right: RialtoConnectionParams { + rialto_host: "rialto-node-alice".into(), + rialto_port: 9944, + rialto_secure: false, + rialto_runtime_version: RialtoRuntimeVersionParams { + rialto_version_mode: RuntimeVersionType::Bundle, + rialto_spec_version: None, + rialto_transaction_version: None, + }, + }, + right_sign: RialtoSigningParams { + rialto_signer: Some("//Charlie".into()), + rialto_signer_password: None, + rialto_signer_file: None, + rialto_signer_password_file: None, + rialto_transactions_mortality: Some(64), + }, + right_messages_pallet_owner: RialtoMessagesPalletOwnerSigningParams { + rialto_messages_pallet_owner: Some("//MillauMessagesOwner".into()), + rialto_messages_pallet_owner_password: None, + }, + right_headers_to_left_sign_override: RialtoHeadersToMillauSigningParams { + rialto_headers_to_millau_signer: None, + rialto_headers_to_millau_signer_password: None, + rialto_headers_to_millau_signer_file: None, + rialto_headers_to_millau_signer_password_file: None, + rialto_headers_to_millau_transactions_mortality: None, + }, + }), + ); + } + + #[test] + fn should_parse_relay_to_parachain_options() { + // when + let res = RelayHeadersAndMessages::from_iter(vec![ + "relay-headers-and-messages", + "millau-rialto-parachain", + "--millau-host", + "millau-node-alice", + "--millau-port", + "9944", + "--millau-signer", + "//Iden", + "--rialto-headers-to-millau-signer", + "//Ken", + "--millau-messages-pallet-owner", + "//RialtoParachainMessagesOwner", + "--millau-transactions-mortality", + "64", + "--rialto-parachain-host", + "rialto-parachain-collator-charlie", + "--rialto-parachain-port", + "9944", + "--rialto-parachain-signer", + "//George", + "--rialto-parachain-messages-pallet-owner", + "//MillauMessagesOwner", + "--rialto-parachain-transactions-mortality", + "64", + "--rialto-host", + "rialto-node-alice", + "--rialto-port", + "9944", + "--lane", + "00000000", + "--prometheus-host", + "0.0.0.0", + ]); + + // then + assert_eq!( + res, + RelayHeadersAndMessages::MillauRialtoParachain( + MillauRialtoParachainHeadersAndMessages { + shared: HeadersAndMessagesSharedParams { + lane: vec![HexLaneId([0x00, 0x00, 0x00, 0x00])], + only_mandatory_headers: false, + prometheus_params: PrometheusParams { + no_prometheus: false, + prometheus_host: "0.0.0.0".into(), + prometheus_port: 9616, + }, + }, + left: MillauConnectionParams { + millau_host: "millau-node-alice".into(), + millau_port: 9944, + millau_secure: false, + millau_runtime_version: MillauRuntimeVersionParams { + millau_version_mode: RuntimeVersionType::Bundle, + millau_spec_version: None, + millau_transaction_version: None, + }, + }, + left_sign: MillauSigningParams { + millau_signer: Some("//Iden".into()), + millau_signer_password: None, + millau_signer_file: None, + millau_signer_password_file: None, + millau_transactions_mortality: Some(64), + }, + left_messages_pallet_owner: MillauMessagesPalletOwnerSigningParams { + millau_messages_pallet_owner: Some("//RialtoParachainMessagesOwner".into()), + millau_messages_pallet_owner_password: None, + }, + left_headers_to_right_sign_override: + MillauHeadersToRialtoParachainSigningParams { + millau_headers_to_rialto_parachain_signer: None, + millau_headers_to_rialto_parachain_signer_password: None, + millau_headers_to_rialto_parachain_signer_file: None, + millau_headers_to_rialto_parachain_signer_password_file: None, + millau_headers_to_rialto_parachain_transactions_mortality: None, + }, + right: RialtoParachainConnectionParams { + rialto_parachain_host: "rialto-parachain-collator-charlie".into(), + rialto_parachain_port: 9944, + rialto_parachain_secure: false, + rialto_parachain_runtime_version: RialtoParachainRuntimeVersionParams { + rialto_parachain_version_mode: RuntimeVersionType::Bundle, + rialto_parachain_spec_version: None, + rialto_parachain_transaction_version: None, + }, + }, + right_sign: RialtoParachainSigningParams { + rialto_parachain_signer: Some("//George".into()), + rialto_parachain_signer_password: None, + rialto_parachain_signer_file: None, + rialto_parachain_signer_password_file: None, + rialto_parachain_transactions_mortality: Some(64), + }, + right_messages_pallet_owner: RialtoParachainMessagesPalletOwnerSigningParams { + rialto_parachain_messages_pallet_owner: Some( + "//MillauMessagesOwner".into() + ), + rialto_parachain_messages_pallet_owner_password: None, + }, + right_relay_headers_to_left_sign_override: RialtoHeadersToMillauSigningParams { + rialto_headers_to_millau_signer: Some("//Ken".into()), + rialto_headers_to_millau_signer_password: None, + rialto_headers_to_millau_signer_file: None, + rialto_headers_to_millau_signer_password_file: None, + rialto_headers_to_millau_transactions_mortality: None, + }, + right_parachains_to_left_sign_override: RialtoParachainsToMillauSigningParams { + rialto_parachains_to_millau_signer: None, + rialto_parachains_to_millau_signer_password: None, + rialto_parachains_to_millau_signer_file: None, + rialto_parachains_to_millau_signer_password_file: None, + rialto_parachains_to_millau_transactions_mortality: None, + }, + right_relay: RialtoConnectionParams { + rialto_host: "rialto-node-alice".into(), + rialto_port: 9944, + rialto_secure: false, + rialto_runtime_version: RialtoRuntimeVersionParams { + rialto_version_mode: RuntimeVersionType::Bundle, + rialto_spec_version: None, + rialto_transaction_version: None, + }, + }, + } + ), + ); + } +} diff --git a/relays/bin-substrate/src/cli/relay_headers_and_messages/parachain_to_parachain.rs b/relays/bin-substrate/src/cli/relay_headers_and_messages/parachain_to_parachain.rs new file mode 100644 index 00000000000..9adc8fd6624 --- /dev/null +++ b/relays/bin-substrate/src/cli/relay_headers_and_messages/parachain_to_parachain.rs @@ -0,0 +1,281 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![allow(unused_macros)] // TODO (https://github.com/paritytech/parity-bridges-common/issues/1629): remove me + +use async_trait::async_trait; +use std::sync::Arc; + +use crate::cli::{ + bridge::{CliBridgeBase, MessagesCliBridge, ParachainToRelayHeadersCliBridge}, + relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams}, + CliChain, +}; +use bp_polkadot_core::parachains::ParaHash; +use bp_runtime::BlockNumberOf; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, Chain, ChainWithTransactions, Client}; +use sp_core::Pair; +use substrate_relay_helper::{ + finality::SubstrateFinalitySyncPipeline, + on_demand::{ + headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay, + }, + TaggedAccount, TransactionParams, +}; + +/// A base relay between two parachain from different consensus systems. +/// +/// Such relay starts 2 messages relay. It also starts 2 on-demand header relays and 2 on-demand +/// parachain heads relay. +pub struct ParachainToParachainBridge< + L2R: MessagesCliBridge + ParachainToRelayHeadersCliBridge, + R2L: MessagesCliBridge + ParachainToRelayHeadersCliBridge, +> { + /// Parameters that are shared by all bridge types. + pub common: + Full2WayBridgeCommonParams<::Target, ::Target>, + /// Client of the left relay chain. + pub left_relay: Client<::SourceRelay>, + /// Client of the right relay chain. + pub right_relay: Client<::SourceRelay>, + + /// Override for right_relay->left headers signer. + pub right_headers_to_left_transaction_params: + TransactionParams::Target>>, + /// Override for left_relay->right headers signer. + pub left_headers_to_right_transaction_params: + TransactionParams::Target>>, + + /// Override for right->left parachains signer. + pub right_parachains_to_left_transaction_params: + TransactionParams::Target>>, + /// Override for left->right parachains signer. + pub left_parachains_to_right_transaction_params: + TransactionParams::Target>>, +} + +macro_rules! declare_parachain_to_parachain_bridge_schema { + // left-parachain, relay-chain-of-left-parachain, right-parachain, relay-chain-of-right-parachain + ($left_parachain:ident, $left_chain:ident, $right_parachain:ident, $right_chain:ident) => { + bp_runtime::paste::item! { + #[doc = $left_parachain ", " $left_chain ", " $right_parachain " and " $right_chain " headers+parachains+messages relay params."] + #[derive(Debug, PartialEq, StructOpt)] + pub struct [<$left_parachain $right_parachain HeadersAndMessages>] { + // shared parameters + #[structopt(flatten)] + shared: HeadersAndMessagesSharedParams, + + #[structopt(flatten)] + left: [<$left_parachain ConnectionParams>], + #[structopt(flatten)] + left_relay: [<$left_chain ConnectionParams>], + + // default signer, which is always used to sign messages relay transactions on the left chain + #[structopt(flatten)] + left_sign: [<$left_parachain SigningParams>], + // signer used to sign parameter update transactions at the left parachain + #[structopt(flatten)] + left_messages_pallet_owner: [<$left_parachain MessagesPalletOwnerSigningParams>], + + #[structopt(flatten)] + right: [<$right_parachain ConnectionParams>], + #[structopt(flatten)] + right_relay: [<$right_chain ConnectionParams>], + + // default signer, which is always used to sign messages relay transactions on the right chain + #[structopt(flatten)] + right_sign: [<$right_parachain SigningParams>], + // signer used to sign parameter update transactions at the right parachain + #[structopt(flatten)] + right_messages_pallet_owner: [<$right_parachain MessagesPalletOwnerSigningParams>], + + // override for right_relay->left-parachain headers signer + #[structopt(flatten)] + right_relay_headers_to_left_sign_override: [<$right_chain HeadersTo $left_parachain SigningParams>], + // override for left_relay->right-parachain headers signer + #[structopt(flatten)] + left_relay_headers_to_right_sign_override: [<$left_chain HeadersTo $right_parachain SigningParams>], + + // override for right->left parachains signer + #[structopt(flatten)] + right_parachains_to_left_sign_override: [<$right_chain ParachainsTo $left_parachain SigningParams>], + // override for left->right parachains signer + #[structopt(flatten)] + left_parachains_to_right_sign_override: [<$left_chain ParachainsTo $right_parachain SigningParams>], + } + + impl [<$left_chain $right_parachain HeadersAndMessages>] { + async fn into_bridge< + Left: ChainWithTransactions + CliChain>, + LeftRelay: CliChain, + Right: ChainWithTransactions + CliChain>, + RightRelay: CliChain, + L2R: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + >( + self, + ) -> anyhow::Result> { + Ok(ParachainToParachainBridge { + common: Full2WayBridgeCommonParams::new::( + self.shared, + BridgeEndCommonParams { + client: self.left.into_client::().await?, + sign: self.left_sign.to_keypair::()?, + transactions_mortality: self.left_sign.transactions_mortality()?, + messages_pallet_owner: self.left_messages_pallet_owner.to_keypair::()?, + accounts: vec![], + }, + BridgeEndCommonParams { + client: self.right.into_client::().await?, + sign: self.right_sign.to_keypair::()?, + transactions_mortality: self.right_sign.transactions_mortality()?, + messages_pallet_owner: self.right_messages_pallet_owner.to_keypair::()?, + accounts: vec![], + }, + )?, + left_relay: self.left_relay.into_client::().await?, + right_relay: self.right_relay.into_client::().await?, + right_headers_to_left_transaction_params: self + .right_relay_headers_to_left_sign_override + .transaction_params_or::(&self.left_sign)?, + left_headers_to_right_transaction_params: self + .left_relay_headers_to_right_sign_override + .transaction_params_or::(&self.right_sign)?, + right_parachains_to_left_transaction_params: self + .right_parachains_to_left_sign_override + .transaction_params_or::(&self.left_sign)?, + left_parachains_to_right_transaction_params: self + .left_parachains_to_right_sign_override + .transaction_params_or::(&self.right_sign)?, + }) + } + } + } + }; +} + +#[async_trait] +impl< + Left: Chain + ChainWithTransactions + CliChain>, + Right: Chain + + ChainWithTransactions + + CliChain>, + LeftRelay: Chain + + CliChain, + RightRelay: Chain + + CliChain, + L2R: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + > Full2WayBridgeBase for ParachainToParachainBridge +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, +{ + type Params = ParachainToParachainBridge; + type Left = Left; + type Right = Right; + + fn common(&self) -> &Full2WayBridgeCommonParams { + &self.common + } + + fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams { + &mut self.common + } + + async fn start_on_demand_headers_relayers( + &mut self, + ) -> anyhow::Result<( + Arc>>, + Arc>>, + )> { + self.common.left.accounts.push(TaggedAccount::Headers { + id: self.right_headers_to_left_transaction_params.signer.public().into(), + bridged_chain: RightRelay::NAME.to_string(), + }); + self.common.left.accounts.push(TaggedAccount::Parachains { + id: self.right_parachains_to_left_transaction_params.signer.public().into(), + bridged_chain: RightRelay::NAME.to_string(), + }); + self.common.right.accounts.push(TaggedAccount::Headers { + id: self.left_headers_to_right_transaction_params.signer.public().into(), + bridged_chain: Left::NAME.to_string(), + }); + self.common.right.accounts.push(TaggedAccount::Parachains { + id: self.left_parachains_to_right_transaction_params.signer.public().into(), + bridged_chain: LeftRelay::NAME.to_string(), + }); + + ::RelayFinality::start_relay_guards( + &self.common.right.client, + &self.left_headers_to_right_transaction_params, + self.common.right.client.can_start_version_guard(), + ) + .await?; + ::RelayFinality::start_relay_guards( + &self.common.left.client, + &self.right_headers_to_left_transaction_params, + self.common.left.client.can_start_version_guard(), + ) + .await?; + + let left_relay_to_right_on_demand_headers = + OnDemandHeadersRelay::new::<::RelayFinality>( + self.left_relay.clone(), + self.common.right.client.clone(), + self.left_headers_to_right_transaction_params.clone(), + self.common.shared.only_mandatory_headers, + ); + let right_relay_to_left_on_demand_headers = + OnDemandHeadersRelay::new::<::RelayFinality>( + self.right_relay.clone(), + self.common.left.client.clone(), + self.right_headers_to_left_transaction_params.clone(), + self.common.shared.only_mandatory_headers, + ); + + let left_to_right_on_demand_parachains = OnDemandParachainsRelay::new::< + ::ParachainFinality, + >( + self.left_relay.clone(), + self.common.right.client.clone(), + self.left_parachains_to_right_transaction_params.clone(), + Arc::new(left_relay_to_right_on_demand_headers), + ); + let right_to_left_on_demand_parachains = OnDemandParachainsRelay::new::< + ::ParachainFinality, + >( + self.right_relay.clone(), + self.common.left.client.clone(), + self.right_parachains_to_left_transaction_params.clone(), + Arc::new(right_relay_to_left_on_demand_headers), + ); + + Ok(( + Arc::new(left_to_right_on_demand_parachains), + Arc::new(right_to_left_on_demand_parachains), + )) + } +} diff --git a/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_parachain.rs b/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_parachain.rs new file mode 100644 index 00000000000..4070783df09 --- /dev/null +++ b/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_parachain.rs @@ -0,0 +1,254 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use async_trait::async_trait; +use std::sync::Arc; + +use crate::cli::{ + bridge::{ + CliBridgeBase, MessagesCliBridge, ParachainToRelayHeadersCliBridge, + RelayToRelayHeadersCliBridge, + }, + relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams}, + CliChain, +}; +use bp_polkadot_core::parachains::ParaHash; +use bp_runtime::BlockNumberOf; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, Chain, ChainWithTransactions, Client}; +use sp_core::Pair; +use substrate_relay_helper::{ + finality::SubstrateFinalitySyncPipeline, + on_demand::{ + headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay, + }, + TaggedAccount, TransactionParams, +}; + +/// A base relay between standalone (relay) chain and a parachain from another consensus system. +/// +/// Such relay starts 2 messages relay. It also starts 2 on-demand header relays and 1 on-demand +/// parachain heads relay. +pub struct RelayToParachainBridge< + L2R: MessagesCliBridge + RelayToRelayHeadersCliBridge, + R2L: MessagesCliBridge + ParachainToRelayHeadersCliBridge, +> { + /// Parameters that are shared by all bridge types. + pub common: + Full2WayBridgeCommonParams<::Target, ::Target>, + /// Client of the right relay chain. + pub right_relay: Client<::SourceRelay>, + + /// Override for right_relay->left headers signer. + pub right_headers_to_left_transaction_params: + TransactionParams::Target>>, + /// Override for right->left parachains signer. + pub right_parachains_to_left_transaction_params: + TransactionParams::Target>>, + /// Override for left->right headers signer. + pub left_headers_to_right_transaction_params: + TransactionParams::Target>>, +} + +macro_rules! declare_relay_to_parachain_bridge_schema { + // chain, parachain, relay-chain-of-parachain + ($left_chain:ident, $right_parachain:ident, $right_chain:ident) => { + bp_runtime::paste::item! { + #[doc = $left_chain ", " $right_parachain " and " $right_chain " headers+parachains+messages relay params."] + #[derive(Debug, PartialEq, StructOpt)] + pub struct [<$left_chain $right_parachain HeadersAndMessages>] { + // shared parameters + #[structopt(flatten)] + shared: HeadersAndMessagesSharedParams, + + #[structopt(flatten)] + left: [<$left_chain ConnectionParams>], + + // default signer, which is always used to sign messages relay transactions on the left chain + #[structopt(flatten)] + left_sign: [<$left_chain SigningParams>], + // signer used to sign parameter update transactions at the left chain + #[structopt(flatten)] + left_messages_pallet_owner: [<$left_chain MessagesPalletOwnerSigningParams>], + + #[structopt(flatten)] + right: [<$right_parachain ConnectionParams>], + #[structopt(flatten)] + right_relay: [<$right_chain ConnectionParams>], + + // default signer, which is always used to sign messages relay transactions on the right chain + #[structopt(flatten)] + right_sign: [<$right_parachain SigningParams>], + // signer used to sign parameter update transactions at the left chain + #[structopt(flatten)] + right_messages_pallet_owner: [<$right_parachain MessagesPalletOwnerSigningParams>], + + + // override for right_relay->left headers signer + #[structopt(flatten)] + right_relay_headers_to_left_sign_override: [<$right_chain HeadersTo $left_chain SigningParams>], + // override for left->right headers signer + #[structopt(flatten)] + left_headers_to_right_sign_override: [<$left_chain HeadersTo $right_parachain SigningParams>], + + // override for right->left parachains signer + #[structopt(flatten)] + right_parachains_to_left_sign_override: [<$right_chain ParachainsTo $left_chain SigningParams>], + } + + impl [<$left_chain $right_parachain HeadersAndMessages>] { + async fn into_bridge< + Left: ChainWithTransactions + CliChain>, + Right: ChainWithTransactions + CliChain>, + RightRelay: CliChain, + L2R: CliBridgeBase + MessagesCliBridge + RelayToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + >( + self, + ) -> anyhow::Result> { + Ok(RelayToParachainBridge { + common: Full2WayBridgeCommonParams::new::( + self.shared, + BridgeEndCommonParams { + client: self.left.into_client::().await?, + sign: self.left_sign.to_keypair::()?, + transactions_mortality: self.left_sign.transactions_mortality()?, + messages_pallet_owner: self.left_messages_pallet_owner.to_keypair::()?, + accounts: vec![], + }, + BridgeEndCommonParams { + client: self.right.into_client::().await?, + sign: self.right_sign.to_keypair::()?, + transactions_mortality: self.right_sign.transactions_mortality()?, + messages_pallet_owner: self.right_messages_pallet_owner.to_keypair::()?, + accounts: vec![], + }, + )?, + right_relay: self.right_relay.into_client::().await?, + right_headers_to_left_transaction_params: self + .right_relay_headers_to_left_sign_override + .transaction_params_or::( + &self.left_sign, + )?, + right_parachains_to_left_transaction_params: self + .right_parachains_to_left_sign_override + .transaction_params_or::( + &self.left_sign, + )?, + left_headers_to_right_transaction_params: self + .left_headers_to_right_sign_override + .transaction_params_or::(&self.right_sign)?, + }) + } + } + } + }; +} + +#[async_trait] +impl< + Left: ChainWithTransactions + CliChain>, + Right: Chain + + ChainWithTransactions + + CliChain>, + RightRelay: Chain + + CliChain, + L2R: CliBridgeBase + + MessagesCliBridge + + RelayToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + ParachainToRelayHeadersCliBridge, + > Full2WayBridgeBase for RelayToParachainBridge +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, +{ + type Params = RelayToParachainBridge; + type Left = Left; + type Right = Right; + + fn common(&self) -> &Full2WayBridgeCommonParams { + &self.common + } + + fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams { + &mut self.common + } + + async fn start_on_demand_headers_relayers( + &mut self, + ) -> anyhow::Result<( + Arc>>, + Arc>>, + )> { + self.common.left.accounts.push(TaggedAccount::Headers { + id: self.right_headers_to_left_transaction_params.signer.public().into(), + bridged_chain: RightRelay::NAME.to_string(), + }); + self.common.left.accounts.push(TaggedAccount::Parachains { + id: self.right_parachains_to_left_transaction_params.signer.public().into(), + bridged_chain: RightRelay::NAME.to_string(), + }); + self.common.right.accounts.push(TaggedAccount::Headers { + id: self.left_headers_to_right_transaction_params.signer.public().into(), + bridged_chain: Left::NAME.to_string(), + }); + + ::Finality::start_relay_guards( + &self.common.right.client, + &self.left_headers_to_right_transaction_params, + self.common.right.client.can_start_version_guard(), + ) + .await?; + ::RelayFinality::start_relay_guards( + &self.common.left.client, + &self.right_headers_to_left_transaction_params, + self.common.left.client.can_start_version_guard(), + ) + .await?; + + let left_to_right_on_demand_headers = + OnDemandHeadersRelay::new::<::Finality>( + self.common.left.client.clone(), + self.common.right.client.clone(), + self.left_headers_to_right_transaction_params.clone(), + self.common.shared.only_mandatory_headers, + ); + let right_relay_to_left_on_demand_headers = + OnDemandHeadersRelay::new::<::RelayFinality>( + self.right_relay.clone(), + self.common.left.client.clone(), + self.right_headers_to_left_transaction_params.clone(), + self.common.shared.only_mandatory_headers, + ); + let right_to_left_on_demand_parachains = OnDemandParachainsRelay::new::< + ::ParachainFinality, + >( + self.right_relay.clone(), + self.common.left.client.clone(), + self.right_parachains_to_left_transaction_params.clone(), + Arc::new(right_relay_to_left_on_demand_headers), + ); + + Ok(( + Arc::new(left_to_right_on_demand_headers), + Arc::new(right_to_left_on_demand_parachains), + )) + } +} diff --git a/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_relay.rs b/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_relay.rs new file mode 100644 index 00000000000..bda532a2afd --- /dev/null +++ b/relays/bin-substrate/src/cli/relay_headers_and_messages/relay_to_relay.rs @@ -0,0 +1,194 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use async_trait::async_trait; +use std::sync::Arc; + +use crate::cli::{ + bridge::{CliBridgeBase, MessagesCliBridge, RelayToRelayHeadersCliBridge}, + relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams}, + CliChain, +}; +use bp_runtime::BlockNumberOf; +use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, ChainWithTransactions}; +use sp_core::Pair; +use substrate_relay_helper::{ + finality::SubstrateFinalitySyncPipeline, + on_demand::{headers::OnDemandHeadersRelay, OnDemandRelay}, + TaggedAccount, TransactionParams, +}; + +/// A base relay between two standalone (relay) chains. +/// +/// Such relay starts 2 messages relay and 2 on-demand header relays. +pub struct RelayToRelayBridge< + L2R: MessagesCliBridge + RelayToRelayHeadersCliBridge, + R2L: MessagesCliBridge + RelayToRelayHeadersCliBridge, +> { + /// Parameters that are shared by all bridge types. + pub common: + Full2WayBridgeCommonParams<::Target, ::Target>, + /// Override for right->left headers signer. + pub right_to_left_transaction_params: + TransactionParams::Target>>, + /// Override for left->right headers signer. + pub left_to_right_transaction_params: + TransactionParams::Target>>, +} + +macro_rules! declare_relay_to_relay_bridge_schema { + ($left_chain:ident, $right_chain:ident) => { + bp_runtime::paste::item! { + #[doc = $left_chain " and " $right_chain " headers+messages relay params."] + #[derive(Debug, PartialEq, StructOpt)] + pub struct [<$left_chain $right_chain HeadersAndMessages>] { + #[structopt(flatten)] + shared: HeadersAndMessagesSharedParams, + // default signer, which is always used to sign messages relay transactions on the left chain + #[structopt(flatten)] + left: [<$left_chain ConnectionParams>], + // override for right->left headers signer + #[structopt(flatten)] + right_headers_to_left_sign_override: [<$right_chain HeadersTo $left_chain SigningParams>], + #[structopt(flatten)] + left_sign: [<$left_chain SigningParams>], + #[structopt(flatten)] + left_messages_pallet_owner: [<$left_chain MessagesPalletOwnerSigningParams>], + // default signer, which is always used to sign messages relay transactions on the right chain + #[structopt(flatten)] + right: [<$right_chain ConnectionParams>], + // override for left->right headers signer + #[structopt(flatten)] + left_headers_to_right_sign_override: [<$left_chain HeadersTo $right_chain SigningParams>], + #[structopt(flatten)] + right_sign: [<$right_chain SigningParams>], + #[structopt(flatten)] + right_messages_pallet_owner: [<$right_chain MessagesPalletOwnerSigningParams>], + } + + impl [<$left_chain $right_chain HeadersAndMessages>] { + async fn into_bridge< + Left: ChainWithTransactions + CliChain>, + Right: ChainWithTransactions + CliChain>, + L2R: CliBridgeBase + MessagesCliBridge + RelayToRelayHeadersCliBridge, + R2L: CliBridgeBase + MessagesCliBridge + RelayToRelayHeadersCliBridge, + >( + self, + ) -> anyhow::Result> { + Ok(RelayToRelayBridge { + common: Full2WayBridgeCommonParams::new::( + self.shared, + BridgeEndCommonParams { + client: self.left.into_client::().await?, + sign: self.left_sign.to_keypair::()?, + transactions_mortality: self.left_sign.transactions_mortality()?, + messages_pallet_owner: self.left_messages_pallet_owner.to_keypair::()?, + accounts: vec![], + }, + BridgeEndCommonParams { + client: self.right.into_client::().await?, + sign: self.right_sign.to_keypair::()?, + transactions_mortality: self.right_sign.transactions_mortality()?, + messages_pallet_owner: self.right_messages_pallet_owner.to_keypair::()?, + accounts: vec![], + }, + )?, + right_to_left_transaction_params: self + .right_headers_to_left_sign_override + .transaction_params_or::(&self.left_sign)?, + left_to_right_transaction_params: self + .left_headers_to_right_sign_override + .transaction_params_or::(&self.right_sign)?, + }) + } + } + } + }; +} + +#[async_trait] +impl< + Left: ChainWithTransactions + CliChain>, + Right: ChainWithTransactions + CliChain>, + L2R: CliBridgeBase + + MessagesCliBridge + + RelayToRelayHeadersCliBridge, + R2L: CliBridgeBase + + MessagesCliBridge + + RelayToRelayHeadersCliBridge, + > Full2WayBridgeBase for RelayToRelayBridge +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, +{ + type Params = RelayToRelayBridge; + type Left = Left; + type Right = Right; + + fn common(&self) -> &Full2WayBridgeCommonParams { + &self.common + } + + fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams { + &mut self.common + } + + async fn start_on_demand_headers_relayers( + &mut self, + ) -> anyhow::Result<( + Arc>>, + Arc>>, + )> { + self.common.right.accounts.push(TaggedAccount::Headers { + id: self.left_to_right_transaction_params.signer.public().into(), + bridged_chain: Self::Left::NAME.to_string(), + }); + self.common.left.accounts.push(TaggedAccount::Headers { + id: self.right_to_left_transaction_params.signer.public().into(), + bridged_chain: Self::Right::NAME.to_string(), + }); + + ::Finality::start_relay_guards( + &self.common.right.client, + &self.left_to_right_transaction_params, + self.common.right.client.can_start_version_guard(), + ) + .await?; + ::Finality::start_relay_guards( + &self.common.left.client, + &self.right_to_left_transaction_params, + self.common.left.client.can_start_version_guard(), + ) + .await?; + + let left_to_right_on_demand_headers = + OnDemandHeadersRelay::new::<::Finality>( + self.common.left.client.clone(), + self.common.right.client.clone(), + self.left_to_right_transaction_params.clone(), + self.common.shared.only_mandatory_headers, + ); + let right_to_left_on_demand_headers = + OnDemandHeadersRelay::new::<::Finality>( + self.common.right.client.clone(), + self.common.left.client.clone(), + self.right_to_left_transaction_params.clone(), + self.common.shared.only_mandatory_headers, + ); + + Ok((Arc::new(left_to_right_on_demand_headers), Arc::new(right_to_left_on_demand_headers))) + } +} diff --git a/relays/bin-substrate/src/cli/relay_messages.rs b/relays/bin-substrate/src/cli/relay_messages.rs new file mode 100644 index 00000000000..7f4dc34ac8b --- /dev/null +++ b/relays/bin-substrate/src/cli/relay_messages.rs @@ -0,0 +1,117 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use async_trait::async_trait; +use sp_core::Pair; +use structopt::StructOpt; +use strum::VariantNames; + +use crate::chains::{ + bridge_hub_rococo_messages_to_bridge_hub_wococo::BridgeHubRococoToBridgeHubWococoMessagesCliBridge, + bridge_hub_wococo_messages_to_bridge_hub_rococo::BridgeHubWococoToBridgeHubRococoMessagesCliBridge, + millau_headers_to_rialto::MillauToRialtoCliBridge, + millau_headers_to_rialto_parachain::MillauToRialtoParachainCliBridge, + rialto_headers_to_millau::RialtoToMillauCliBridge, + rialto_parachains_to_millau::RialtoParachainToMillauCliBridge, +}; +use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, BalanceOf, ChainWithTransactions}; +use substrate_relay_helper::{messages_lane::MessagesRelayParams, TransactionParams}; + +use crate::cli::{bridge::*, chain_schema::*, CliChain, HexLaneId, PrometheusParams}; + +/// Start messages relayer process. +#[derive(StructOpt)] +pub struct RelayMessages { + /// A bridge instance to relay messages for. + #[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)] + bridge: FullBridge, + /// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`. + #[structopt(long, default_value = "00000000")] + lane: HexLaneId, + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + source_sign: SourceSigningParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + #[structopt(flatten)] + prometheus_params: PrometheusParams, +} + +#[async_trait] +trait MessagesRelayer: MessagesCliBridge +where + Self::Source: ChainWithTransactions + CliChain>, + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom>, +{ + async fn relay_messages(data: RelayMessages) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let source_sign = data.source_sign.to_keypair::()?; + let source_transactions_mortality = data.source_sign.transactions_mortality()?; + let target_client = data.target.into_client::().await?; + let target_sign = data.target_sign.to_keypair::()?; + let target_transactions_mortality = data.target_sign.transactions_mortality()?; + + substrate_relay_helper::messages_lane::run::(MessagesRelayParams { + source_client, + source_transaction_params: TransactionParams { + signer: source_sign, + mortality: source_transactions_mortality, + }, + target_client, + target_transaction_params: TransactionParams { + signer: target_sign, + mortality: target_transactions_mortality, + }, + source_to_target_headers_relay: None, + target_to_source_headers_relay: None, + lane_id: data.lane.into(), + metrics_params: data.prometheus_params.into(), + }) + .await + .map_err(|e| anyhow::format_err!("{}", e)) + } +} + +impl MessagesRelayer for MillauToRialtoCliBridge {} +impl MessagesRelayer for RialtoToMillauCliBridge {} +impl MessagesRelayer for MillauToRialtoParachainCliBridge {} +impl MessagesRelayer for RialtoParachainToMillauCliBridge {} +impl MessagesRelayer for BridgeHubRococoToBridgeHubWococoMessagesCliBridge {} +impl MessagesRelayer for BridgeHubWococoToBridgeHubRococoMessagesCliBridge {} + +impl RelayMessages { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + match self.bridge { + FullBridge::MillauToRialto => MillauToRialtoCliBridge::relay_messages(self), + FullBridge::RialtoToMillau => RialtoToMillauCliBridge::relay_messages(self), + FullBridge::MillauToRialtoParachain => + MillauToRialtoParachainCliBridge::relay_messages(self), + FullBridge::RialtoParachainToMillau => + RialtoParachainToMillauCliBridge::relay_messages(self), + FullBridge::BridgeHubRococoToBridgeHubWococo => + BridgeHubRococoToBridgeHubWococoMessagesCliBridge::relay_messages(self), + FullBridge::BridgeHubWococoToBridgeHubRococo => + BridgeHubWococoToBridgeHubRococoMessagesCliBridge::relay_messages(self), + } + .await + } +} diff --git a/relays/bin-substrate/src/cli/relay_parachains.rs b/relays/bin-substrate/src/cli/relay_parachains.rs new file mode 100644 index 00000000000..286cecc914b --- /dev/null +++ b/relays/bin-substrate/src/cli/relay_parachains.rs @@ -0,0 +1,139 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::chains::{ + rialto_parachains_to_millau::RialtoParachainToMillauCliBridge, + rococo_parachains_to_bridge_hub_wococo::BridgeHubRococoToBridgeHubWococoCliBridge, + westend_parachains_to_millau::WestmintToMillauCliBridge, + wococo_parachains_to_bridge_hub_rococo::BridgeHubWococoToBridgeHubRococoCliBridge, +}; +use async_std::sync::Mutex; +use async_trait::async_trait; +use bp_polkadot_core::parachains::ParaId; +use parachains_relay::parachains_loop::{ + AvailableHeader, ParachainSyncParams, SourceClient, TargetClient, +}; +use relay_utils::metrics::{GlobalMetrics, StandaloneMetric}; +use std::sync::Arc; +use structopt::StructOpt; +use strum::{EnumString, EnumVariantNames, VariantNames}; +use substrate_relay_helper::{ + parachains::{ + source::ParachainsSource, target::ParachainsTarget, ParachainsPipelineAdapter, + SubstrateParachainsPipeline, + }, + TransactionParams, +}; + +use crate::cli::{bridge::ParachainToRelayHeadersCliBridge, chain_schema::*, PrometheusParams}; + +/// Start parachain heads relayer process. +#[derive(StructOpt)] +pub struct RelayParachains { + /// A bridge instance to relay parachains heads for. + #[structopt(possible_values = RelayParachainsBridge::VARIANTS, case_insensitive = true)] + bridge: RelayParachainsBridge, + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + #[structopt(flatten)] + prometheus_params: PrometheusParams, +} + +/// Parachain heads relay bridge. +#[derive(Debug, EnumString, EnumVariantNames)] +#[strum(serialize_all = "kebab_case")] +pub enum RelayParachainsBridge { + RialtoToMillau, + // TODO:check-parameter - rename to WestmintToMillau? + WestendToMillau, + BridgeHubRococoToBridgeHubWococo, + BridgeHubWococoToBridgeHubRococo, +} + +#[async_trait] +trait ParachainsRelayer: ParachainToRelayHeadersCliBridge +where + ParachainsSource: + SourceClient>, + ParachainsTarget: + TargetClient>, +{ + async fn relay_headers(data: RelayParachains) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let source_client = ParachainsSource::::new( + source_client, + Arc::new(Mutex::new(AvailableHeader::Missing)), + ); + + let target_transaction_params = TransactionParams { + signer: data.target_sign.to_keypair::()?, + mortality: data.target_sign.target_transactions_mortality, + }; + let target_client = data.target.into_client::().await?; + let target_client = ParachainsTarget::::new( + target_client.clone(), + target_transaction_params, + ); + + let metrics_params: relay_utils::metrics::MetricsParams = data.prometheus_params.into(); + GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?; + + parachains_relay::parachains_loop::run( + source_client, + target_client, + ParachainSyncParams { + parachains: vec![ + ParaId(::SOURCE_PARACHAIN_PARA_ID) + ], + stall_timeout: std::time::Duration::from_secs(60), + strategy: parachains_relay::parachains_loop::ParachainSyncStrategy::Any, + }, + metrics_params, + futures::future::pending(), + ) + .await + .map_err(|e| anyhow::format_err!("{}", e)) + } +} + +impl ParachainsRelayer for RialtoParachainToMillauCliBridge {} + +impl ParachainsRelayer for WestmintToMillauCliBridge {} + +impl ParachainsRelayer for BridgeHubRococoToBridgeHubWococoCliBridge {} + +impl ParachainsRelayer for BridgeHubWococoToBridgeHubRococoCliBridge {} + +impl RelayParachains { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + match self.bridge { + RelayParachainsBridge::RialtoToMillau => + RialtoParachainToMillauCliBridge::relay_headers(self), + RelayParachainsBridge::WestendToMillau => + WestmintToMillauCliBridge::relay_headers(self), + RelayParachainsBridge::BridgeHubRococoToBridgeHubWococo => + BridgeHubRococoToBridgeHubWococoCliBridge::relay_headers(self), + RelayParachainsBridge::BridgeHubWococoToBridgeHubRococo => + BridgeHubWococoToBridgeHubRococoCliBridge::relay_headers(self), + } + .await + } +} diff --git a/relays/bin-substrate/src/cli/resubmit_transactions.rs b/relays/bin-substrate/src/cli/resubmit_transactions.rs new file mode 100644 index 00000000000..c69f9c8ae4e --- /dev/null +++ b/relays/bin-substrate/src/cli/resubmit_transactions.rs @@ -0,0 +1,561 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::cli::{chain_schema::*, Balance}; + +use bp_runtime::HeaderIdProvider; +use codec::{Decode, Encode}; +use num_traits::{One, Zero}; +use relay_substrate_client::{ + AccountKeyPairOf, BlockWithJustification, Chain, ChainWithTransactions, Client, + Error as SubstrateError, HeaderIdOf, HeaderOf, SignParam, +}; +use relay_utils::FailedClient; +use sp_core::Bytes; +use sp_runtime::{ + traits::{Hash, Header as HeaderT}, + transaction_validity::TransactionPriority, +}; +use structopt::StructOpt; +use strum::{EnumString, EnumVariantNames, VariantNames}; +use substrate_relay_helper::TransactionParams; + +/// Start resubmit transactions process. +#[derive(StructOpt)] +pub struct ResubmitTransactions { + /// A bridge instance to relay headers for. + #[structopt(possible_values = RelayChain::VARIANTS, case_insensitive = true)] + chain: RelayChain, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + /// Number of blocks we see before considering queued transaction as stalled. + #[structopt(long, default_value = "5")] + stalled_blocks: u32, + /// Tip limit. We'll never submit transaction with larger tip. + #[structopt(long)] + tip_limit: Balance, + /// Tip increase step. We'll be checking updated transaction priority by increasing its tip by + /// this step. + #[structopt(long)] + tip_step: Balance, + /// Priority selection strategy. + #[structopt(subcommand)] + strategy: PrioritySelectionStrategy, +} + +/// Chain, which transactions we're going to track && resubmit. +#[derive(Debug, EnumString, EnumVariantNames)] +#[strum(serialize_all = "kebab_case")] +pub enum RelayChain { + Millau, +} + +/// Strategy to use for priority selection. +#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy)] +pub enum PrioritySelectionStrategy { + /// Strategy selects tip that changes transaction priority to be better than priority of + /// the first transaction of previous block. + /// + /// It only makes sense to use this strategy for Millau transactions. Millau has transactions + /// that are close to block limits, so if there are any other queued transactions, 'large' + /// transaction won't fit the block && will be postponed. To avoid this, we change its priority + /// to some large value, making it best transaction => it'll be 'mined' first. + MakeItBestTransaction, + /// Strategy selects tip that changes transaction priority to be better than priority of + /// selected queued transaction. + /// + /// When we first see stalled transaction, we make it better than worst 1/4 of queued + /// transactions. If it is still stalled, we'll make it better than 1/3 of queued transactions, + /// ... + MakeItBetterThanQueuedTransaction, +} + +macro_rules! select_bridge { + ($bridge: expr, $generic: tt) => { + match $bridge { + RelayChain::Millau => { + type Target = relay_millau_client::Millau; + type TargetSign = relay_millau_client::Millau; + + $generic + }, + } + }; +} + +impl ResubmitTransactions { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + select_bridge!(self.chain, { + let relay_loop_name = format!("ResubmitTransactions{}", Target::NAME); + let client = self.target.into_client::().await?; + let transaction_params = TransactionParams { + signer: self.target_sign.to_keypair::()?, + mortality: self.target_sign.target_transactions_mortality, + }; + + relay_utils::relay_loop((), client) + .run(relay_loop_name, move |_, client, _| { + run_until_connection_lost( + client, + transaction_params.clone(), + Context { + strategy: self.strategy, + best_header: HeaderOf::::new( + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ), + transaction: None, + resubmitted: 0, + stalled_for: Zero::zero(), + stalled_for_limit: self.stalled_blocks as _, + tip_step: self.tip_step.cast() as _, + tip_limit: self.tip_limit.cast() as _, + }, + ) + }) + .await + .map_err(Into::into) + }) + } +} + +impl PrioritySelectionStrategy { + /// Select target priority. + async fn select_target_priority( + &self, + client: &Client, + context: &Context, + ) -> Result, SubstrateError> { + match *self { + PrioritySelectionStrategy::MakeItBestTransaction => + read_previous_block_best_priority(client, context).await, + PrioritySelectionStrategy::MakeItBetterThanQueuedTransaction => + select_priority_from_queue(client, context).await, + } + } +} + +#[derive(Debug)] +struct Context { + /// Priority selection strategy. + strategy: PrioritySelectionStrategy, + /// Best known block header. + best_header: C::Header, + /// Hash of the (potentially) stalled transaction. + transaction: Option, + /// How many times we have resubmitted this `transaction`? + resubmitted: u32, + /// This transaction is in pool for `stalled_for` wakeup intervals. + stalled_for: C::BlockNumber, + /// When `stalled_for` reaching this limit, transaction is considered stalled. + stalled_for_limit: C::BlockNumber, + /// Tip step interval. + tip_step: C::Balance, + /// Maximal tip. + tip_limit: C::Balance, +} + +impl Context { + /// Return true if transaction has stalled. + fn is_stalled(&self) -> bool { + self.stalled_for >= self.stalled_for_limit + } + + /// Notice resubmitted transaction. + fn notice_resubmitted_transaction(mut self, transaction: C::Hash) -> Self { + self.transaction = Some(transaction); + self.stalled_for = Zero::zero(); + self.resubmitted += 1; + self + } + + /// Notice transaction from the transaction pool. + fn notice_transaction(mut self, transaction: C::Hash) -> Self { + if self.transaction == Some(transaction) { + self.stalled_for += One::one(); + } else { + self.transaction = Some(transaction); + self.stalled_for = One::one(); + self.resubmitted = 0; + } + self + } +} + +/// Run resubmit transactions loop. +async fn run_until_connection_lost( + client: Client, + transaction_params: TransactionParams>, + mut context: Context, +) -> Result<(), FailedClient> { + loop { + async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await; + + let result = run_loop_iteration(client.clone(), transaction_params.clone(), context).await; + context = match result { + Ok(context) => context, + Err(error) => { + log::error!( + target: "bridge", + "Resubmit {} transactions loop has failed with error: {:?}", + C::NAME, + error, + ); + return Err(FailedClient::Target) + }, + }; + } +} + +/// Run single loop iteration. +async fn run_loop_iteration( + client: Client, + transaction_params: TransactionParams>, + mut context: Context, +) -> Result, SubstrateError> { + // correct best header is required for all other actions + context.best_header = client.best_header().await?; + + // check if there's queued transaction, signed by given author + let original_transaction = + match lookup_signer_transaction(&client, &transaction_params.signer).await? { + Some(original_transaction) => original_transaction, + None => { + log::trace!(target: "bridge", "No {} transactions from required signer in the txpool", C::NAME); + return Ok(context) + }, + }; + let original_transaction_hash = C::Hasher::hash(&original_transaction.encode()); + let context = context.notice_transaction(original_transaction_hash); + + // if transaction hasn't been mined for `stalled_blocks`, we'll need to resubmit it + if !context.is_stalled() { + log::trace!( + target: "bridge", + "{} transaction {:?} is not yet stalled ({:?}/{:?})", + C::NAME, + context.transaction, + context.stalled_for, + context.stalled_for_limit, + ); + return Ok(context) + } + + // select priority for updated transaction + let target_priority = match context.strategy.select_target_priority(&client, &context).await? { + Some(target_priority) => target_priority, + None => { + log::trace!(target: "bridge", "Failed to select target priority"); + return Ok(context) + }, + }; + + // update transaction tip + let (is_updated, updated_transaction) = update_transaction_tip( + &client, + &transaction_params, + context.best_header.id(), + original_transaction, + context.tip_step, + context.tip_limit, + target_priority, + ) + .await?; + + if !is_updated { + log::trace!(target: "bridge", "{} transaction tip can not be updated. Reached limit?", C::NAME); + return Ok(context) + } + + let updated_transaction = updated_transaction.encode(); + let updated_transaction_hash = C::Hasher::hash(&updated_transaction); + client.submit_unsigned_extrinsic(Bytes(updated_transaction)).await?; + + log::info!( + target: "bridge", + "Replaced {} transaction {} with {} in txpool", + C::NAME, + original_transaction_hash, + updated_transaction_hash, + ); + + Ok(context.notice_resubmitted_transaction(updated_transaction_hash)) +} + +/// Search transaction pool for transaction, signed by given key pair. +async fn lookup_signer_transaction( + client: &Client, + key_pair: &AccountKeyPairOf, +) -> Result, SubstrateError> { + let pending_transactions = client.pending_extrinsics().await?; + for pending_transaction in pending_transactions { + let pending_transaction = C::SignedTransaction::decode(&mut &pending_transaction.0[..]) + .map_err(SubstrateError::ResponseParseFailed)?; + if !C::is_signed_by(key_pair, &pending_transaction) { + continue + } + + return Ok(Some(pending_transaction)) + } + + Ok(None) +} + +/// Read priority of best signed transaction of previous block. +async fn read_previous_block_best_priority( + client: &Client, + context: &Context, +) -> Result, SubstrateError> { + let best_block = client.get_block(Some(context.best_header.hash())).await?; + let best_transaction = best_block + .extrinsics() + .iter() + .filter_map(|xt| C::SignedTransaction::decode(&mut &xt[..]).ok()) + .find(|xt| C::is_signed(xt)); + match best_transaction { + Some(best_transaction) => Ok(Some( + client + .validate_transaction(*context.best_header.parent_hash(), best_transaction) + .await?? + .priority, + )), + None => Ok(None), + } +} + +/// Select priority of some queued transaction. +async fn select_priority_from_queue( + client: &Client, + context: &Context, +) -> Result, SubstrateError> { + // select transaction from the queue + let queued_transactions = client.pending_extrinsics().await?; + let selected_transaction = match select_transaction_from_queue(queued_transactions, context) { + Some(selected_transaction) => selected_transaction, + None => return Ok(None), + }; + + let selected_transaction = C::SignedTransaction::decode(&mut &selected_transaction[..]) + .map_err(SubstrateError::ResponseParseFailed)?; + let target_priority = client + .validate_transaction(context.best_header.hash(), selected_transaction) + .await?? + .priority; + Ok(Some(target_priority)) +} + +/// Select transaction with target priority from the vec of queued transactions. +fn select_transaction_from_queue( + mut queued_transactions: Vec, + context: &Context, +) -> Option { + if queued_transactions.is_empty() { + return None + } + + // the more times we resubmit transaction (`context.resubmitted`), the closer we move + // to the front of the transaction queue + let total_transactions = queued_transactions.len(); + let resubmitted_factor = context.resubmitted; + let divisor = + 1usize.saturating_add(1usize.checked_shl(resubmitted_factor).unwrap_or(usize::MAX)); + let transactions_to_skip = total_transactions / divisor; + + Some( + queued_transactions + .swap_remove(std::cmp::min(total_transactions - 1, transactions_to_skip)), + ) +} + +/// Try to find appropriate tip for transaction so that its priority is larger than given. +async fn update_transaction_tip( + client: &Client, + transaction_params: &TransactionParams>, + at_block: HeaderIdOf, + tx: C::SignedTransaction, + tip_step: C::Balance, + tip_limit: C::Balance, + target_priority: TransactionPriority, +) -> Result<(bool, C::SignedTransaction), SubstrateError> { + let stx = format!("{tx:?}"); + let mut current_priority = client.validate_transaction(at_block.1, tx.clone()).await??.priority; + let mut unsigned_tx = C::parse_transaction(tx).ok_or_else(|| { + SubstrateError::Custom(format!("Failed to parse {} transaction {stx}", C::NAME,)) + })?; + let old_tip = unsigned_tx.tip; + + let (spec_version, transaction_version) = client.simple_runtime_version().await?; + while current_priority < target_priority { + let next_tip = unsigned_tx.tip + tip_step; + if next_tip > tip_limit { + break + } + + log::trace!( + target: "bridge", + "{} transaction priority with tip={:?}: {}. Target priority: {}", + C::NAME, + unsigned_tx.tip, + current_priority, + target_priority, + ); + + unsigned_tx.tip = next_tip; + current_priority = client + .validate_transaction( + at_block.1, + C::sign_transaction( + SignParam { + spec_version, + transaction_version, + genesis_hash: *client.genesis_hash(), + signer: transaction_params.signer.clone(), + }, + unsigned_tx.clone(), + )?, + ) + .await?? + .priority; + } + + log::debug!( + target: "bridge", + "{} transaction tip has changed from {:?} to {:?}", + C::NAME, + old_tip, + unsigned_tx.tip, + ); + + Ok(( + old_tip != unsigned_tx.tip, + C::sign_transaction( + SignParam { + spec_version, + transaction_version, + genesis_hash: *client.genesis_hash(), + signer: transaction_params.signer.clone(), + }, + unsigned_tx.era(relay_substrate_client::TransactionEra::new( + at_block, + transaction_params.mortality, + )), + )?, + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use bp_rialto::Hash; + use relay_rialto_client::Rialto; + + fn context() -> Context { + Context { + strategy: PrioritySelectionStrategy::MakeItBestTransaction, + best_header: HeaderOf::::new( + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ), + transaction: None, + resubmitted: 0, + stalled_for: Zero::zero(), + stalled_for_limit: 3, + tip_step: 100, + tip_limit: 1000, + } + } + + #[test] + fn context_works() { + let mut context = context(); + + // when transaction is noticed 2/3 times, it isn't stalled + context = context.notice_transaction(Default::default()); + assert!(!context.is_stalled()); + assert_eq!(context.stalled_for, 1); + assert_eq!(context.resubmitted, 0); + context = context.notice_transaction(Default::default()); + assert!(!context.is_stalled()); + assert_eq!(context.stalled_for, 2); + assert_eq!(context.resubmitted, 0); + + // when transaction is noticed for 3rd time in a row, it is considered stalled + context = context.notice_transaction(Default::default()); + assert!(context.is_stalled()); + assert_eq!(context.stalled_for, 3); + assert_eq!(context.resubmitted, 0); + + // and after we resubmit it, we forget previous transaction + context = context.notice_resubmitted_transaction(Hash::from([1; 32])); + assert_eq!(context.transaction, Some(Hash::from([1; 32]))); + assert_eq!(context.resubmitted, 1); + assert_eq!(context.stalled_for, 0); + } + + #[test] + fn select_transaction_from_queue_works_with_empty_queue() { + assert_eq!(select_transaction_from_queue(vec![], &context()), None); + } + + #[test] + fn select_transaction_from_queue_works() { + let mut context = context(); + let queued_transactions = vec![ + Bytes(vec![1]), + Bytes(vec![2]), + Bytes(vec![3]), + Bytes(vec![4]), + Bytes(vec![5]), + Bytes(vec![6]), + ]; + + // when we resubmit tx for the first time, 1/2 of queue is skipped + assert_eq!( + select_transaction_from_queue(queued_transactions.clone(), &context), + Some(Bytes(vec![4])), + ); + + // when we resubmit tx for the second time, 1/3 of queue is skipped + context = context.notice_resubmitted_transaction(Hash::from([1; 32])); + assert_eq!( + select_transaction_from_queue(queued_transactions.clone(), &context), + Some(Bytes(vec![3])), + ); + + // when we resubmit tx for the third time, 1/5 of queue is skipped + context = context.notice_resubmitted_transaction(Hash::from([2; 32])); + assert_eq!( + select_transaction_from_queue(queued_transactions.clone(), &context), + Some(Bytes(vec![2])), + ); + + // when we resubmit tx for the second time, 1/9 of queue is skipped + context = context.notice_resubmitted_transaction(Hash::from([3; 32])); + assert_eq!( + select_transaction_from_queue(queued_transactions, &context), + Some(Bytes(vec![1])), + ); + } +} diff --git a/relays/bin-substrate/src/cli/send_message.rs b/relays/bin-substrate/src/cli/send_message.rs new file mode 100644 index 00000000000..d492d3422c7 --- /dev/null +++ b/relays/bin-substrate/src/cli/send_message.rs @@ -0,0 +1,195 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{ + chains::{ + millau_headers_to_rialto::MillauToRialtoCliBridge, + millau_headers_to_rialto_parachain::MillauToRialtoParachainCliBridge, + rialto_headers_to_millau::RialtoToMillauCliBridge, + rialto_parachains_to_millau::RialtoParachainToMillauCliBridge, + }, + cli::{ + bridge::{FullBridge, MessagesCliBridge}, + chain_schema::*, + encode_message::{self, CliEncodeMessage, RawMessage}, + CliChain, + }, +}; +use async_trait::async_trait; +use codec::{Decode, Encode}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, Chain, ChainBase, ChainWithTransactions, SignParam, + UnsignedTransaction, +}; +use sp_core::Pair; +use sp_runtime::AccountId32; +use std::fmt::Display; +use structopt::StructOpt; +use strum::VariantNames; + +/// Send bridge message. +#[derive(StructOpt)] +pub struct SendMessage { + /// A bridge instance to encode call for. + #[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)] + bridge: FullBridge, + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + source_sign: SourceSigningParams, + /// Message type. + #[structopt(subcommand)] + message: crate::cli::encode_message::Message, +} + +#[async_trait] +trait MessageSender: MessagesCliBridge +where + Self::Source: ChainBase + + ChainWithTransactions + + CliChain> + + CliEncodeMessage, + ::Balance: Display + From + Into, + ::Call: Sync, + ::SignedTransaction: Sync, + AccountIdOf: From< as Pair>::Public>, + AccountId32: From< as Pair>::Public>, +{ + async fn send_message(data: SendMessage) -> anyhow::Result<()> { + let payload = encode_message::encode_message::(&data.message)?; + + let source_client = data.source.into_client::().await?; + let source_sign = data.source_sign.to_keypair::()?; + + let payload_len = payload.encoded_size(); + let send_message_call = Self::Source::encode_send_xcm( + decode_xcm(payload)?, + data.bridge.bridge_instance_index(), + )?; + + let source_genesis_hash = *source_client.genesis_hash(); + let (spec_version, transaction_version) = source_client.simple_runtime_version().await?; + source_client + .submit_signed_extrinsic( + source_sign.public().into(), + SignParam:: { + spec_version, + transaction_version, + genesis_hash: source_genesis_hash, + signer: source_sign.clone(), + }, + move |_, transaction_nonce| { + let unsigned = UnsignedTransaction::new(send_message_call, transaction_nonce); + log::info!( + target: "bridge", + "Sending message to {}. Size: {}", + Self::Target::NAME, + payload_len, + ); + Ok(unsigned) + }, + ) + .await?; + + Ok(()) + } +} + +impl MessageSender for MillauToRialtoCliBridge {} +impl MessageSender for RialtoToMillauCliBridge {} +impl MessageSender for MillauToRialtoParachainCliBridge {} +impl MessageSender for RialtoParachainToMillauCliBridge {} + +impl SendMessage { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + match self.bridge { + FullBridge::MillauToRialto => MillauToRialtoCliBridge::send_message(self), + FullBridge::RialtoToMillau => RialtoToMillauCliBridge::send_message(self), + FullBridge::MillauToRialtoParachain => + MillauToRialtoParachainCliBridge::send_message(self), + FullBridge::RialtoParachainToMillau => + RialtoParachainToMillauCliBridge::send_message(self), + FullBridge::BridgeHubRococoToBridgeHubWococo => unimplemented!( + "Sending message from BridgeHubRococo to BridgeHubWococo is not supported" + ), + FullBridge::BridgeHubWococoToBridgeHubRococo => unimplemented!( + "Sending message from BridgeHubWococo to BridgeHubRococo is not supported" + ), + } + .await + } +} + +/// Decode SCALE encoded raw XCM message. +pub(crate) fn decode_xcm(message: RawMessage) -> anyhow::Result> { + Decode::decode(&mut &message[..]) + .map_err(|e| anyhow::format_err!("Failed to decode XCM program: {:?}", e)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::{ExplicitOrMaximal, HexBytes}; + + #[test] + fn send_raw_rialto_to_millau() { + // given + let send_message = SendMessage::from_iter(vec![ + "send-message", + "rialto-to-millau", + "--source-port", + "1234", + "--source-signer", + "//Alice", + "raw", + "dead", + ]); + + // then + assert_eq!(send_message.bridge, FullBridge::RialtoToMillau); + assert_eq!(send_message.source.source_port, 1234); + assert_eq!(send_message.source_sign.source_signer, Some("//Alice".into())); + assert_eq!( + send_message.message, + crate::cli::encode_message::Message::Raw { data: HexBytes(vec![0xDE, 0xAD]) } + ); + } + + #[test] + fn send_sized_rialto_to_millau() { + // given + let send_message = SendMessage::from_iter(vec![ + "send-message", + "rialto-to-millau", + "--source-port", + "1234", + "--source-signer", + "//Alice", + "sized", + "max", + ]); + + // then + assert_eq!(send_message.bridge, FullBridge::RialtoToMillau); + assert_eq!(send_message.source.source_port, 1234); + assert_eq!(send_message.source_sign.source_signer, Some("//Alice".into())); + assert_eq!( + send_message.message, + crate::cli::encode_message::Message::Sized { size: ExplicitOrMaximal::Maximal } + ); + } +} diff --git a/relays/bin-substrate/src/main.rs b/relays/bin-substrate/src/main.rs new file mode 100644 index 00000000000..bc84786ee27 --- /dev/null +++ b/relays/bin-substrate/src/main.rs @@ -0,0 +1,31 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate-to-substrate relay entrypoint. + +#![warn(missing_docs)] + +mod chains; +mod cli; + +fn main() { + let command = cli::parse_args(); + let run = command.run(); + let result = async_std::task::block_on(run); + if let Err(error) = result { + log::error!(target: "bridge", "substrate-relay: {}", error); + } +} diff --git a/relays/client-bridge-hub-rococo/Cargo.toml b/relays/client-bridge-hub-rococo/Cargo.toml new file mode 100644 index 00000000000..4a01d81e777 --- /dev/null +++ b/relays/client-bridge-hub-rococo/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "relay-bridge-hub-rococo-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +relay-substrate-client = { path = "../client-substrate" } + +# Bridge dependencies + +bp-bridge-hub-rococo = { path = "../../primitives/chain-bridge-hub-rococo" } +bp-bridge-hub-wococo = { path = "../../primitives/chain-bridge-hub-wococo" } +bp-header-chain = { path = "../../primitives/header-chain" } +bp-messages = { path = "../../primitives/messages" } +bp-polkadot-core = { path = "../../primitives/polkadot-core" } +bp-runtime = { path = "../../primitives/runtime" } + +bridge-runtime-common = { path = "../../bin/runtime-common" } +# Substrate Dependencies + +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[dev-dependencies] +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-bridge-hub-rococo/src/lib.rs b/relays/client-bridge-hub-rococo/src/lib.rs new file mode 100644 index 00000000000..e4740490cb5 --- /dev/null +++ b/relays/client-bridge-hub-rococo/src/lib.rs @@ -0,0 +1,165 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the BridgeHub-Rococo-Substrate parachain. + +use bp_messages::{MessageNonce, Weight}; +use codec::Encode; +use relay_substrate_client::{ + Chain, ChainBase, ChainWithMessages, ChainWithTransactions, Error as SubstrateError, SignParam, + UnsignedTransaction, +}; +use sp_core::Pair; +use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; +use std::time::Duration; + +/// Re-export runtime wrapper +pub mod runtime_wrapper; +pub use runtime_wrapper as runtime; + +/// Rococo chain definition +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BridgeHubRococo; + +impl ChainBase for BridgeHubRococo { + type BlockNumber = bp_bridge_hub_rococo::BlockNumber; + type Hash = bp_bridge_hub_rococo::Hash; + type Hasher = bp_bridge_hub_rococo::Hashing; + type Header = bp_bridge_hub_rococo::Header; + + type AccountId = bp_bridge_hub_rococo::AccountId; + type Balance = bp_bridge_hub_rococo::Balance; + type Index = bp_bridge_hub_rococo::Nonce; + type Signature = bp_bridge_hub_rococo::Signature; + + fn max_extrinsic_size() -> u32 { + bp_bridge_hub_rococo::BridgeHubRococo::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_bridge_hub_rococo::BridgeHubRococo::max_extrinsic_weight() + } +} + +impl Chain for BridgeHubRococo { + const NAME: &'static str = "BridgeHubRococo"; + const TOKEN_ID: Option<&'static str> = None; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_bridge_hub_rococo::BEST_FINALIZED_BRIDGE_HUB_ROCOCO_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type SignedBlock = bp_bridge_hub_rococo::SignedBlock; + type Call = runtime::Call; +} + +impl ChainWithTransactions for BridgeHubRococo { + type AccountKeyPair = sp_core::sr25519::Pair; + type SignedTransaction = runtime::UncheckedExtrinsic; + + fn sign_transaction( + param: SignParam, + unsigned: UnsignedTransaction, + ) -> Result { + let raw_payload = SignedPayload::new( + unsigned.call, + bp_bridge_hub_rococo::SignedExtensions::new( + param.spec_version, + param.transaction_version, + unsigned.era, + param.genesis_hash, + unsigned.nonce, + unsigned.tip, + ), + )?; + + let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload)); + let signer: sp_runtime::MultiSigner = param.signer.public().into(); + let (call, extra, _) = raw_payload.deconstruct(); + + Ok(bp_bridge_hub_rococo::UncheckedExtrinsic::new_signed( + call, + signer.into_account().into(), + signature.into(), + extra, + )) + } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| { + *address == bp_bridge_hub_rococo::Address::Id(signer.public().into()) + }) + .unwrap_or(false) + } + + fn parse_transaction(tx: Self::SignedTransaction) -> Option> { + let extra = &tx.signature.as_ref()?.2; + Some(UnsignedTransaction::new(tx.function, extra.nonce()).tip(extra.tip())) + } +} + +impl ChainWithMessages for BridgeHubRococo { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + bp_bridge_hub_rococo::WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME; + + const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_bridge_hub_rococo::TO_BRIDGE_HUB_ROCOCO_MESSAGE_DETAILS_METHOD; + const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_bridge_hub_rococo::FROM_BRIDGE_HUB_ROCOCO_MESSAGE_DETAILS_METHOD; + + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + bp_bridge_hub_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + + type WeightToFee = bp_bridge_hub_rococo::WeightToFee; + // TODO: fix (https://github.com/paritytech/parity-bridges-common/issues/1640) + type WeightInfo = (); +} + +#[cfg(test)] +mod tests { + use super::*; + use relay_substrate_client::TransactionEra; + + #[test] + fn parse_transaction_works() { + let unsigned = UnsignedTransaction { + call: runtime::Call::System(runtime::SystemCall::remark(b"Hello world!".to_vec())) + .into(), + nonce: 777, + tip: 888, + era: TransactionEra::immortal(), + }; + let signed_transaction = BridgeHubRococo::sign_transaction( + SignParam { + spec_version: 42, + transaction_version: 50000, + genesis_hash: [42u8; 32].into(), + signer: sp_core::sr25519::Pair::from_seed_slice(&[1u8; 32]).unwrap(), + }, + unsigned.clone(), + ) + .unwrap(); + let parsed_transaction = BridgeHubRococo::parse_transaction(signed_transaction).unwrap(); + assert_eq!(parsed_transaction, unsigned); + } +} diff --git a/relays/client-bridge-hub-rococo/src/runtime_wrapper.rs b/relays/client-bridge-hub-rococo/src/runtime_wrapper.rs new file mode 100644 index 00000000000..6f83257cf4d --- /dev/null +++ b/relays/client-bridge-hub-rococo/src/runtime_wrapper.rs @@ -0,0 +1,219 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +// TODO: join with primitives do we need this here or move to the primitives? + +//! Types that are specific to the BridgeHubRococo runtime. + +use bp_polkadot_core::PolkadotLike; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +pub use bp_bridge_hub_rococo::SS58Prefix; +use bp_messages::UnrewardedRelayersState; +use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; +use bp_runtime::Chain; + +// TODO:check-parameter - check SignedExtension +/// Unchecked BridgeHubRococo extrinsic. +pub type UncheckedExtrinsic = bp_bridge_hub_rococo::UncheckedExtrinsic; + +/// Rococo Runtime `Call` enum. +/// +/// The enum represents a subset of possible `Call`s we can send to Rococo chain. +/// Ideally this code would be auto-generated from metadata, because we want to +/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s. +/// +/// All entries here (like pretty much in the entire file) must be kept in sync with Rococo +/// `construct_runtime`, so that we maintain SCALE-compatibility. +/// +/// // TODO:check-parameter -> change bko-bridge-rococo-wococo when merged to master in cumulus +/// See: [link](https://github.com/paritytech/cumulus/blob/bko-bridge-rococo-wococo/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs) +#[allow(clippy::large_enum_variant)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +pub enum Call { + /// System pallet. + #[codec(index = 0)] + System(SystemCall), + /// Wococo bridge pallet. + #[codec(index = 41)] + BridgeWococoGrandpa(BridgeWococoGrandpaCall), + /// Rococo bridge pallet. + #[codec(index = 43)] + BridgeRococoGrandpa(BridgeRococoGrandpaCall), + + /// Wococo parachain bridge pallet. + #[codec(index = 42)] + BridgeWococoParachain(BridgeParachainCall), + /// Rococo parachain bridge pallet. + #[codec(index = 44)] + BridgeRococoParachain(BridgeParachainCall), + + /// Wococo messages bridge pallet. + #[codec(index = 46)] + BridgeWococoMessages(BridgeWococoMessagesCall), + /// Rococo messages bridge pallet. + #[codec(index = 45)] + BridgeRococoMessages(BridgeRococoMessagesCall), +} + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum SystemCall { + #[codec(index = 1)] + remark(Vec), +} + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum BridgeWococoGrandpaCall { + #[codec(index = 0)] + submit_finality_proof( + Box<::Header>, + bp_header_chain::justification::GrandpaJustification<::Header>, + ), + #[codec(index = 1)] + initialize(bp_header_chain::InitializationData<::Header>), +} + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum BridgeRococoGrandpaCall { + #[codec(index = 0)] + submit_finality_proof( + Box<::Header>, + bp_header_chain::justification::GrandpaJustification<::Header>, + ), + #[codec(index = 1)] + initialize(bp_header_chain::InitializationData<::Header>), +} + +pub type RelayBlockHash = bp_polkadot_core::Hash; +pub type RelayBlockNumber = bp_polkadot_core::BlockNumber; + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum BridgeParachainCall { + #[codec(index = 0)] + submit_parachain_heads( + (RelayBlockNumber, RelayBlockHash), + Vec<(ParaId, ParaHash)>, + ParaHeadsProof, + ), +} + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum BridgeWococoMessagesCall { + #[codec(index = 2)] + receive_messages_proof( + relay_substrate_client::AccountIdOf, + bridge_runtime_common::messages::target::FromBridgedChainMessagesProof< + relay_substrate_client::HashOf, + >, + u32, + bp_messages::Weight, + ), + + #[codec(index = 3)] + receive_messages_delivery_proof( + bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof< + relay_substrate_client::HashOf, + >, + UnrewardedRelayersState, + ), +} + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[allow(non_camel_case_types)] +pub enum BridgeRococoMessagesCall { + #[codec(index = 2)] + receive_messages_proof( + relay_substrate_client::AccountIdOf, + bridge_runtime_common::messages::target::FromBridgedChainMessagesProof< + relay_substrate_client::HashOf, + >, + u32, + bp_messages::Weight, + ), + + #[codec(index = 3)] + receive_messages_delivery_proof( + bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof< + relay_substrate_client::HashOf, + >, + UnrewardedRelayersState, + ), +} + +impl sp_runtime::traits::Dispatchable for Call { + type RuntimeOrigin = (); + type Config = (); + type Info = (); + type PostInfo = (); + + fn dispatch( + self, + _origin: Self::RuntimeOrigin, + ) -> sp_runtime::DispatchResultWithInfo { + unimplemented!("The Call is not expected to be dispatched.") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bp_runtime::BasicOperatingMode; + use sp_core::hexdisplay::HexDisplay; + use sp_finality_grandpa::AuthorityList; + use sp_runtime::traits::Header; + use std::str::FromStr; + + pub type RelayBlockHasher = bp_polkadot_core::Hasher; + pub type RelayBlockHeader = sp_runtime::generic::Header; + + #[test] + fn encode_decode_calls() { + let header = RelayBlockHeader::new( + 75, + bp_polkadot_core::Hash::from_str( + "0xd2c0afaab32de0cb8f7f0d89217e37c5ea302c1ffb5a7a83e10d20f12c32874d", + ) + .expect("invalid value"), + bp_polkadot_core::Hash::from_str( + "0x92b965f0656a4e0e5fc0167da2d4b5ee72b3be2c1583c4c1e5236c8c12aa141b", + ) + .expect("invalid value"), + bp_polkadot_core::Hash::from_str( + "0xae4a25acf250d72ed02c149ecc7dd3c9ee976d41a2888fc551de8064521dc01d", + ) + .expect("invalid value"), + Default::default(), + ); + let init_data = bp_header_chain::InitializationData { + header: Box::new(header), + authority_list: AuthorityList::default(), + set_id: 6, + operating_mode: BasicOperatingMode::Normal, + }; + let call = BridgeRococoGrandpaCall::initialize(init_data); + let tx = Call::BridgeRococoGrandpa(call); + + // encode call as hex string + let hex_encoded_call = format!("0x{:?}", HexDisplay::from(&Encode::encode(&tx))); + assert_eq!(hex_encoded_call, "0x2b01ae4a25acf250d72ed02c149ecc7dd3c9ee976d41a2888fc551de8064521dc01d2d0192b965f0656a4e0e5fc0167da2d4b5ee72b3be2c1583c4c1e5236c8c12aa141bd2c0afaab32de0cb8f7f0d89217e37c5ea302c1ffb5a7a83e10d20f12c32874d0000060000000000000000"); + } +} diff --git a/relays/client-bridge-hub-wococo/Cargo.toml b/relays/client-bridge-hub-wococo/Cargo.toml new file mode 100644 index 00000000000..fa28dfde202 --- /dev/null +++ b/relays/client-bridge-hub-wococo/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "relay-bridge-hub-wococo-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +relay-substrate-client = { path = "../client-substrate" } + +# Bridge dependencies + +bp-bridge-hub-wococo = { path = "../../primitives/chain-bridge-hub-wococo" } +bp-messages = { path = "../../primitives/messages" } +relay-bridge-hub-rococo-client = { path = "../client-bridge-hub-rococo" } + +# Substrate Dependencies + +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-bridge-hub-wococo/src/lib.rs b/relays/client-bridge-hub-wococo/src/lib.rs new file mode 100644 index 00000000000..3199b3b4fc1 --- /dev/null +++ b/relays/client-bridge-hub-wococo/src/lib.rs @@ -0,0 +1,165 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the BridgeHub-Wococo-Substrate parachain. + +use bp_messages::{MessageNonce, Weight}; +use codec::Encode; +use relay_substrate_client::{ + Chain, ChainBase, ChainWithMessages, ChainWithTransactions, Error as SubstrateError, SignParam, + UnsignedTransaction, +}; +use sp_core::Pair; +use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; +use std::time::Duration; + +/// Re-export runtime wrapper +pub mod runtime_wrapper; +pub use runtime_wrapper as runtime; + +/// Wococo chain definition +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BridgeHubWococo; + +impl ChainBase for BridgeHubWococo { + type BlockNumber = bp_bridge_hub_wococo::BlockNumber; + type Hash = bp_bridge_hub_wococo::Hash; + type Hasher = bp_bridge_hub_wococo::Hashing; + type Header = bp_bridge_hub_wococo::Header; + + type AccountId = bp_bridge_hub_wococo::AccountId; + type Balance = bp_bridge_hub_wococo::Balance; + type Index = bp_bridge_hub_wococo::Nonce; + type Signature = bp_bridge_hub_wococo::Signature; + + fn max_extrinsic_size() -> u32 { + bp_bridge_hub_wococo::BridgeHubWococo::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_bridge_hub_wococo::BridgeHubWococo::max_extrinsic_weight() + } +} + +impl Chain for BridgeHubWococo { + const NAME: &'static str = "BridgeHubWococo"; + const TOKEN_ID: Option<&'static str> = None; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_bridge_hub_wococo::BEST_FINALIZED_BRIDGE_HUB_WOCOCO_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type SignedBlock = bp_bridge_hub_wococo::SignedBlock; + type Call = runtime::Call; +} + +impl ChainWithTransactions for BridgeHubWococo { + type AccountKeyPair = sp_core::sr25519::Pair; + type SignedTransaction = runtime::UncheckedExtrinsic; + + fn sign_transaction( + param: SignParam, + unsigned: UnsignedTransaction, + ) -> Result { + let raw_payload = SignedPayload::new( + unsigned.call, + bp_bridge_hub_wococo::SignedExtensions::new( + param.spec_version, + param.transaction_version, + unsigned.era, + param.genesis_hash, + unsigned.nonce, + unsigned.tip, + ), + )?; + + let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload)); + let signer: sp_runtime::MultiSigner = param.signer.public().into(); + let (call, extra, _) = raw_payload.deconstruct(); + + Ok(bp_bridge_hub_wococo::UncheckedExtrinsic::new_signed( + call, + signer.into_account().into(), + signature.into(), + extra, + )) + } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| { + *address == bp_bridge_hub_wococo::Address::Id(signer.public().into()) + }) + .unwrap_or(false) + } + + fn parse_transaction(tx: Self::SignedTransaction) -> Option> { + let extra = &tx.signature.as_ref()?.2; + Some(UnsignedTransaction::new(tx.function, extra.nonce()).tip(extra.tip())) + } +} + +impl ChainWithMessages for BridgeHubWococo { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + bp_bridge_hub_wococo::WITH_BRIDGE_HUB_WOCOCO_MESSAGES_PALLET_NAME; + + const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_bridge_hub_wococo::TO_BRIDGE_HUB_WOCOCO_MESSAGE_DETAILS_METHOD; + const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_bridge_hub_wococo::FROM_BRIDGE_HUB_WOCOCO_MESSAGE_DETAILS_METHOD; + + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + bp_bridge_hub_wococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + bp_bridge_hub_wococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + + type WeightToFee = bp_bridge_hub_wococo::WeightToFee; + // TODO: fix (https://github.com/paritytech/parity-bridges-common/issues/1640) + type WeightInfo = (); +} + +#[cfg(test)] +mod tests { + use super::*; + use relay_substrate_client::TransactionEra; + + #[test] + fn parse_transaction_works() { + let unsigned = UnsignedTransaction { + call: runtime::Call::System(runtime::SystemCall::remark(b"Hello world!".to_vec())) + .into(), + nonce: 777, + tip: 888, + era: TransactionEra::immortal(), + }; + let signed_transaction = BridgeHubWococo::sign_transaction( + SignParam { + spec_version: 42, + transaction_version: 50000, + genesis_hash: [42u8; 32].into(), + signer: sp_core::sr25519::Pair::from_seed_slice(&[1u8; 32]).unwrap(), + }, + unsigned.clone(), + ) + .unwrap(); + let parsed_transaction = BridgeHubWococo::parse_transaction(signed_transaction).unwrap(); + assert_eq!(parsed_transaction, unsigned); + } +} diff --git a/relays/client-bridge-hub-wococo/src/runtime_wrapper.rs b/relays/client-bridge-hub-wococo/src/runtime_wrapper.rs new file mode 100644 index 00000000000..8d94e7f9026 --- /dev/null +++ b/relays/client-bridge-hub-wococo/src/runtime_wrapper.rs @@ -0,0 +1,28 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types that are specific to the BridgeHubWococo runtime. + +pub use bp_bridge_hub_wococo::SS58Prefix; + +// We reuse everything from rococo runtime wrapper +pub type Call = relay_bridge_hub_rococo_client::runtime::Call; +pub type UncheckedExtrinsic = bp_bridge_hub_wococo::UncheckedExtrinsic; +pub type BridgeGrandpaRococoCall = relay_bridge_hub_rococo_client::runtime::BridgeRococoGrandpaCall; +pub type BridgeParachainCall = relay_bridge_hub_rococo_client::runtime::BridgeParachainCall; +pub type BridgeRococoMessagesCall = + relay_bridge_hub_rococo_client::runtime::BridgeRococoMessagesCall; +pub type SystemCall = relay_bridge_hub_rococo_client::runtime::SystemCall; diff --git a/relays/client-kusama/Cargo.toml b/relays/client-kusama/Cargo.toml new file mode 100644 index 00000000000..2efe62d8ec8 --- /dev/null +++ b/relays/client-kusama/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "relay-kusama-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-kusama = { path = "../../primitives/chain-kusama" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-kusama/src/lib.rs b/relays/client-kusama/src/lib.rs new file mode 100644 index 00000000000..355f26b8496 --- /dev/null +++ b/relays/client-kusama/src/lib.rs @@ -0,0 +1,73 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the Kusama chain. + +use frame_support::weights::Weight; +use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, ChainWithGrandpa}; +use sp_core::storage::StorageKey; +use std::time::Duration; + +/// Kusama header id. +pub type HeaderId = relay_utils::HeaderId; + +/// Kusama chain definition +#[derive(Debug, Clone, Copy)] +pub struct Kusama; + +impl ChainBase for Kusama { + type BlockNumber = bp_kusama::BlockNumber; + type Hash = bp_kusama::Hash; + type Hasher = bp_kusama::Hasher; + type Header = bp_kusama::Header; + + type AccountId = bp_kusama::AccountId; + type Balance = bp_kusama::Balance; + type Index = bp_kusama::Nonce; + type Signature = bp_kusama::Signature; + + fn max_extrinsic_size() -> u32 { + bp_kusama::Kusama::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_kusama::Kusama::max_extrinsic_weight() + } +} + +impl Chain for Kusama { + const NAME: &'static str = "Kusama"; + const TOKEN_ID: Option<&'static str> = Some("kusama"); + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type SignedBlock = bp_kusama::SignedBlock; + type Call = (); +} + +impl ChainWithGrandpa for Kusama { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_kusama::WITH_KUSAMA_GRANDPA_PALLET_NAME; +} + +impl ChainWithBalances for Kusama { + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey { + StorageKey(bp_kusama::account_info_storage_key(account_id)) + } +} + +/// Kusama header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; diff --git a/relays/client-millau/Cargo.toml b/relays/client-millau/Cargo.toml new file mode 100644 index 00000000000..66395d5de89 --- /dev/null +++ b/relays/client-millau/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "relay-millau-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5" } +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } + +# Supported Chains + +bp-messages = { path = "../../primitives/messages" } +bp-millau = { path = "../../primitives/chain-millau" } +millau-runtime = { path = "../../bin/millau/runtime" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-millau/src/lib.rs b/relays/client-millau/src/lib.rs new file mode 100644 index 00000000000..f8b350d3d0c --- /dev/null +++ b/relays/client-millau/src/lib.rs @@ -0,0 +1,202 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the Millau-Substrate chain. + +use bp_messages::MessageNonce; +use codec::{Compact, Decode, Encode}; +use frame_support::weights::Weight; +use relay_substrate_client::{ + BalanceOf, Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, ChainWithMessages, + ChainWithTransactions, Error as SubstrateError, IndexOf, SignParam, UnsignedTransaction, +}; +use sp_core::{storage::StorageKey, Pair}; +use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; +use std::time::Duration; + +/// Millau header id. +pub type HeaderId = relay_utils::HeaderId; + +/// Millau chain definition. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Millau; + +impl ChainBase for Millau { + type BlockNumber = millau_runtime::BlockNumber; + type Hash = millau_runtime::Hash; + type Hasher = millau_runtime::Hashing; + type Header = millau_runtime::Header; + + type AccountId = millau_runtime::AccountId; + type Balance = millau_runtime::Balance; + type Index = millau_runtime::Index; + type Signature = millau_runtime::Signature; + + fn max_extrinsic_size() -> u32 { + bp_millau::Millau::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_millau::Millau::max_extrinsic_weight() + } +} + +impl ChainWithGrandpa for Millau { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_millau::WITH_MILLAU_GRANDPA_PALLET_NAME; +} + +impl ChainWithMessages for Millau { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + bp_millau::WITH_MILLAU_MESSAGES_PALLET_NAME; + const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_millau::TO_MILLAU_MESSAGE_DETAILS_METHOD; + const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_millau::FROM_MILLAU_MESSAGE_DETAILS_METHOD; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + bp_millau::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + bp_millau::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + type WeightToFee = bp_millau::WeightToFee; + type WeightInfo = (); +} + +impl Chain for Millau { + const NAME: &'static str = "Millau"; + // Rialto token has no value, but we associate it with KSM token + const TOKEN_ID: Option<&'static str> = Some("kusama"); + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5); + + type SignedBlock = millau_runtime::SignedBlock; + type Call = millau_runtime::RuntimeCall; +} + +impl ChainWithBalances for Millau { + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey { + use frame_support::storage::generator::StorageMap; + StorageKey(frame_system::Account::::storage_map_final_key( + account_id, + )) + } +} + +impl ChainWithTransactions for Millau { + type AccountKeyPair = sp_core::sr25519::Pair; + type SignedTransaction = millau_runtime::UncheckedExtrinsic; + + fn sign_transaction( + param: SignParam, + unsigned: UnsignedTransaction, + ) -> Result { + let raw_payload = SignedPayload::from_raw( + unsigned.call.clone(), + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(unsigned.era.frame_era()), + frame_system::CheckNonce::::from(unsigned.nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(unsigned.tip), + millau_runtime::BridgeRejectObsoleteHeadersAndMessages, + ), + ( + (), + param.spec_version, + param.transaction_version, + param.genesis_hash, + unsigned.era.signed_payload(param.genesis_hash), + (), + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload)); + let signer: sp_runtime::MultiSigner = param.signer.public().into(); + let (call, extra, _) = raw_payload.deconstruct(); + + Ok(millau_runtime::UncheckedExtrinsic::new_signed( + call.into_decoded()?, + signer.into_account(), + signature.into(), + extra, + )) + } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| { + *address == millau_runtime::Address::from(*signer.public().as_array_ref()) + }) + .unwrap_or(false) + } + + fn parse_transaction(tx: Self::SignedTransaction) -> Option> { + let extra = &tx.signature.as_ref()?.2; + Some( + UnsignedTransaction::new( + tx.function.into(), + Compact::>::decode(&mut &extra.5.encode()[..]).ok()?.into(), + ) + .tip(Compact::>::decode(&mut &extra.7.encode()[..]).ok()?.into()), + ) + } +} + +/// Millau signing params. +pub type SigningParams = sp_core::sr25519::Pair; + +/// Millau header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; + +#[cfg(test)] +mod tests { + use super::*; + use relay_substrate_client::TransactionEra; + + #[test] + fn parse_transaction_works() { + let unsigned = UnsignedTransaction { + call: millau_runtime::RuntimeCall::System(millau_runtime::SystemCall::remark { + remark: b"Hello world!".to_vec(), + }) + .into(), + nonce: 777, + tip: 888, + era: TransactionEra::immortal(), + }; + let signed_transaction = Millau::sign_transaction( + SignParam { + spec_version: 42, + transaction_version: 50000, + genesis_hash: [42u8; 64].into(), + signer: sp_core::sr25519::Pair::from_seed_slice(&[1u8; 32]).unwrap(), + }, + unsigned.clone(), + ) + .unwrap(); + let parsed_transaction = Millau::parse_transaction(signed_transaction).unwrap(); + assert_eq!(parsed_transaction, unsigned); + } +} diff --git a/relays/client-polkadot/Cargo.toml b/relays/client-polkadot/Cargo.toml new file mode 100644 index 00000000000..aefbadfdd18 --- /dev/null +++ b/relays/client-polkadot/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "relay-polkadot-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-polkadot = { path = "../../primitives/chain-polkadot" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-polkadot/src/lib.rs b/relays/client-polkadot/src/lib.rs new file mode 100644 index 00000000000..101b62b9c35 --- /dev/null +++ b/relays/client-polkadot/src/lib.rs @@ -0,0 +1,74 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the Polkadot chain. + +use frame_support::weights::Weight; +use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, ChainWithGrandpa}; +use sp_core::storage::StorageKey; +use std::time::Duration; + +/// Polkadot header id. +pub type HeaderId = relay_utils::HeaderId; + +/// Polkadot chain definition +#[derive(Debug, Clone, Copy)] +pub struct Polkadot; + +impl ChainBase for Polkadot { + type BlockNumber = bp_polkadot::BlockNumber; + type Hash = bp_polkadot::Hash; + type Hasher = bp_polkadot::Hasher; + type Header = bp_polkadot::Header; + + type AccountId = bp_polkadot::AccountId; + type Balance = bp_polkadot::Balance; + type Index = bp_polkadot::Nonce; + type Signature = bp_polkadot::Signature; + + fn max_extrinsic_size() -> u32 { + bp_polkadot::Polkadot::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_polkadot::Polkadot::max_extrinsic_weight() + } +} + +impl Chain for Polkadot { + const NAME: &'static str = "Polkadot"; + const TOKEN_ID: Option<&'static str> = Some("polkadot"); + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type SignedBlock = bp_polkadot::SignedBlock; + type Call = (); +} + +impl ChainWithGrandpa for Polkadot { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = + bp_polkadot::WITH_POLKADOT_GRANDPA_PALLET_NAME; +} + +impl ChainWithBalances for Polkadot { + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey { + StorageKey(bp_polkadot::account_info_storage_key(account_id)) + } +} + +/// Polkadot header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; diff --git a/relays/client-rialto-parachain/Cargo.toml b/relays/client-rialto-parachain/Cargo.toml new file mode 100644 index 00000000000..915d0e786cc --- /dev/null +++ b/relays/client-rialto-parachain/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "relay-rialto-parachain-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5" } +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-messages = { path = "../../primitives/messages" } +bp-rialto-parachain = { path = "../../primitives/chain-rialto-parachain" } +rialto-parachain-runtime = { path = "../../bin/rialto-parachain/runtime" } + +# Substrate Dependencies + +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-rialto-parachain/src/lib.rs b/relays/client-rialto-parachain/src/lib.rs new file mode 100644 index 00000000000..8cf74bdf9b1 --- /dev/null +++ b/relays/client-rialto-parachain/src/lib.rs @@ -0,0 +1,165 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the Rialto-Substrate chain. + +use bp_messages::MessageNonce; +use codec::Encode; +use frame_support::weights::Weight; +use relay_substrate_client::{ + Chain, ChainBase, ChainWithBalances, ChainWithMessages, ChainWithTransactions, + Error as SubstrateError, SignParam, UnsignedTransaction, +}; +use sp_core::{storage::StorageKey, Pair}; +use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; +use std::time::Duration; + +/// Rialto header id. +pub type HeaderId = + relay_utils::HeaderId; + +/// Rialto parachain definition +#[derive(Debug, Clone, Copy)] +pub struct RialtoParachain; + +impl ChainBase for RialtoParachain { + type BlockNumber = rialto_parachain_runtime::BlockNumber; + type Hash = rialto_parachain_runtime::Hash; + type Hasher = rialto_parachain_runtime::Hashing; + type Header = rialto_parachain_runtime::Header; + + type AccountId = rialto_parachain_runtime::AccountId; + type Balance = rialto_parachain_runtime::Balance; + type Index = rialto_parachain_runtime::Index; + type Signature = rialto_parachain_runtime::Signature; + + fn max_extrinsic_size() -> u32 { + bp_rialto_parachain::RialtoParachain::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_rialto_parachain::RialtoParachain::max_extrinsic_weight() + } +} + +impl Chain for RialtoParachain { + const NAME: &'static str = "RialtoParachain"; + // RialtoParachain token has no value, but we associate it with DOT token + const TOKEN_ID: Option<&'static str> = Some("polkadot"); + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_rialto_parachain::BEST_FINALIZED_RIALTO_PARACHAIN_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5); + + type SignedBlock = rialto_parachain_runtime::SignedBlock; + type Call = rialto_parachain_runtime::RuntimeCall; +} + +impl ChainWithBalances for RialtoParachain { + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey { + use frame_support::storage::generator::StorageMap; + StorageKey( + frame_system::Account::::storage_map_final_key( + account_id, + ), + ) + } +} + +impl ChainWithMessages for RialtoParachain { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + bp_rialto_parachain::WITH_RIALTO_PARACHAIN_MESSAGES_PALLET_NAME; + const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_rialto_parachain::TO_RIALTO_PARACHAIN_MESSAGE_DETAILS_METHOD; + const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_rialto_parachain::FROM_RIALTO_PARACHAIN_MESSAGE_DETAILS_METHOD; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + bp_rialto_parachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + bp_rialto_parachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + type WeightToFee = bp_rialto_parachain::WeightToFee; + type WeightInfo = (); +} + +impl ChainWithTransactions for RialtoParachain { + type AccountKeyPair = sp_core::sr25519::Pair; + type SignedTransaction = rialto_parachain_runtime::UncheckedExtrinsic; + + fn sign_transaction( + param: SignParam, + unsigned: UnsignedTransaction, + ) -> Result { + let raw_payload = SignedPayload::from_raw( + unsigned.call, + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from( + unsigned.era.frame_era(), + ), + frame_system::CheckNonce::::from(unsigned.nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::< + rialto_parachain_runtime::Runtime, + >::from(unsigned.tip), + ), + ( + (), + param.spec_version, + param.transaction_version, + param.genesis_hash, + unsigned.era.signed_payload(param.genesis_hash), + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload)); + let signer: sp_runtime::MultiSigner = param.signer.public().into(); + let (call, extra, _) = raw_payload.deconstruct(); + + Ok(rialto_parachain_runtime::UncheckedExtrinsic::new_signed( + call.into_decoded()?, + signer.into_account().into(), + signature.into(), + extra, + )) + } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| { + *address == rialto_parachain_runtime::Address::Id(signer.public().into()) + }) + .unwrap_or(false) + } + + fn parse_transaction(_tx: Self::SignedTransaction) -> Option> { + None + } +} + +/// RialtoParachain signing params. +pub type SigningParams = sp_core::sr25519::Pair; + +/// RialtoParachain header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; diff --git a/relays/client-rialto/Cargo.toml b/relays/client-rialto/Cargo.toml new file mode 100644 index 00000000000..ef339feb8a5 --- /dev/null +++ b/relays/client-rialto/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "relay-rialto-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5" } +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-messages = { path = "../../primitives/messages" } +bp-rialto = { path = "../../primitives/chain-rialto" } +rialto-runtime = { path = "../../bin/rialto/runtime" } + +# Substrate Dependencies + +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-rialto/src/lib.rs b/relays/client-rialto/src/lib.rs new file mode 100644 index 00000000000..23ecbbd47f6 --- /dev/null +++ b/relays/client-rialto/src/lib.rs @@ -0,0 +1,205 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the Rialto-Substrate chain. + +use bp_messages::MessageNonce; +use codec::{Compact, Decode, Encode}; +use frame_support::weights::Weight; +use relay_substrate_client::{ + BalanceOf, Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, ChainWithMessages, + ChainWithTransactions, Error as SubstrateError, IndexOf, RelayChain, SignParam, + UnsignedTransaction, +}; +use sp_core::{storage::StorageKey, Pair}; +use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; +use std::time::Duration; + +/// Rialto header id. +pub type HeaderId = relay_utils::HeaderId; + +/// Rialto chain definition +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rialto; + +impl ChainBase for Rialto { + type BlockNumber = rialto_runtime::BlockNumber; + type Hash = rialto_runtime::Hash; + type Hasher = rialto_runtime::Hashing; + type Header = rialto_runtime::Header; + + type AccountId = rialto_runtime::AccountId; + type Balance = rialto_runtime::Balance; + type Index = rialto_runtime::Index; + type Signature = rialto_runtime::Signature; + + fn max_extrinsic_size() -> u32 { + bp_rialto::Rialto::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_rialto::Rialto::max_extrinsic_weight() + } +} + +impl Chain for Rialto { + const NAME: &'static str = "Rialto"; + // Rialto token has no value, but we associate it with DOT token + const TOKEN_ID: Option<&'static str> = Some("polkadot"); + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5); + + type SignedBlock = rialto_runtime::SignedBlock; + type Call = rialto_runtime::RuntimeCall; +} + +impl RelayChain for Rialto { + const PARAS_PALLET_NAME: &'static str = bp_rialto::PARAS_PALLET_NAME; + const PARACHAINS_FINALITY_PALLET_NAME: &'static str = + bp_rialto::WITH_RIALTO_BRIDGE_PARAS_PALLET_NAME; +} + +impl ChainWithGrandpa for Rialto { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_rialto::WITH_RIALTO_GRANDPA_PALLET_NAME; +} + +impl ChainWithMessages for Rialto { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + bp_rialto::WITH_RIALTO_MESSAGES_PALLET_NAME; + const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_rialto::TO_RIALTO_MESSAGE_DETAILS_METHOD; + const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = + bp_rialto::FROM_RIALTO_MESSAGE_DETAILS_METHOD; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + bp_rialto::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + type WeightToFee = bp_rialto::WeightToFee; + type WeightInfo = (); +} + +impl ChainWithBalances for Rialto { + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey { + use frame_support::storage::generator::StorageMap; + StorageKey(frame_system::Account::::storage_map_final_key( + account_id, + )) + } +} + +impl ChainWithTransactions for Rialto { + type AccountKeyPair = sp_core::sr25519::Pair; + type SignedTransaction = rialto_runtime::UncheckedExtrinsic; + + fn sign_transaction( + param: SignParam, + unsigned: UnsignedTransaction, + ) -> Result { + let raw_payload = SignedPayload::from_raw( + unsigned.call.clone(), + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(unsigned.era.frame_era()), + frame_system::CheckNonce::::from(unsigned.nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(unsigned.tip), + ), + ( + (), + param.spec_version, + param.transaction_version, + param.genesis_hash, + unsigned.era.signed_payload(param.genesis_hash), + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload)); + let signer: sp_runtime::MultiSigner = param.signer.public().into(); + let (call, extra, _) = raw_payload.deconstruct(); + + Ok(rialto_runtime::UncheckedExtrinsic::new_signed( + call.into_decoded()?, + signer.into_account().into(), + signature.into(), + extra, + )) + } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| *address == rialto_runtime::Address::Id(signer.public().into())) + .unwrap_or(false) + } + + fn parse_transaction(tx: Self::SignedTransaction) -> Option> { + let extra = &tx.signature.as_ref()?.2; + Some( + UnsignedTransaction::new( + tx.function.into(), + Compact::>::decode(&mut &extra.5.encode()[..]).ok()?.into(), + ) + .tip(Compact::>::decode(&mut &extra.7.encode()[..]).ok()?.into()), + ) + } +} + +/// Rialto signing params. +pub type SigningParams = sp_core::sr25519::Pair; + +/// Rialto header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; + +#[cfg(test)] +mod tests { + use super::*; + use relay_substrate_client::TransactionEra; + + #[test] + fn parse_transaction_works() { + let unsigned = UnsignedTransaction { + call: rialto_runtime::RuntimeCall::System(rialto_runtime::SystemCall::remark { + remark: b"Hello world!".to_vec(), + }) + .into(), + nonce: 777, + tip: 888, + era: TransactionEra::immortal(), + }; + let signed_transaction = Rialto::sign_transaction( + SignParam { + spec_version: 42, + transaction_version: 50000, + genesis_hash: [42u8; 32].into(), + signer: sp_core::sr25519::Pair::from_seed_slice(&[1u8; 32]).unwrap(), + }, + unsigned.clone(), + ) + .unwrap(); + let parsed_transaction = Rialto::parse_transaction(signed_transaction).unwrap(); + assert_eq!(parsed_transaction, unsigned); + } +} diff --git a/relays/client-rococo/Cargo.toml b/relays/client-rococo/Cargo.toml new file mode 100644 index 00000000000..14d3c8ca232 --- /dev/null +++ b/relays/client-rococo/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "relay-rococo-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-rococo = { path = "../../primitives/chain-rococo" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-rococo/src/lib.rs b/relays/client-rococo/src/lib.rs new file mode 100644 index 00000000000..fc3aa76eb44 --- /dev/null +++ b/relays/client-rococo/src/lib.rs @@ -0,0 +1,78 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the Rococo-Substrate chain. + +use frame_support::weights::Weight; +use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, RelayChain}; +use sp_core::storage::StorageKey; +use std::time::Duration; + +/// Rococo header id. +pub type HeaderId = relay_utils::HeaderId; + +/// Rococo header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; + +/// Rococo chain definition +#[derive(Debug, Clone, Copy)] +pub struct Rococo; + +impl ChainBase for Rococo { + type BlockNumber = bp_rococo::BlockNumber; + type Hash = bp_rococo::Hash; + type Hasher = bp_rococo::Hashing; + type Header = bp_rococo::Header; + + type AccountId = bp_rococo::AccountId; + type Balance = bp_rococo::Balance; + type Index = bp_rococo::Nonce; + type Signature = bp_rococo::Signature; + + fn max_extrinsic_size() -> u32 { + bp_rococo::Rococo::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_rococo::Rococo::max_extrinsic_weight() + } +} + +impl Chain for Rococo { + const NAME: &'static str = "Rococo"; + const TOKEN_ID: Option<&'static str> = None; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type SignedBlock = bp_rococo::SignedBlock; + type Call = (); +} + +impl ChainWithGrandpa for Rococo { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_rococo::WITH_ROCOCO_GRANDPA_PALLET_NAME; +} + +impl ChainWithBalances for Rococo { + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey { + StorageKey(bp_rococo::account_info_storage_key(account_id)) + } +} + +impl RelayChain for Rococo { + const PARAS_PALLET_NAME: &'static str = bp_rococo::PARAS_PALLET_NAME; + const PARACHAINS_FINALITY_PALLET_NAME: &'static str = "bridgeRococoParachain"; +} diff --git a/relays/client-substrate/Cargo.toml b/relays/client-substrate/Cargo.toml new file mode 100644 index 00000000000..60ef1c67a26 --- /dev/null +++ b/relays/client-substrate/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "relay-substrate-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +async-std = { version = "1.6.5", features = ["attributes"] } +async-trait = "0.1" +codec = { package = "parity-scale-codec", version = "3.1.5" } +futures = "0.3.7" +jsonrpsee = { version = "0.15", features = ["macros", "ws-client"] } +log = "0.4.17" +num-traits = "0.2" +rand = "0.7" +tokio = { version = "1.8", features = ["rt-multi-thread"] } +thiserror = "1.0.26" + +# Bridge dependencies + +bp-header-chain = { path = "../../primitives/header-chain" } +bp-messages = { path = "../../primitives/messages" } +bp-runtime = { path = "../../primitives/runtime" } +pallet-bridge-messages = { path = "../../modules/messages" } +finality-relay = { path = "../finality" } +relay-utils = { path = "../utils" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-transaction-pool-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-storage = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[features] +default = [] +test-helpers = [] diff --git a/relays/client-substrate/src/chain.rs b/relays/client-substrate/src/chain.rs new file mode 100644 index 00000000000..0e19cf92b77 --- /dev/null +++ b/relays/client-substrate/src/chain.rs @@ -0,0 +1,235 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use bp_messages::MessageNonce; +use bp_runtime::{ + Chain as ChainBase, EncodedOrDecodedCall, HashOf, TransactionEra, TransactionEraOf, +}; +use codec::{Codec, Encode}; +use frame_support::weights::WeightToFee; +use jsonrpsee::core::{DeserializeOwned, Serialize}; +use num_traits::Zero; +use sc_transaction_pool_api::TransactionStatus; +use sp_core::{storage::StorageKey, Pair}; +use sp_runtime::{ + generic::SignedBlock, + traits::{Block as BlockT, Dispatchable, Member}, + ConsensusEngineId, EncodedJustification, +}; +use std::{fmt::Debug, time::Duration}; + +/// Substrate-based chain from minimal relay-client point of view. +pub trait Chain: ChainBase + Clone { + /// Chain name. + const NAME: &'static str; + /// Identifier of the basic token of the chain (if applicable). + /// + /// This identifier is used to fetch token price. In case of testnets, you may either + /// set it to `None`, or associate testnet with one of the existing tokens. + const TOKEN_ID: Option<&'static str>; + /// Name of the runtime API method that is returning best known finalized header number + /// and hash (as tuple). + /// + /// Keep in mind that this method is normally provided by the other chain, which is + /// bridged with this chain. + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str; + + /// Average block interval. + /// + /// How often blocks are produced on that chain. It's suggested to set this value + /// to match the block time of the chain. + const AVERAGE_BLOCK_INTERVAL: Duration; + + /// Block type. + type SignedBlock: Member + Serialize + DeserializeOwned + BlockWithJustification; + /// The aggregated `Call` type. + type Call: Clone + Codec + Dispatchable + Debug + Send; +} + +/// Substrate-based relay chain that supports parachains. +/// +/// We assume that the parachains are supported using `runtime_parachains::paras` pallet. +pub trait RelayChain: Chain { + /// Name of the `runtime_parachains::paras` pallet in the runtime of this chain. + const PARAS_PALLET_NAME: &'static str; + /// Name of the bridge parachains pallet (used in `construct_runtime` macro call) that is + /// deployed at the **bridged** chain. + /// + /// We assume that all chains that are bridging with this `ChainWithGrandpa` are using + /// the same name. + const PARACHAINS_FINALITY_PALLET_NAME: &'static str; +} + +/// Substrate-based chain that is using direct GRANDPA finality from minimal relay-client point of +/// view. +/// +/// Keep in mind that parachains are relying on relay chain GRANDPA, so they should not implement +/// this trait. +pub trait ChainWithGrandpa: Chain { + /// Name of the bridge GRANDPA pallet (used in `construct_runtime` macro call) that is deployed + /// at some other chain to bridge with this `ChainWithGrandpa`. + /// + /// We assume that all chains that are bridging with this `ChainWithGrandpa` are using + /// the same name. + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str; +} + +/// Substrate-based chain with messaging support from minimal relay-client point of view. +pub trait ChainWithMessages: Chain { + /// Name of the bridge messages pallet (used in `construct_runtime` macro call) that is deployed + /// at some other chain to bridge with this `ChainWithMessages`. + /// + /// We assume that all chains that are bridging with this `ChainWithMessages` are using + /// the same name. + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str; + + /// Name of the `ToOutboundLaneApi::message_details` runtime API method. + /// The method is provided by the runtime that is bridged with this `ChainWithMessages`. + const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str; + + /// Name of the `FromInboundLaneApi::message_details` runtime API method. + /// The method is provided by the runtime that is bridged with this `ChainWithMessages`. + const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str; + + /// Maximal number of unrewarded relayers in a single confirmation transaction at this + /// `ChainWithMessages`. + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce; + /// Maximal number of unconfirmed messages in a single confirmation transaction at this + /// `ChainWithMessages`. + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce; + + /// Type that is used by the chain, to convert from weight to fee. + type WeightToFee: WeightToFee; + /// Weights of message pallet calls. + type WeightInfo: pallet_bridge_messages::WeightInfoExt; +} + +/// Call type used by the chain. +pub type CallOf = ::Call; +/// Weight-to-Fee type used by the chain. +pub type WeightToFeeOf = ::WeightToFee; +/// Transaction status of the chain. +pub type TransactionStatusOf = TransactionStatus, HashOf>; + +/// Substrate-based chain with `AccountData` generic argument of `frame_system::AccountInfo` set to +/// the `pallet_balances::AccountData`. +pub trait ChainWithBalances: Chain { + /// Return runtime storage key for getting `frame_system::AccountInfo` of given account. + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey; +} + +/// SCALE-encoded extrinsic. +pub type EncodedExtrinsic = Vec; + +/// Block with justification. +pub trait BlockWithJustification
{ + /// Return block header. + fn header(&self) -> Header; + /// Return encoded block extrinsics. + fn extrinsics(&self) -> Vec; + /// Return block justification, if known. + fn justification(&self, engine_id: ConsensusEngineId) -> Option<&EncodedJustification>; +} + +/// Transaction before it is signed. +#[derive(Clone, Debug, PartialEq)] +pub struct UnsignedTransaction { + /// Runtime call of this transaction. + pub call: EncodedOrDecodedCall, + /// Transaction nonce. + pub nonce: C::Index, + /// Tip included into transaction. + pub tip: C::Balance, + /// Transaction era used by the chain. + pub era: TransactionEraOf, +} + +impl UnsignedTransaction { + /// Create new unsigned transaction with given call, nonce, era and zero tip. + pub fn new(call: EncodedOrDecodedCall, nonce: C::Index) -> Self { + Self { call, nonce, era: TransactionEra::Immortal, tip: Zero::zero() } + } + + /// Set transaction tip. + #[must_use] + pub fn tip(mut self, tip: C::Balance) -> Self { + self.tip = tip; + self + } + + /// Set transaction era. + #[must_use] + pub fn era(mut self, era: TransactionEraOf) -> Self { + self.era = era; + self + } +} + +/// Account key pair used by transactions signing scheme. +pub type AccountKeyPairOf = ::AccountKeyPair; + +/// Substrate-based chain transactions signing scheme. +pub trait ChainWithTransactions: Chain { + /// Type of key pairs used to sign transactions. + type AccountKeyPair: Pair; + /// Signed transaction. + type SignedTransaction: Clone + Debug + Codec + Send + 'static; + + /// Create transaction for given runtime call, signed by given account. + fn sign_transaction( + param: SignParam, + unsigned: UnsignedTransaction, + ) -> Result + where + Self: Sized; + + /// Returns true if transaction is signed. + fn is_signed(tx: &Self::SignedTransaction) -> bool; + + /// Returns true if transaction is signed by given signer. + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool; + + /// Parse signed transaction into its unsigned part. + /// + /// Returns `None` if signed transaction has unsupported format. + fn parse_transaction(tx: Self::SignedTransaction) -> Option>; +} + +/// Sign transaction parameters +pub struct SignParam { + /// Version of the runtime specification. + pub spec_version: u32, + /// Transaction version + pub transaction_version: u32, + /// Hash of the genesis block. + pub genesis_hash: HashOf, + /// Signer account + pub signer: AccountKeyPairOf, +} + +impl BlockWithJustification for SignedBlock { + fn header(&self) -> Block::Header { + self.block.header().clone() + } + + fn extrinsics(&self) -> Vec { + self.block.extrinsics().iter().map(Encode::encode).collect() + } + + fn justification(&self, engine_id: ConsensusEngineId) -> Option<&EncodedJustification> { + self.justifications.as_ref().and_then(|j| j.get(engine_id)) + } +} diff --git a/relays/client-substrate/src/client.rs b/relays/client-substrate/src/client.rs new file mode 100644 index 00000000000..4f783291ee3 --- /dev/null +++ b/relays/client-substrate/src/client.rs @@ -0,0 +1,742 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate node client. + +use crate::{ + chain::{Chain, ChainWithBalances, ChainWithTransactions}, + rpc::{ + SubstrateAuthorClient, SubstrateChainClient, SubstrateFinalityClient, + SubstrateFrameSystemClient, SubstrateStateClient, SubstrateSystemClient, + SubstrateTransactionPaymentClient, + }, + transaction_stall_timeout, ConnectionParams, Error, HashOf, HeaderIdOf, Result, SignParam, + TransactionTracker, UnsignedTransaction, +}; + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use bp_runtime::{HeaderIdProvider, StorageDoubleMapKeyProvider, StorageMapKeyProvider}; +use codec::{Decode, Encode}; +use frame_system::AccountInfo; +use futures::{SinkExt, StreamExt}; +use jsonrpsee::{ + core::DeserializeOwned, + ws_client::{WsClient as RpcClient, WsClientBuilder as RpcClientBuilder}, +}; +use num_traits::{Bounded, Zero}; +use pallet_balances::AccountData; +use pallet_transaction_payment::InclusionFee; +use relay_utils::{relay_loop::RECONNECT_DELAY, STALL_TIMEOUT}; +use sp_core::{ + storage::{StorageData, StorageKey}, + Bytes, Hasher, +}; +use sp_runtime::{ + traits::Header as HeaderT, + transaction_validity::{TransactionSource, TransactionValidity}, +}; +use sp_trie::StorageProof; +use sp_version::RuntimeVersion; +use std::{convert::TryFrom, future::Future}; + +const SUB_API_GRANDPA_AUTHORITIES: &str = "GrandpaApi_grandpa_authorities"; +const SUB_API_TXPOOL_VALIDATE_TRANSACTION: &str = "TaggedTransactionQueue_validate_transaction"; +const MAX_SUBSCRIPTION_CAPACITY: usize = 4096; + +/// Opaque justifications subscription type. +pub struct Subscription(pub(crate) Mutex>>); + +/// Opaque GRANDPA authorities set. +pub type OpaqueGrandpaAuthoritiesSet = Vec; + +/// Chain runtime version in client +#[derive(Clone, Debug)] +pub enum ChainRuntimeVersion { + /// Auto query from chain. + Auto, + /// Custom runtime version, defined by user. + /// the first is `spec_version` + /// the second is `transaction_version` + Custom(u32, u32), +} + +/// Substrate client type. +/// +/// Cloning `Client` is a cheap operation. +pub struct Client { + /// Tokio runtime handle. + tokio: Arc, + /// Client connection params. + params: Arc, + /// Substrate RPC client. + client: Arc, + /// Genesis block hash. + genesis_hash: HashOf, + /// If several tasks are submitting their transactions simultaneously using + /// `submit_signed_extrinsic` method, they may get the same transaction nonce. So one of + /// transactions will be rejected from the pool. This lock is here to prevent situations like + /// that. + submit_signed_extrinsic_lock: Arc>, + /// Saved chain runtime version + chain_runtime_version: ChainRuntimeVersion, +} + +#[async_trait] +impl relay_utils::relay_loop::Client for Client { + type Error = Error; + + async fn reconnect(&mut self) -> Result<()> { + let (tokio, client) = Self::build_client(&self.params).await?; + self.tokio = tokio; + self.client = client; + Ok(()) + } +} + +impl Clone for Client { + fn clone(&self) -> Self { + Client { + tokio: self.tokio.clone(), + params: self.params.clone(), + client: self.client.clone(), + genesis_hash: self.genesis_hash, + submit_signed_extrinsic_lock: self.submit_signed_extrinsic_lock.clone(), + chain_runtime_version: self.chain_runtime_version.clone(), + } + } +} + +impl std::fmt::Debug for Client { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct("Client").field("genesis_hash", &self.genesis_hash).finish() + } +} + +impl Client { + /// Returns client that is able to call RPCs on Substrate node over websocket connection. + /// + /// This function will keep connecting to given Substrate node until connection is established + /// and is functional. If attempt fail, it will wait for `RECONNECT_DELAY` and retry again. + pub async fn new(params: ConnectionParams) -> Self { + let params = Arc::new(params); + loop { + match Self::try_connect(params.clone()).await { + Ok(client) => return client, + Err(error) => log::error!( + target: "bridge", + "Failed to connect to {} node: {:?}. Going to retry in {}s", + C::NAME, + error, + RECONNECT_DELAY.as_secs(), + ), + } + + async_std::task::sleep(RECONNECT_DELAY).await; + } + } + + /// Try to connect to Substrate node over websocket. Returns Substrate RPC client if connection + /// has been established or error otherwise. + pub async fn try_connect(params: Arc) -> Result { + let (tokio, client) = Self::build_client(¶ms).await?; + + let number: C::BlockNumber = Zero::zero(); + let genesis_hash_client = client.clone(); + let genesis_hash = tokio + .spawn(async move { + SubstrateChainClient::::block_hash(&*genesis_hash_client, Some(number)).await + }) + .await??; + + let chain_runtime_version = params.chain_runtime_version.clone(); + Ok(Self { + tokio, + params, + client, + genesis_hash, + submit_signed_extrinsic_lock: Arc::new(Mutex::new(())), + chain_runtime_version, + }) + } + + /// Build client to use in connection. + async fn build_client( + params: &ConnectionParams, + ) -> Result<(Arc, Arc)> { + let tokio = tokio::runtime::Runtime::new()?; + let uri = format!( + "{}://{}:{}", + if params.secure { "wss" } else { "ws" }, + params.host, + params.port, + ); + log::info!(target: "bridge", "Connecting to {} node at {}", C::NAME, uri); + + let client = tokio + .spawn(async move { + RpcClientBuilder::default() + .max_notifs_per_subscription(MAX_SUBSCRIPTION_CAPACITY) + .build(&uri) + .await + }) + .await??; + + Ok((Arc::new(tokio), Arc::new(client))) + } +} + +impl Client { + /// Return simple runtime version, only include `spec_version` and `transaction_version`. + pub async fn simple_runtime_version(&self) -> Result<(u32, u32)> { + let (spec_version, transaction_version) = match self.chain_runtime_version { + ChainRuntimeVersion::Auto => { + let runtime_version = self.runtime_version().await?; + (runtime_version.spec_version, runtime_version.transaction_version) + }, + ChainRuntimeVersion::Custom(spec_version, transaction_version) => + (spec_version, transaction_version), + }; + Ok((spec_version, transaction_version)) + } + + /// Returns true if client is connected to at least one peer and is in synced state. + pub async fn ensure_synced(&self) -> Result<()> { + self.jsonrpsee_execute(|client| async move { + let health = SubstrateSystemClient::::health(&*client).await?; + let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0); + if is_synced { + Ok(()) + } else { + Err(Error::ClientNotSynced(health)) + } + }) + .await + } + + /// Return hash of the genesis block. + pub fn genesis_hash(&self) -> &C::Hash { + &self.genesis_hash + } + + /// Return hash of the best finalized block. + pub async fn best_finalized_header_hash(&self) -> Result { + self.jsonrpsee_execute(|client| async move { + Ok(SubstrateChainClient::::finalized_head(&*client).await?) + }) + .await + } + + /// Return number of the best finalized block. + pub async fn best_finalized_header_number(&self) -> Result { + Ok(*self.header_by_hash(self.best_finalized_header_hash().await?).await?.number()) + } + + /// Return header of the best finalized block. + pub async fn best_finalized_header(&self) -> Result { + self.header_by_hash(self.best_finalized_header_hash().await?).await + } + + /// Returns the best Substrate header. + pub async fn best_header(&self) -> Result + where + C::Header: DeserializeOwned, + { + self.jsonrpsee_execute(|client| async move { + Ok(SubstrateChainClient::::header(&*client, None).await?) + }) + .await + } + + /// Get a Substrate block from its hash. + pub async fn get_block(&self, block_hash: Option) -> Result { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateChainClient::::block(&*client, block_hash).await?) + }) + .await + } + + /// Get a Substrate header by its hash. + pub async fn header_by_hash(&self, block_hash: C::Hash) -> Result + where + C::Header: DeserializeOwned, + { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateChainClient::::header(&*client, Some(block_hash)).await?) + }) + .await + } + + /// Get a Substrate block hash by its number. + pub async fn block_hash_by_number(&self, number: C::BlockNumber) -> Result { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateChainClient::::block_hash(&*client, Some(number)).await?) + }) + .await + } + + /// Get a Substrate header by its number. + pub async fn header_by_number(&self, block_number: C::BlockNumber) -> Result + where + C::Header: DeserializeOwned, + { + let block_hash = Self::block_hash_by_number(self, block_number).await?; + let header_by_hash = Self::header_by_hash(self, block_hash).await?; + Ok(header_by_hash) + } + + /// Return runtime version. + pub async fn runtime_version(&self) -> Result { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateStateClient::::runtime_version(&*client).await?) + }) + .await + } + + /// Read value from runtime storage. + pub async fn storage_value( + &self, + storage_key: StorageKey, + block_hash: Option, + ) -> Result> { + self.raw_storage_value(storage_key, block_hash) + .await? + .map(|encoded_value| { + T::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed) + }) + .transpose() + } + + /// Read `MapStorage` value from runtime storage. + pub async fn storage_map_value( + &self, + pallet_prefix: &str, + key: &T::Key, + block_hash: Option, + ) -> Result> { + let storage_key = T::final_key(pallet_prefix, key); + + self.raw_storage_value(storage_key, block_hash) + .await? + .map(|encoded_value| { + T::Value::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed) + }) + .transpose() + } + + /// Read `DoubleMapStorage` value from runtime storage. + pub async fn storage_double_map_value( + &self, + pallet_prefix: &str, + key1: &T::Key1, + key2: &T::Key2, + block_hash: Option, + ) -> Result> { + let storage_key = T::final_key(pallet_prefix, key1, key2); + + self.raw_storage_value(storage_key, block_hash) + .await? + .map(|encoded_value| { + T::Value::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed) + }) + .transpose() + } + + /// Read raw value from runtime storage. + pub async fn raw_storage_value( + &self, + storage_key: StorageKey, + block_hash: Option, + ) -> Result> { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateStateClient::::storage(&*client, storage_key, block_hash).await?) + }) + .await + } + + /// Return native tokens balance of the account. + pub async fn free_native_balance(&self, account: C::AccountId) -> Result + where + C: ChainWithBalances, + { + self.jsonrpsee_execute(move |client| async move { + let storage_key = C::account_info_storage_key(&account); + let encoded_account_data = + SubstrateStateClient::::storage(&*client, storage_key, None) + .await? + .ok_or(Error::AccountDoesNotExist)?; + let decoded_account_data = AccountInfo::>::decode( + &mut &encoded_account_data.0[..], + ) + .map_err(Error::ResponseParseFailed)?; + Ok(decoded_account_data.data.free) + }) + .await + } + + /// Get the nonce of the given Substrate account. + /// + /// Note: It's the caller's responsibility to make sure `account` is a valid SS58 address. + pub async fn next_account_index(&self, account: C::AccountId) -> Result { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateFrameSystemClient::::account_next_index(&*client, account).await?) + }) + .await + } + + /// Submit unsigned extrinsic for inclusion in a block. + /// + /// Note: The given transaction needs to be SCALE encoded beforehand. + pub async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result { + self.jsonrpsee_execute(move |client| async move { + let tx_hash = SubstrateAuthorClient::::submit_extrinsic(&*client, transaction) + .await + .map_err(|e| { + log::error!(target: "bridge", "Failed to send transaction to {} node: {:?}", C::NAME, e); + e + })?; + log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash); + Ok(tx_hash) + }) + .await + } + + /// Submit an extrinsic signed by given account. + /// + /// All calls of this method are synchronized, so there can't be more than one active + /// `submit_signed_extrinsic()` call. This guarantees that no nonces collision may happen + /// if all client instances are clones of the same initial `Client`. + /// + /// Note: The given transaction needs to be SCALE encoded beforehand. + pub async fn submit_signed_extrinsic( + &self, + extrinsic_signer: C::AccountId, + signing_data: SignParam, + prepare_extrinsic: impl FnOnce(HeaderIdOf, C::Index) -> Result> + + Send + + 'static, + ) -> Result + where + C: ChainWithTransactions, + { + let _guard = self.submit_signed_extrinsic_lock.lock().await; + let transaction_nonce = self.next_account_index(extrinsic_signer).await?; + let best_header = self.best_header().await?; + + // By using parent of best block here, we are protecing again best-block reorganizations. + // E.g. transaction may have been submitted when the best block was `A[num=100]`. Then it + // has been changed to `B[num=100]`. Hash of `A` has been included into transaction + // signature payload. So when signature will be checked, the check will fail and transaction + // will be dropped from the pool. + let best_header_id = best_header.parent_id().unwrap_or_else(|| best_header.id()); + + self.jsonrpsee_execute(move |client| async move { + let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce)?; + let signed_extrinsic = C::sign_transaction(signing_data, extrinsic)?.encode(); + let tx_hash = + SubstrateAuthorClient::::submit_extrinsic(&*client, Bytes(signed_extrinsic)) + .await + .map_err(|e| { + log::error!(target: "bridge", "Failed to send transaction to {} node: {:?}", C::NAME, e); + e + })?; + log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash); + Ok(tx_hash) + }) + .await + } + + /// Does exactly the same as `submit_signed_extrinsic`, but keeps watching for extrinsic status + /// after submission. + pub async fn submit_and_watch_signed_extrinsic( + &self, + extrinsic_signer: C::AccountId, + signing_data: SignParam, + prepare_extrinsic: impl FnOnce(HeaderIdOf, C::Index) -> Result> + + Send + + 'static, + ) -> Result> + where + C: ChainWithTransactions, + { + let self_clone = self.clone(); + let _guard = self.submit_signed_extrinsic_lock.lock().await; + let transaction_nonce = self.next_account_index(extrinsic_signer).await?; + let best_header = self.best_header().await?; + let best_header_id = best_header.id(); + let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY); + let (tracker, subscription) = self + .jsonrpsee_execute(move |client| async move { + let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce)?; + let stall_timeout = transaction_stall_timeout( + extrinsic.era.mortality_period(), + C::AVERAGE_BLOCK_INTERVAL, + STALL_TIMEOUT, + ); + let signed_extrinsic = C::sign_transaction(signing_data, extrinsic)?.encode(); + let tx_hash = C::Hasher::hash(&signed_extrinsic); + let subscription = SubstrateAuthorClient::::submit_and_watch_extrinsic( + &*client, + Bytes(signed_extrinsic), + ) + .await + .map_err(|e| { + log::error!(target: "bridge", "Failed to send transaction to {} node: {:?}", C::NAME, e); + e + })?; + log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash); + let tracker = TransactionTracker::new( + self_clone, + stall_timeout, + tx_hash, + Subscription(Mutex::new(receiver)), + ); + Ok((tracker, subscription)) + }) + .await?; + self.tokio.spawn(Subscription::background_worker( + C::NAME.into(), + "extrinsic".into(), + subscription, + sender, + )); + Ok(tracker) + } + + /// Returns pending extrinsics from transaction pool. + pub async fn pending_extrinsics(&self) -> Result> { + self.jsonrpsee_execute(move |client| async move { + Ok(SubstrateAuthorClient::::pending_extrinsics(&*client).await?) + }) + .await + } + + /// Validate transaction at given block state. + pub async fn validate_transaction( + &self, + at_block: C::Hash, + transaction: SignedTransaction, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + let call = SUB_API_TXPOOL_VALIDATE_TRANSACTION.to_string(); + let data = Bytes((TransactionSource::External, transaction, at_block).encode()); + + let encoded_response = + SubstrateStateClient::::call(&*client, call, data, Some(at_block)).await?; + let validity = TransactionValidity::decode(&mut &encoded_response.0[..]) + .map_err(Error::ResponseParseFailed)?; + + Ok(validity) + }) + .await + } + + /// Estimate fee that will be spent on given extrinsic. + pub async fn estimate_extrinsic_fee( + &self, + transaction: Bytes, + ) -> Result> { + self.jsonrpsee_execute(move |client| async move { + let fee_details = + SubstrateTransactionPaymentClient::::fee_details(&*client, transaction, None) + .await?; + let inclusion_fee = fee_details + .inclusion_fee + .map(|inclusion_fee| InclusionFee { + base_fee: C::Balance::try_from(inclusion_fee.base_fee.into_u256()) + .unwrap_or_else(|_| C::Balance::max_value()), + len_fee: C::Balance::try_from(inclusion_fee.len_fee.into_u256()) + .unwrap_or_else(|_| C::Balance::max_value()), + adjusted_weight_fee: C::Balance::try_from( + inclusion_fee.adjusted_weight_fee.into_u256(), + ) + .unwrap_or_else(|_| C::Balance::max_value()), + }) + .unwrap_or_else(|| InclusionFee { + base_fee: Zero::zero(), + len_fee: Zero::zero(), + adjusted_weight_fee: Zero::zero(), + }); + Ok(inclusion_fee) + }) + .await + } + + /// Get the GRANDPA authority set at given block. + pub async fn grandpa_authorities_set( + &self, + block: C::Hash, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + let call = SUB_API_GRANDPA_AUTHORITIES.to_string(); + let data = Bytes(Vec::new()); + + let encoded_response = + SubstrateStateClient::::call(&*client, call, data, Some(block)).await?; + let authority_list = encoded_response.0; + + Ok(authority_list) + }) + .await + } + + /// Execute runtime call at given block, provided the input and output types. + /// It also performs the input encode and output decode. + pub async fn typed_state_call( + &self, + method_name: String, + input: Input, + at_block: Option, + ) -> Result { + let encoded_output = self.state_call(method_name, Bytes(input.encode()), at_block).await?; + Output::decode(&mut &encoded_output.0[..]).map_err(Error::ResponseParseFailed) + } + + /// Execute runtime call at given block. + pub async fn state_call( + &self, + method: String, + data: Bytes, + at_block: Option, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + SubstrateStateClient::::call(&*client, method, data, at_block) + .await + .map_err(Into::into) + }) + .await + } + + /// Returns storage proof of given storage keys. + pub async fn prove_storage( + &self, + keys: Vec, + at_block: C::Hash, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + SubstrateStateClient::::prove_storage(&*client, keys, Some(at_block)) + .await + .map(|proof| { + StorageProof::new(proof.proof.into_iter().map(|b| b.0).collect::>()) + }) + .map_err(Into::into) + }) + .await + } + + /// Return `tokenDecimals` property from the set of chain properties. + pub async fn token_decimals(&self) -> Result> { + self.jsonrpsee_execute(move |client| async move { + let system_properties = SubstrateSystemClient::::properties(&*client).await?; + Ok(system_properties.get("tokenDecimals").and_then(|v| v.as_u64())) + }) + .await + } + + /// Return new finality justifications stream. + pub async fn subscribe_finality_justifications>( + &self, + ) -> Result> { + let subscription = self + .jsonrpsee_execute(move |client| async move { + Ok(FC::subscribe_justifications(&client).await?) + }) + .await?; + let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY); + self.tokio.spawn(Subscription::background_worker( + C::NAME.into(), + "justification".into(), + subscription, + sender, + )); + Ok(Subscription(Mutex::new(receiver))) + } + + /// Execute jsonrpsee future in tokio context. + async fn jsonrpsee_execute(&self, make_jsonrpsee_future: MF) -> Result + where + MF: FnOnce(Arc) -> F + Send + 'static, + F: Future> + Send, + T: Send + 'static, + { + let client = self.client.clone(); + self.tokio.spawn(async move { make_jsonrpsee_future(client).await }).await? + } + + /// Returns `true` if version guard can be started. + /// + /// There's no reason to run version guard when version mode is set to `Auto`. It can + /// lead to relay shutdown when chain is upgraded, even though we have explicitly + /// said that we don't want to shutdown. + pub fn can_start_version_guard(&self) -> bool { + !matches!(self.chain_runtime_version, ChainRuntimeVersion::Auto) + } +} + +impl Subscription { + /// Consumes subscription and returns future statuses stream. + pub fn into_stream(self) -> impl futures::Stream { + futures::stream::unfold(self, |this| async { + let item = this.0.lock().await.next().await.unwrap_or(None); + item.map(|i| (i, this)) + }) + } + + /// Return next item from the subscription. + pub async fn next(&self) -> Result> { + let mut receiver = self.0.lock().await; + let item = receiver.next().await; + Ok(item.unwrap_or(None)) + } + + /// Background worker that is executed in tokio context as `jsonrpsee` requires. + async fn background_worker( + chain_name: String, + item_type: String, + mut subscription: jsonrpsee::core::client::Subscription, + mut sender: futures::channel::mpsc::Sender>, + ) { + loop { + match subscription.next().await { + Some(Ok(item)) => + if sender.send(Some(item)).await.is_err() { + break + }, + Some(Err(e)) => { + log::trace!( + target: "bridge", + "{} {} subscription stream has returned '{:?}'. Stream needs to be restarted.", + chain_name, + item_type, + e, + ); + let _ = sender.send(None).await; + break + }, + None => { + log::trace!( + target: "bridge", + "{} {} subscription stream has returned None. Stream needs to be restarted.", + chain_name, + item_type, + ); + let _ = sender.send(None).await; + break + }, + } + } + } +} diff --git a/relays/client-substrate/src/error.rs b/relays/client-substrate/src/error.rs new file mode 100644 index 00000000000..9323b757221 --- /dev/null +++ b/relays/client-substrate/src/error.rs @@ -0,0 +1,86 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate node RPC errors. + +use jsonrpsee::core::Error as RpcError; +use relay_utils::MaybeConnectionError; +use sc_rpc_api::system::Health; +use sp_runtime::transaction_validity::TransactionValidityError; +use thiserror::Error; + +/// Result type used by Substrate client. +pub type Result = std::result::Result; + +/// Errors that can occur only when interacting with +/// a Substrate node through RPC. +#[derive(Error, Debug)] +pub enum Error { + /// IO error. + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + /// An error that can occur when making a request to + /// an JSON-RPC server. + #[error("RPC error: {0}")] + RpcError(#[from] RpcError), + /// The response from the server could not be SCALE decoded. + #[error("Response parse failed: {0}")] + ResponseParseFailed(#[from] codec::Error), + /// Account does not exist on the chain. + #[error("Account does not exist on the chain.")] + AccountDoesNotExist, + /// Runtime storage is missing some mandatory value. + #[error("Mandatory storage value is missing from the runtime storage.")] + MissingMandatoryStorageValue, + /// The client we're connected to is not synced, so we can't rely on its state. + #[error("Substrate client is not synced {0}.")] + ClientNotSynced(Health), + /// The bridge pallet is halted and all transactions will be rejected. + #[error("Bridge pallet is halted.")] + BridgePalletIsHalted, + /// The bridge pallet is not yet initialized and all transactions will be rejected. + #[error("Bridge pallet is not initialized.")] + BridgePalletIsNotInitialized, + /// An error has happened when we have tried to parse storage proof. + #[error("Error when parsing storage proof: {0:?}.")] + StorageProofError(bp_runtime::StorageProofError), + /// The Substrate transaction is invalid. + #[error("Substrate transaction is invalid: {0:?}")] + TransactionInvalid(#[from] TransactionValidityError), + /// Custom logic error. + #[error("{0}")] + Custom(String), +} + +impl From for Error { + fn from(error: tokio::task::JoinError) -> Self { + Error::Custom(format!("Failed to wait tokio task: {error}")) + } +} + +impl MaybeConnectionError for Error { + fn is_connection_error(&self) -> bool { + matches!( + *self, + Error::RpcError(RpcError::Transport(_)) + // right now if connection to the ws server is dropped (after it is already established), + // we're getting this error + | Error::RpcError(RpcError::Internal(_)) + | Error::RpcError(RpcError::RestartNeeded(_)) + | Error::ClientNotSynced(_), + ) + } +} diff --git a/relays/client-substrate/src/guard.rs b/relays/client-substrate/src/guard.rs new file mode 100644 index 00000000000..bc8fd8e7d30 --- /dev/null +++ b/relays/client-substrate/src/guard.rs @@ -0,0 +1,372 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Pallet provides a set of guard functions that are running in background threads +//! and are aborting process if some condition fails. + +use crate::{error::Error, Chain, ChainWithBalances, Client}; + +use async_trait::async_trait; +use num_traits::CheckedSub; +use sp_version::RuntimeVersion; +use std::{ + collections::VecDeque, + fmt::Display, + time::{Duration, Instant}, +}; + +/// Guards environment. +#[async_trait] +pub trait Environment: Send + Sync + 'static { + /// Error type. + type Error: Display + Send + Sync + 'static; + + /// Return current runtime version. + async fn runtime_version(&mut self) -> Result; + /// Return free native balance of the account on the chain. + async fn free_native_balance( + &mut self, + account: C::AccountId, + ) -> Result; + + /// Return current time. + fn now(&self) -> Instant { + Instant::now() + } + + /// Sleep given amount of time. + async fn sleep(&mut self, duration: Duration) { + async_std::task::sleep(duration).await + } + + /// Abort current process. Called when guard condition check fails. + async fn abort(&mut self) { + std::process::abort(); + } +} + +/// Abort when runtime spec version is different from specified. +pub fn abort_on_spec_version_change( + mut env: impl Environment, + expected_spec_version: u32, +) { + async_std::task::spawn(async move { + log::info!( + target: "bridge-guard", + "Starting spec_version guard for {}. Expected spec_version: {}", + C::NAME, + expected_spec_version, + ); + + loop { + let actual_spec_version = env.runtime_version().await; + match actual_spec_version { + Ok(version) if version.spec_version == expected_spec_version => (), + Ok(version) => { + log::error!( + target: "bridge-guard", + "{} runtime spec version has changed from {} to {}. Aborting relay", + C::NAME, + expected_spec_version, + version.spec_version, + ); + + env.abort().await; + }, + Err(error) => log::warn!( + target: "bridge-guard", + "Failed to read {} runtime version: {}. Relay may need to be stopped manually", + C::NAME, + error, + ), + } + + env.sleep(conditions_check_delay::()).await; + } + }); +} + +/// Abort if, during 24 hours, free balance of given account is decreased at least by given value. +/// Other components may increase (or decrease) balance of account and it WILL affect logic of the +/// guard. +pub fn abort_when_account_balance_decreased( + mut env: impl Environment, + account_id: C::AccountId, + maximal_decrease: C::Balance, +) { + const DAY: Duration = Duration::from_secs(60 * 60 * 24); + + async_std::task::spawn(async move { + log::info!( + target: "bridge-guard", + "Starting balance guard for {}/{:?}. Maximal decrease: {:?}", + C::NAME, + account_id, + maximal_decrease, + ); + + let mut balances = VecDeque::new(); + + loop { + let current_time = env.now(); + + // remember balances that are beyound 24h border + let time_border = current_time - DAY; + while balances.front().map(|(time, _)| *time < time_border).unwrap_or(false) { + balances.pop_front(); + } + + // read balance of the account + let current_balance = env.free_native_balance(account_id.clone()).await; + + // remember balance and check difference + match current_balance { + Ok(current_balance) => { + // remember balance + balances.push_back((current_time, current_balance)); + + // check if difference between current and oldest balance is too large + let (oldest_time, oldest_balance) = + balances.front().expect("pushed to queue couple of lines above; qed"); + let balances_difference = oldest_balance.checked_sub(¤t_balance); + if balances_difference > Some(maximal_decrease) { + log::error!( + target: "bridge-guard", + "Balance of {} account {:?} has decreased from {:?} to {:?} in {} minutes. Aborting relay", + C::NAME, + account_id, + oldest_balance, + current_balance, + current_time.duration_since(*oldest_time).as_secs() / 60, + ); + + env.abort().await; + } + }, + Err(error) => { + log::warn!( + target: "bridge-guard", + "Failed to read {} account {:?} balance: {}. Relay may need to be stopped manually", + C::NAME, + account_id, + error, + ); + }, + }; + + env.sleep(conditions_check_delay::()).await; + } + }); +} + +/// Delay between conditions check. +fn conditions_check_delay() -> Duration { + C::AVERAGE_BLOCK_INTERVAL * (10 + rand::random::() % 10) +} + +#[async_trait] +impl Environment for Client { + type Error = Error; + + async fn runtime_version(&mut self) -> Result { + Client::::runtime_version(self).await + } + + async fn free_native_balance( + &mut self, + account: C::AccountId, + ) -> Result { + Client::::free_native_balance(self, account).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_chain::TestChain; + use futures::{ + channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, + future::FutureExt, + stream::StreamExt, + SinkExt, + }; + + struct TestEnvironment { + runtime_version_rx: UnboundedReceiver, + free_native_balance_rx: UnboundedReceiver, + slept_tx: UnboundedSender<()>, + aborted_tx: UnboundedSender<()>, + } + + #[async_trait] + impl Environment for TestEnvironment { + type Error = Error; + + async fn runtime_version(&mut self) -> Result { + Ok(self.runtime_version_rx.next().await.unwrap_or_default()) + } + + async fn free_native_balance(&mut self, _account: u32) -> Result { + Ok(self.free_native_balance_rx.next().await.unwrap_or_default()) + } + + async fn sleep(&mut self, _duration: Duration) { + let _ = self.slept_tx.send(()).await; + } + + async fn abort(&mut self) { + let _ = self.aborted_tx.send(()).await; + // simulate process abort :) + async_std::task::sleep(Duration::from_secs(60)).await; + } + } + + #[test] + fn aborts_when_spec_version_is_changed() { + async_std::task::block_on(async { + let ( + (mut runtime_version_tx, runtime_version_rx), + (_free_native_balance_tx, free_native_balance_rx), + (slept_tx, mut slept_rx), + (aborted_tx, mut aborted_rx), + ) = (unbounded(), unbounded(), unbounded(), unbounded()); + abort_on_spec_version_change( + TestEnvironment { + runtime_version_rx, + free_native_balance_rx, + slept_tx, + aborted_tx, + }, + 0, + ); + + // client responds with wrong version + runtime_version_tx + .send(RuntimeVersion { spec_version: 42, ..Default::default() }) + .await + .unwrap(); + + // then the `abort` function is called + aborted_rx.next().await; + // and we do not reach the `sleep` function call + assert!(slept_rx.next().now_or_never().is_none()); + }); + } + + #[test] + fn does_not_aborts_when_spec_version_is_unchanged() { + async_std::task::block_on(async { + let ( + (mut runtime_version_tx, runtime_version_rx), + (_free_native_balance_tx, free_native_balance_rx), + (slept_tx, mut slept_rx), + (aborted_tx, mut aborted_rx), + ) = (unbounded(), unbounded(), unbounded(), unbounded()); + abort_on_spec_version_change( + TestEnvironment { + runtime_version_rx, + free_native_balance_rx, + slept_tx, + aborted_tx, + }, + 42, + ); + + // client responds with the same version + runtime_version_tx + .send(RuntimeVersion { spec_version: 42, ..Default::default() }) + .await + .unwrap(); + + // then the `sleep` function is called + slept_rx.next().await; + // and the `abort` function is not called + assert!(aborted_rx.next().now_or_never().is_none()); + }); + } + + #[test] + fn aborts_when_balance_is_too_low() { + async_std::task::block_on(async { + let ( + (_runtime_version_tx, runtime_version_rx), + (mut free_native_balance_tx, free_native_balance_rx), + (slept_tx, mut slept_rx), + (aborted_tx, mut aborted_rx), + ) = (unbounded(), unbounded(), unbounded(), unbounded()); + abort_when_account_balance_decreased( + TestEnvironment { + runtime_version_rx, + free_native_balance_rx, + slept_tx, + aborted_tx, + }, + 0, + 100, + ); + + // client responds with initial balance + free_native_balance_tx.send(1000).await.unwrap(); + + // then the guard sleeps + slept_rx.next().await; + + // and then client responds with updated balance, which is too low + free_native_balance_tx.send(899).await.unwrap(); + + // then the `abort` function is called + aborted_rx.next().await; + // and we do not reach next `sleep` function call + assert!(slept_rx.next().now_or_never().is_none()); + }); + } + + #[test] + fn does_not_aborts_when_balance_is_enough() { + async_std::task::block_on(async { + let ( + (_runtime_version_tx, runtime_version_rx), + (mut free_native_balance_tx, free_native_balance_rx), + (slept_tx, mut slept_rx), + (aborted_tx, mut aborted_rx), + ) = (unbounded(), unbounded(), unbounded(), unbounded()); + abort_when_account_balance_decreased( + TestEnvironment { + runtime_version_rx, + free_native_balance_rx, + slept_tx, + aborted_tx, + }, + 0, + 100, + ); + + // client responds with initial balance + free_native_balance_tx.send(1000).await.unwrap(); + + // then the guard sleeps + slept_rx.next().await; + + // and then client responds with updated balance, which is enough + free_native_balance_tx.send(950).await.unwrap(); + + // then the `sleep` function is called + slept_rx.next().await; + // and `abort` is not called + assert!(aborted_rx.next().now_or_never().is_none()); + }); + } +} diff --git a/relays/client-substrate/src/lib.rs b/relays/client-substrate/src/lib.rs new file mode 100644 index 00000000000..99ff0fbe394 --- /dev/null +++ b/relays/client-substrate/src/lib.rs @@ -0,0 +1,91 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tools to interact with Substrate node using RPC methods. + +#![warn(missing_docs)] + +mod chain; +mod client; +mod error; +mod rpc; +mod sync_header; +mod transaction_tracker; + +pub mod guard; +pub mod metrics; +pub mod test_chain; + +use std::time::Duration; + +pub use crate::{ + chain::{ + AccountKeyPairOf, BlockWithJustification, CallOf, Chain, ChainWithBalances, + ChainWithGrandpa, ChainWithMessages, ChainWithTransactions, RelayChain, SignParam, + TransactionStatusOf, UnsignedTransaction, WeightToFeeOf, + }, + client::{ChainRuntimeVersion, Client, OpaqueGrandpaAuthoritiesSet, Subscription}, + error::{Error, Result}, + rpc::{SubstrateBeefyFinalityClient, SubstrateFinalityClient, SubstrateGrandpaFinalityClient}, + sync_header::SyncHeader, + transaction_tracker::TransactionTracker, +}; +pub use bp_runtime::{ + AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf, + IndexOf, SignatureOf, TransactionEra, TransactionEraOf, +}; + +/// Header id used by the chain. +pub type HeaderIdOf = relay_utils::HeaderId, BlockNumberOf>; + +/// Substrate-over-websocket connection params. +#[derive(Debug, Clone)] +pub struct ConnectionParams { + /// Websocket server host name. + pub host: String, + /// Websocket server TCP port. + pub port: u16, + /// Use secure websocket connection. + pub secure: bool, + /// Defined chain runtime version + pub chain_runtime_version: ChainRuntimeVersion, +} + +impl Default for ConnectionParams { + fn default() -> Self { + ConnectionParams { + host: "localhost".into(), + port: 9944, + secure: false, + chain_runtime_version: ChainRuntimeVersion::Auto, + } + } +} + +/// Returns stall timeout for relay loop. +/// +/// Relay considers himself stalled if he has submitted transaction to the node, but it has not +/// been mined for this period. +pub fn transaction_stall_timeout( + mortality_period: Option, + average_block_interval: Duration, + default_stall_timeout: Duration, +) -> Duration { + // 1 extra block for transaction to reach the pool && 1 for relayer to awake after it is mined + mortality_period + .map(|mortality_period| average_block_interval.saturating_mul(mortality_period + 1 + 1)) + .unwrap_or(default_stall_timeout) +} diff --git a/relays/client-substrate/src/metrics/float_storage_value.rs b/relays/client-substrate/src/metrics/float_storage_value.rs new file mode 100644 index 00000000000..7bb92693b38 --- /dev/null +++ b/relays/client-substrate/src/metrics/float_storage_value.rs @@ -0,0 +1,133 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{chain::Chain, client::Client, Error as SubstrateError}; + +use async_std::sync::{Arc, RwLock}; +use async_trait::async_trait; +use codec::Decode; +use num_traits::One; +use relay_utils::metrics::{ + metric_name, register, F64SharedRef, Gauge, Metric, PrometheusError, Registry, + StandaloneMetric, F64, +}; +use sp_core::storage::{StorageData, StorageKey}; +use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber, FixedU128}; +use std::{marker::PhantomData, time::Duration}; + +/// Storage value update interval (in blocks). +const UPDATE_INTERVAL_IN_BLOCKS: u32 = 5; + +/// Fied-point storage value and the way it is decoded from the raw storage value. +pub trait FloatStorageValue: 'static + Clone + Send + Sync { + /// Type of the value. + type Value: FixedPointNumber; + /// Try to decode value from the raw storage value. + fn decode( + &self, + maybe_raw_value: Option, + ) -> Result, SubstrateError>; +} + +/// Implementation of `FloatStorageValue` that expects encoded `FixedU128` value and returns `1` if +/// value is missing from the storage. +#[derive(Clone, Debug, Default)] +pub struct FixedU128OrOne; + +impl FloatStorageValue for FixedU128OrOne { + type Value = FixedU128; + + fn decode( + &self, + maybe_raw_value: Option, + ) -> Result, SubstrateError> { + maybe_raw_value + .map(|raw_value| { + FixedU128::decode(&mut &raw_value.0[..]) + .map_err(SubstrateError::ResponseParseFailed) + .map(Some) + }) + .unwrap_or_else(|| Ok(Some(FixedU128::one()))) + } +} + +/// Metric that represents fixed-point runtime storage value as float gauge. +#[derive(Clone, Debug)] +pub struct FloatStorageValueMetric { + value_converter: V, + client: Client, + storage_key: StorageKey, + metric: Gauge, + shared_value_ref: F64SharedRef, + _phantom: PhantomData, +} + +impl FloatStorageValueMetric { + /// Create new metric. + pub fn new( + value_converter: V, + client: Client, + storage_key: StorageKey, + name: String, + help: String, + ) -> Result { + let shared_value_ref = Arc::new(RwLock::new(None)); + Ok(FloatStorageValueMetric { + value_converter, + client, + storage_key, + metric: Gauge::new(metric_name(None, &name), help)?, + shared_value_ref, + _phantom: Default::default(), + }) + } + + /// Get shared reference to metric value. + pub fn shared_value_ref(&self) -> F64SharedRef { + self.shared_value_ref.clone() + } +} + +impl Metric for FloatStorageValueMetric { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.metric.clone(), registry).map(drop) + } +} + +#[async_trait] +impl StandaloneMetric for FloatStorageValueMetric { + fn update_interval(&self) -> Duration { + C::AVERAGE_BLOCK_INTERVAL * UPDATE_INTERVAL_IN_BLOCKS + } + + async fn update(&self) { + let value = self + .client + .raw_storage_value(self.storage_key.clone(), None) + .await + .and_then(|maybe_storage_value| { + self.value_converter.decode(maybe_storage_value).map(|maybe_fixed_point_value| { + maybe_fixed_point_value.map(|fixed_point_value| { + fixed_point_value.into_inner().unique_saturated_into() as f64 / + V::Value::DIV.unique_saturated_into() as f64 + }) + }) + }) + .map_err(|e| e.to_string()); + relay_utils::metrics::set_gauge_value(&self.metric, value.clone()); + *self.shared_value_ref.write().await = value.ok().and_then(|x| x); + } +} diff --git a/relays/client-substrate/src/metrics/mod.rs b/relays/client-substrate/src/metrics/mod.rs new file mode 100644 index 00000000000..fe200e2d3dc --- /dev/null +++ b/relays/client-substrate/src/metrics/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Contains several Substrate-specific metrics that may be exposed by relay. + +pub use float_storage_value::{FixedU128OrOne, FloatStorageValue, FloatStorageValueMetric}; + +mod float_storage_value; diff --git a/relays/client-substrate/src/rpc.rs b/relays/client-substrate/src/rpc.rs new file mode 100644 index 00000000000..083b1dea761 --- /dev/null +++ b/relays/client-substrate/src/rpc.rs @@ -0,0 +1,170 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! The most generic Substrate node RPC interface. + +use async_trait::async_trait; + +use crate::{Chain, ChainWithGrandpa, TransactionStatusOf}; + +use jsonrpsee::{ + core::{client::Subscription, RpcResult}, + proc_macros::rpc, + ws_client::WsClient, +}; +use pallet_transaction_payment_rpc_runtime_api::FeeDetails; +use sc_rpc_api::{state::ReadProof, system::Health}; +use sp_core::{ + storage::{StorageData, StorageKey}, + Bytes, +}; +use sp_rpc::number::NumberOrHex; +use sp_version::RuntimeVersion; + +/// RPC methods of Substrate `system` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "system")] +pub(crate) trait SubstrateSystem { + /// Return node health. + #[method(name = "health")] + async fn health(&self) -> RpcResult; + /// Return system properties. + #[method(name = "properties")] + async fn properties(&self) -> RpcResult; +} + +/// RPC methods of Substrate `chain` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "chain")] +pub(crate) trait SubstrateChain { + /// Get block hash by its number. + #[method(name = "getBlockHash")] + async fn block_hash(&self, block_number: Option) -> RpcResult; + /// Return block header by its hash. + #[method(name = "getHeader")] + async fn header(&self, block_hash: Option) -> RpcResult; + /// Return best finalized block hash. + #[method(name = "getFinalizedHead")] + async fn finalized_head(&self) -> RpcResult; + /// Return signed block (with justifications) by its hash. + #[method(name = "getBlock")] + async fn block(&self, block_hash: Option) -> RpcResult; +} + +/// RPC methods of Substrate `author` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "author")] +pub(crate) trait SubstrateAuthor { + /// Submit extrinsic to the transaction pool. + #[method(name = "submitExtrinsic")] + async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult; + /// Return vector of pending extrinsics from the transaction pool. + #[method(name = "pendingExtrinsics")] + async fn pending_extrinsics(&self) -> RpcResult>; + /// Submit and watch for extrinsic state. + #[subscription(name = "submitAndWatchExtrinsic", unsubscribe = "unwatchExtrinsic", item = TransactionStatusOf)] + fn submit_and_watch_extrinsic(&self, extrinsic: Bytes); +} + +/// RPC methods of Substrate `state` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "state")] +pub(crate) trait SubstrateState { + /// Get current runtime version. + #[method(name = "getRuntimeVersion")] + async fn runtime_version(&self) -> RpcResult; + /// Call given runtime method. + #[method(name = "call")] + async fn call( + &self, + method: String, + data: Bytes, + at_block: Option, + ) -> RpcResult; + /// Get value of the runtime storage. + #[method(name = "getStorage")] + async fn storage( + &self, + key: StorageKey, + at_block: Option, + ) -> RpcResult>; + /// Get proof of the runtime storage value. + #[method(name = "getReadProof")] + async fn prove_storage( + &self, + keys: Vec, + hash: Option, + ) -> RpcResult>; +} + +/// RPC methods that we are using for a certain finality gadget. +#[async_trait] +pub trait SubstrateFinalityClient { + /// Subscribe to finality justifications. + async fn subscribe_justifications(client: &WsClient) -> RpcResult>; +} + +/// RPC methods of Substrate `grandpa` namespace, that we are using. +#[rpc(client, client_bounds(C: ChainWithGrandpa), namespace = "grandpa")] +pub(crate) trait SubstrateGrandpa { + /// Subscribe to GRANDPA justifications. + #[subscription(name = "subscribeJustifications", unsubscribe = "unsubscribeJustifications", item = Bytes)] + fn subscribe_justifications(&self); +} + +/// RPC finality methods of Substrate `grandpa` namespace, that we are using. +pub struct SubstrateGrandpaFinalityClient; +#[async_trait] +impl SubstrateFinalityClient for SubstrateGrandpaFinalityClient { + async fn subscribe_justifications(client: &WsClient) -> RpcResult> { + SubstrateGrandpaClient::::subscribe_justifications(client).await + } +} + +// TODO: Use `ChainWithBeefy` instead of `Chain` after #1606 is merged +/// RPC methods of Substrate `beefy` namespace, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "beefy")] +pub(crate) trait SubstrateBeefy { + /// Subscribe to BEEFY justifications. + #[subscription(name = "subscribeJustifications", unsubscribe = "unsubscribeJustifications", item = Bytes)] + fn subscribe_justifications(&self); +} + +/// RPC finality methods of Substrate `beefy` namespace, that we are using. +pub struct SubstrateBeefyFinalityClient; +// TODO: Use `ChainWithBeefy` instead of `Chain` after #1606 is merged +#[async_trait] +impl SubstrateFinalityClient for SubstrateBeefyFinalityClient { + async fn subscribe_justifications(client: &WsClient) -> RpcResult> { + SubstrateBeefyClient::::subscribe_justifications(client).await + } +} + +/// RPC methods of Substrate `system` frame pallet, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "system")] +pub(crate) trait SubstrateFrameSystem { + /// Return index of next account transaction. + #[method(name = "accountNextIndex")] + async fn account_next_index(&self, account_id: C::AccountId) -> RpcResult; +} + +/// RPC methods of Substrate `pallet_transaction_payment` frame pallet, that we are using. +#[rpc(client, client_bounds(C: Chain), namespace = "payment")] +pub(crate) trait SubstrateTransactionPayment { + /// Query transaction fee details. + #[method(name = "queryFeeDetails")] + async fn fee_details( + &self, + extrinsic: Bytes, + at_block: Option, + ) -> RpcResult>; +} diff --git a/relays/client-substrate/src/sync_header.rs b/relays/client-substrate/src/sync_header.rs new file mode 100644 index 00000000000..fdfd1f22ce9 --- /dev/null +++ b/relays/client-substrate/src/sync_header.rs @@ -0,0 +1,61 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use bp_header_chain::ConsensusLogReader; +use finality_relay::SourceHeader as FinalitySourceHeader; +use sp_runtime::traits::Header as HeaderT; + +/// Generic wrapper for `sp_runtime::traits::Header` based headers, that +/// implements `finality_relay::SourceHeader` and may be used in headers sync directly. +#[derive(Clone, Debug, PartialEq)] +pub struct SyncHeader
(Header); + +impl
SyncHeader
{ + /// Extracts wrapped header from self. + pub fn into_inner(self) -> Header { + self.0 + } +} + +impl
std::ops::Deref for SyncHeader
{ + type Target = Header; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl
From
for SyncHeader
{ + fn from(header: Header) -> Self { + Self(header) + } +} + +impl FinalitySourceHeader + for SyncHeader
+{ + fn hash(&self) -> Header::Hash { + self.0.hash() + } + + fn number(&self) -> Header::Number { + *self.0.number() + } + + fn is_mandatory(&self) -> bool { + R::schedules_authorities_change(self.digest()) + } +} diff --git a/relays/client-substrate/src/test_chain.rs b/relays/client-substrate/src/test_chain.rs new file mode 100644 index 00000000000..4589687d39d --- /dev/null +++ b/relays/client-substrate/src/test_chain.rs @@ -0,0 +1,68 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Pallet provides a set of guard functions that are running in background threads +//! and are aborting process if some condition fails. + +//! Test chain implementation to use in tests. + +#![cfg(any(feature = "test-helpers", test))] + +use crate::{Chain, ChainWithBalances}; +use frame_support::weights::Weight; +use std::time::Duration; + +/// Chain that may be used in tests. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TestChain; + +impl bp_runtime::Chain for TestChain { + type BlockNumber = u32; + type Hash = sp_core::H256; + type Hasher = sp_runtime::traits::BlakeTwo256; + type Header = sp_runtime::generic::Header; + + type AccountId = u32; + type Balance = u32; + type Index = u32; + type Signature = sp_runtime::testing::TestSignature; + + fn max_extrinsic_size() -> u32 { + unreachable!() + } + + fn max_extrinsic_weight() -> Weight { + unreachable!() + } +} + +impl Chain for TestChain { + const NAME: &'static str = "Test"; + const TOKEN_ID: Option<&'static str> = None; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestMethod"; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0); + + type SignedBlock = sp_runtime::generic::SignedBlock< + sp_runtime::generic::Block, + >; + type Call = (); +} + +impl ChainWithBalances for TestChain { + fn account_info_storage_key(_account_id: &u32) -> sp_core::storage::StorageKey { + unreachable!() + } +} diff --git a/relays/client-substrate/src/transaction_tracker.rs b/relays/client-substrate/src/transaction_tracker.rs new file mode 100644 index 00000000000..211f7faab0e --- /dev/null +++ b/relays/client-substrate/src/transaction_tracker.rs @@ -0,0 +1,447 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Helper for tracking transaction invalidation events. + +use crate::{Chain, Client, Error, HashOf, HeaderIdOf, Subscription, TransactionStatusOf}; + +use async_trait::async_trait; +use futures::{future::Either, Future, FutureExt, Stream, StreamExt}; +use relay_utils::{HeaderId, TrackedTransactionStatus}; +use sp_runtime::traits::Header as _; +use std::time::Duration; + +/// Transaction tracker environment. +#[async_trait] +pub trait Environment: Send + Sync { + /// Returns header id by its hash. + async fn header_id_by_hash(&self, hash: HashOf) -> Result, Error>; +} + +#[async_trait] +impl Environment for Client { + async fn header_id_by_hash(&self, hash: HashOf) -> Result, Error> { + self.header_by_hash(hash).await.map(|h| HeaderId(*h.number(), hash)) + } +} + +/// Substrate transaction tracker implementation. +/// +/// Substrate node provides RPC API to submit and watch for transaction events. This way +/// we may know when transaction is included into block, finalized or rejected. There are +/// some edge cases, when we can't fully trust this mechanism - e.g. transaction may broadcasted +/// and then dropped out of node transaction pool (some other cases are also possible - node +/// restarts, connection lost, ...). Then we can't know for sure - what is currently happening +/// with our transaction. Is the transaction really lost? Is it still alive on the chain network? +/// +/// We have several options to handle such cases: +/// +/// 1) hope that the transaction is still alive and wait for its mining until it is spoiled; +/// +/// 2) assume that the transaction is lost and resubmit another transaction instantly; +/// +/// 3) wait for some time (if transaction is mortal - then until block where it dies; if it is +/// immortal - then for some time that we assume is long enough to mine it) and assume that +/// it is lost. +/// +/// This struct implements third option as it seems to be the most optimal. +pub struct TransactionTracker { + environment: E, + transaction_hash: HashOf, + stall_timeout: Duration, + subscription: Subscription>, +} + +impl> TransactionTracker { + /// Create transaction tracker. + pub fn new( + environment: E, + stall_timeout: Duration, + transaction_hash: HashOf, + subscription: Subscription>, + ) -> Self { + Self { environment, stall_timeout, transaction_hash, subscription } + } + + /// Wait for final transaction status and return it along with last known internal invalidation + /// status. + async fn do_wait( + self, + wait_for_stall_timeout: impl Future, + wait_for_stall_timeout_rest: impl Future, + ) -> (TrackedTransactionStatus>, Option>>) { + // sometimes we want to wait for the rest of the stall timeout even if + // `wait_for_invalidation` has been "select"ed first => it is shared + let wait_for_invalidation = watch_transaction_status::<_, C, _>( + self.environment, + self.transaction_hash, + self.subscription.into_stream(), + ); + futures::pin_mut!(wait_for_stall_timeout, wait_for_invalidation); + + match futures::future::select(wait_for_stall_timeout, wait_for_invalidation).await { + Either::Left((_, _)) => { + log::trace!( + target: "bridge", + "{} transaction {:?} is considered lost after timeout (no status response from the node)", + C::NAME, + self.transaction_hash, + ); + + (TrackedTransactionStatus::Lost, None) + }, + Either::Right((invalidation_status, _)) => match invalidation_status { + InvalidationStatus::Finalized(at_block) => + (TrackedTransactionStatus::Finalized(at_block), Some(invalidation_status)), + InvalidationStatus::Invalid => + (TrackedTransactionStatus::Lost, Some(invalidation_status)), + InvalidationStatus::Lost => { + // wait for the rest of stall timeout - this way we'll be sure that the + // transaction is actually dead if it has been crafted properly + wait_for_stall_timeout_rest.await; + // if someone is still watching for our transaction, then we're reporting + // an error here (which is treated as "transaction lost") + log::trace!( + target: "bridge", + "{} transaction {:?} is considered lost after timeout", + C::NAME, + self.transaction_hash, + ); + + (TrackedTransactionStatus::Lost, Some(invalidation_status)) + }, + }, + } + } +} + +#[async_trait] +impl> relay_utils::TransactionTracker for TransactionTracker { + type HeaderId = HeaderIdOf; + + async fn wait(self) -> TrackedTransactionStatus> { + let wait_for_stall_timeout = async_std::task::sleep(self.stall_timeout).shared(); + let wait_for_stall_timeout_rest = wait_for_stall_timeout.clone(); + self.do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest).await.0 + } +} + +/// Transaction invalidation status. +/// +/// Note that in places where the `TransactionTracker` is used, the finalization event will be +/// ignored - relay loops are detecting the mining/finalization using their own +/// techniques. That's why we're using `InvalidationStatus` here. +#[derive(Debug, PartialEq)] +enum InvalidationStatus { + /// Transaction has been included into block and finalized at given block. + Finalized(BlockId), + /// Transaction has been invalidated. + Invalid, + /// We have lost track of transaction status. + Lost, +} + +/// Watch for transaction status until transaction is finalized or we lose track of its status. +async fn watch_transaction_status< + E: Environment, + C: Chain, + S: Stream>, +>( + environment: E, + transaction_hash: HashOf, + subscription: S, +) -> InvalidationStatus> { + futures::pin_mut!(subscription); + + loop { + match subscription.next().await { + Some(TransactionStatusOf::::Finalized((block_hash, _))) => { + // the only "successful" outcome of this method is when the block with transaction + // has been finalized + log::trace!( + target: "bridge", + "{} transaction {:?} has been finalized at block: {:?}", + C::NAME, + transaction_hash, + block_hash, + ); + + let header_id = match environment.header_id_by_hash(block_hash).await { + Ok(header_id) => header_id, + Err(e) => { + log::error!( + target: "bridge", + "Failed to read header {:?} when watching for {} transaction {:?}: {:?}", + block_hash, + C::NAME, + transaction_hash, + e, + ); + // that's the best option we have here + return InvalidationStatus::Lost + }, + }; + return InvalidationStatus::Finalized(header_id) + }, + Some(TransactionStatusOf::::Invalid) => { + // if node says that the transaction is invalid, there are still chances that + // it is not actually invalid - e.g. if the block where transaction has been + // revalidated is retracted and transaction (at some other node pool) becomes + // valid again on other fork. But let's assume that the chances of this event + // are almost zero - there's a lot of things that must happen for this to be the + // case. + log::trace!( + target: "bridge", + "{} transaction {:?} has been invalidated", + C::NAME, + transaction_hash, + ); + return InvalidationStatus::Invalid + }, + Some(TransactionStatusOf::::Future) | + Some(TransactionStatusOf::::Ready) | + Some(TransactionStatusOf::::Broadcast(_)) => { + // nothing important (for us) has happened + }, + Some(TransactionStatusOf::::InBlock(block_hash)) => { + // TODO: read matching system event (ExtrinsicSuccess or ExtrinsicFailed), log it + // here and use it later (on finality) for reporting invalid transaction + // https://github.com/paritytech/parity-bridges-common/issues/1464 + log::trace!( + target: "bridge", + "{} transaction {:?} has been included in block: {:?}", + C::NAME, + transaction_hash, + block_hash, + ); + }, + Some(TransactionStatusOf::::Retracted(block_hash)) => { + log::trace!( + target: "bridge", + "{} transaction {:?} at block {:?} has been retracted", + C::NAME, + transaction_hash, + block_hash, + ); + }, + Some(TransactionStatusOf::::FinalityTimeout(block_hash)) => { + // finality is lagging? let's wait a bit more and report a stall + log::trace!( + target: "bridge", + "{} transaction {:?} block {:?} has not been finalized for too long", + C::NAME, + transaction_hash, + block_hash, + ); + return InvalidationStatus::Lost + }, + Some(TransactionStatusOf::::Usurped(new_transaction_hash)) => { + // this may be result of our transaction resubmitter work or some manual + // intervention. In both cases - let's start stall timeout, because the meaning + // of transaction may have changed + log::trace!( + target: "bridge", + "{} transaction {:?} has been usurped by new transaction: {:?}", + C::NAME, + transaction_hash, + new_transaction_hash, + ); + return InvalidationStatus::Lost + }, + Some(TransactionStatusOf::::Dropped) => { + // the transaction has been removed from the pool because of its limits. Let's wait + // a bit and report a stall + log::trace!( + target: "bridge", + "{} transaction {:?} has been dropped from the pool", + C::NAME, + transaction_hash, + ); + return InvalidationStatus::Lost + }, + None => { + // the status of transaction is unknown to us (the subscription has been closed?). + // Let's wait a bit and report a stall + return InvalidationStatus::Lost + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_chain::TestChain; + use futures::{FutureExt, SinkExt}; + use sc_transaction_pool_api::TransactionStatus; + + struct TestEnvironment(Result, Error>); + + #[async_trait] + impl Environment for TestEnvironment { + async fn header_id_by_hash( + &self, + _hash: HashOf, + ) -> Result, Error> { + self.0.as_ref().map_err(|_| Error::BridgePalletIsNotInitialized).cloned() + } + } + + async fn on_transaction_status( + status: TransactionStatus, HashOf>, + ) -> Option<( + TrackedTransactionStatus>, + InvalidationStatus>, + )> { + let (mut sender, receiver) = futures::channel::mpsc::channel(1); + let tx_tracker = TransactionTracker::::new( + TestEnvironment(Ok(HeaderId(0, Default::default()))), + Duration::from_secs(0), + Default::default(), + Subscription(async_std::sync::Mutex::new(receiver)), + ); + + let wait_for_stall_timeout = futures::future::pending(); + let wait_for_stall_timeout_rest = futures::future::ready(()); + sender.send(Some(status)).await.unwrap(); + tx_tracker + .do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest) + .now_or_never() + .map(|(ts, is)| (ts, is.unwrap())) + } + + #[async_std::test] + async fn returns_finalized_on_finalized() { + assert_eq!( + on_transaction_status(TransactionStatus::Finalized(Default::default())).await, + Some(( + TrackedTransactionStatus::Finalized(Default::default()), + InvalidationStatus::Finalized(Default::default()) + )), + ); + } + + #[async_std::test] + async fn returns_lost_on_finalized_and_environment_error() { + assert_eq!( + watch_transaction_status::<_, TestChain, _>( + TestEnvironment(Err(Error::BridgePalletIsNotInitialized)), + Default::default(), + futures::stream::iter([TransactionStatus::Finalized(Default::default())]) + ) + .now_or_never(), + Some(InvalidationStatus::Lost), + ); + } + + #[async_std::test] + async fn returns_invalid_on_invalid() { + assert_eq!( + on_transaction_status(TransactionStatus::Invalid).await, + Some((TrackedTransactionStatus::Lost, InvalidationStatus::Invalid)), + ); + } + + #[async_std::test] + async fn waits_on_future() { + assert_eq!(on_transaction_status(TransactionStatus::Future).await, None,); + } + + #[async_std::test] + async fn waits_on_ready() { + assert_eq!(on_transaction_status(TransactionStatus::Ready).await, None,); + } + + #[async_std::test] + async fn waits_on_broadcast() { + assert_eq!( + on_transaction_status(TransactionStatus::Broadcast(Default::default())).await, + None, + ); + } + + #[async_std::test] + async fn waits_on_in_block() { + assert_eq!( + on_transaction_status(TransactionStatus::InBlock(Default::default())).await, + None, + ); + } + + #[async_std::test] + async fn waits_on_retracted() { + assert_eq!( + on_transaction_status(TransactionStatus::Retracted(Default::default())).await, + None, + ); + } + + #[async_std::test] + async fn lost_on_finality_timeout() { + assert_eq!( + on_transaction_status(TransactionStatus::FinalityTimeout(Default::default())).await, + Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)), + ); + } + + #[async_std::test] + async fn lost_on_usurped() { + assert_eq!( + on_transaction_status(TransactionStatus::Usurped(Default::default())).await, + Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)), + ); + } + + #[async_std::test] + async fn lost_on_dropped() { + assert_eq!( + on_transaction_status(TransactionStatus::Dropped).await, + Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)), + ); + } + + #[async_std::test] + async fn lost_on_subscription_error() { + assert_eq!( + watch_transaction_status::<_, TestChain, _>( + TestEnvironment(Ok(HeaderId(0, Default::default()))), + Default::default(), + futures::stream::iter([]) + ) + .now_or_never(), + Some(InvalidationStatus::Lost), + ); + } + + #[async_std::test] + async fn lost_on_timeout_when_waiting_for_invalidation_status() { + let (_sender, receiver) = futures::channel::mpsc::channel(1); + let tx_tracker = TransactionTracker::::new( + TestEnvironment(Ok(HeaderId(0, Default::default()))), + Duration::from_secs(0), + Default::default(), + Subscription(async_std::sync::Mutex::new(receiver)), + ); + + let wait_for_stall_timeout = futures::future::ready(()).shared(); + let wait_for_stall_timeout_rest = wait_for_stall_timeout.clone(); + let wait_result = tx_tracker + .do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest) + .now_or_never(); + + assert_eq!(wait_result, Some((TrackedTransactionStatus::Lost, None))); + } +} diff --git a/relays/client-westend/Cargo.toml b/relays/client-westend/Cargo.toml new file mode 100644 index 00000000000..57d2ca3b1e7 --- /dev/null +++ b/relays/client-westend/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "relay-westend-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-westend = { path = "../../primitives/chain-westend" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-westend/src/lib.rs b/relays/client-westend/src/lib.rs new file mode 100644 index 00000000000..6a2944629be --- /dev/null +++ b/relays/client-westend/src/lib.rs @@ -0,0 +1,119 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the Westend chain. + +use frame_support::weights::Weight; +use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, RelayChain}; +use sp_core::storage::StorageKey; +use std::time::Duration; + +/// Westend header id. +pub type HeaderId = relay_utils::HeaderId; + +/// Westend header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; + +/// Westend chain definition +#[derive(Debug, Clone, Copy)] +pub struct Westend; + +impl ChainBase for Westend { + type BlockNumber = bp_westend::BlockNumber; + type Hash = bp_westend::Hash; + type Hasher = bp_westend::Hasher; + type Header = bp_westend::Header; + + type AccountId = bp_westend::AccountId; + type Balance = bp_westend::Balance; + type Index = bp_westend::Nonce; + type Signature = bp_westend::Signature; + + fn max_extrinsic_size() -> u32 { + bp_westend::Westend::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_westend::Westend::max_extrinsic_weight() + } +} + +impl Chain for Westend { + const NAME: &'static str = "Westend"; + const TOKEN_ID: Option<&'static str> = None; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type SignedBlock = bp_westend::SignedBlock; + type Call = (); +} + +impl RelayChain for Westend { + const PARAS_PALLET_NAME: &'static str = bp_westend::PARAS_PALLET_NAME; + const PARACHAINS_FINALITY_PALLET_NAME: &'static str = + bp_westend::WITH_WESTEND_BRIDGE_PARAS_PALLET_NAME; +} + +impl ChainWithGrandpa for Westend { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = + bp_westend::WITH_WESTEND_GRANDPA_PALLET_NAME; +} + +impl ChainWithBalances for Westend { + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey { + StorageKey(bp_westend::account_info_storage_key(account_id)) + } +} + +/// Westmint parachain definition +#[derive(Debug, Clone, Copy)] +pub struct Westmint; + +// Westmint seems to use the same configuration as all Polkadot-like chains, so we'll use Westend +// primitives here. +impl ChainBase for Westmint { + type BlockNumber = bp_westend::BlockNumber; + type Hash = bp_westend::Hash; + type Hasher = bp_westend::Hasher; + type Header = bp_westend::Header; + + type AccountId = bp_westend::AccountId; + type Balance = bp_westend::Balance; + type Index = bp_westend::Nonce; + type Signature = bp_westend::Signature; + + fn max_extrinsic_size() -> u32 { + bp_westend::Westend::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_westend::Westend::max_extrinsic_weight() + } +} + +// Westmint seems to use the same configuration as all Polkadot-like chains, so we'll use Westend +// primitives here. +impl Chain for Westmint { + const NAME: &'static str = "Westmint"; + const TOKEN_ID: Option<&'static str> = None; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_westend::BEST_FINALIZED_WESTMINT_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type SignedBlock = bp_westend::SignedBlock; + type Call = (); +} diff --git a/relays/client-wococo/Cargo.toml b/relays/client-wococo/Cargo.toml new file mode 100644 index 00000000000..5b97694af1c --- /dev/null +++ b/relays/client-wococo/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "relay-wococo-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +relay-substrate-client = { path = "../client-substrate" } +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-wococo = { path = "../../primitives/chain-wococo" } + +# Substrate Dependencies +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/client-wococo/src/lib.rs b/relays/client-wococo/src/lib.rs new file mode 100644 index 00000000000..04b5193afd3 --- /dev/null +++ b/relays/client-wococo/src/lib.rs @@ -0,0 +1,78 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types used to connect to the Wococo-Substrate chain. + +use frame_support::weights::Weight; +use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, RelayChain}; +use sp_core::storage::StorageKey; +use std::time::Duration; + +/// Wococo header id. +pub type HeaderId = relay_utils::HeaderId; + +/// Wococo header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; + +/// Wococo chain definition +#[derive(Debug, Clone, Copy)] +pub struct Wococo; + +impl ChainBase for Wococo { + type BlockNumber = bp_wococo::BlockNumber; + type Hash = bp_wococo::Hash; + type Hasher = bp_wococo::Hashing; + type Header = bp_wococo::Header; + + type AccountId = bp_wococo::AccountId; + type Balance = bp_wococo::Balance; + type Index = bp_wococo::Nonce; + type Signature = bp_wococo::Signature; + + fn max_extrinsic_size() -> u32 { + bp_wococo::Wococo::max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + bp_wococo::Wococo::max_extrinsic_weight() + } +} + +impl Chain for Wococo { + const NAME: &'static str = "Wococo"; + const TOKEN_ID: Option<&'static str> = None; + const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = + bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type SignedBlock = bp_wococo::SignedBlock; + type Call = (); +} + +impl ChainWithGrandpa for Wococo { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_wococo::WITH_WOCOCO_GRANDPA_PALLET_NAME; +} + +impl ChainWithBalances for Wococo { + fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey { + StorageKey(bp_wococo::account_info_storage_key(account_id)) + } +} + +impl RelayChain for Wococo { + const PARAS_PALLET_NAME: &'static str = bp_wococo::PARAS_PALLET_NAME; + const PARACHAINS_FINALITY_PALLET_NAME: &'static str = "bridgeWococoParachain"; +} diff --git a/relays/finality/Cargo.toml b/relays/finality/Cargo.toml new file mode 100644 index 00000000000..ae4be5b5524 --- /dev/null +++ b/relays/finality/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "finality-relay" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +description = "Finality proofs relay" + +[dependencies] +async-std = "1.6.5" +async-trait = "0.1" +backoff = "0.2" +bp-header-chain = { path = "../../primitives/header-chain" } +futures = "0.3.5" +log = "0.4.17" +num-traits = "0.2" +relay-utils = { path = "../utils" } + +[dev-dependencies] +parking_lot = "0.11.0" diff --git a/relays/finality/src/finality_loop.rs b/relays/finality/src/finality_loop.rs new file mode 100644 index 00000000000..1ee1a8d9db6 --- /dev/null +++ b/relays/finality/src/finality_loop.rs @@ -0,0 +1,761 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! The loop basically reads all missing headers and their finality proofs from the source client. +//! The proof for the best possible header is then submitted to the target node. The only exception +//! is the mandatory headers, which we always submit to the target node. For such headers, we +//! assume that the persistent proof either exists, or will eventually become available. + +use crate::{ + sync_loop_metrics::SyncLoopMetrics, FinalityProof, FinalitySyncPipeline, SourceHeader, +}; + +use async_trait::async_trait; +use backoff::backoff::Backoff; +use futures::{select, Future, FutureExt, Stream, StreamExt}; +use num_traits::{One, Saturating}; +use relay_utils::{ + metrics::MetricsParams, relay_loop::Client as RelayClient, retry_backoff, FailedClient, + HeaderId, MaybeConnectionError, TrackedTransactionStatus, TransactionTracker, +}; +use std::{ + fmt::Debug, + pin::Pin, + time::{Duration, Instant}, +}; + +/// Finality proof synchronization loop parameters. +#[derive(Debug, Clone)] +pub struct FinalitySyncParams { + /// Interval at which we check updates on both clients. Normally should be larger than + /// `min(source_block_time, target_block_time)`. + /// + /// This parameter may be used to limit transactions rate. Increase the value && you'll get + /// infrequent updates => sparse headers => potential slow down of bridge applications, but + /// pallet storage won't be super large. Decrease the value to near `source_block_time` and + /// you'll get transaction for (almost) every block of the source chain => all source headers + /// will be known to the target chain => bridge applications will run faster, but pallet + /// storage may explode (but if pruning is there, then it's fine). + pub tick: Duration, + /// Number of finality proofs to keep in internal buffer between loop iterations. + /// + /// While in "major syncing" state, we still read finality proofs from the stream. They're + /// stored in the internal buffer between loop iterations. When we're close to the tip of the + /// chain, we may meet finality delays if headers are not finalized frequently. So instead of + /// waiting for next finality proof to appear in the stream, we may use existing proof from + /// that buffer. + pub recent_finality_proofs_limit: usize, + /// Timeout before we treat our transactions as lost and restart the whole sync process. + pub stall_timeout: Duration, + /// If true, only mandatory headers are relayed. + pub only_mandatory_headers: bool, +} + +/// Source client used in finality synchronization loop. +#[async_trait] +pub trait SourceClient: RelayClient { + /// Stream of new finality proofs. The stream is allowed to miss proofs for some + /// headers, even if those headers are mandatory. + type FinalityProofsStream: Stream + Send; + + /// Get best finalized block number. + async fn best_finalized_block_number(&self) -> Result; + + /// Get canonical header and its finality proof by number. + async fn header_and_finality_proof( + &self, + number: P::Number, + ) -> Result<(P::Header, Option), Self::Error>; + + /// Subscribe to new finality proofs. + async fn finality_proofs(&self) -> Result; +} + +/// Target client used in finality synchronization loop. +#[async_trait] +pub trait TargetClient: RelayClient { + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker; + + /// Get best finalized source block number. + async fn best_finalized_source_block_id( + &self, + ) -> Result, Self::Error>; + + /// Submit header finality proof. + async fn submit_finality_proof( + &self, + header: P::Header, + proof: P::FinalityProof, + ) -> Result; +} + +/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs +/// sync loop. +pub fn metrics_prefix() -> String { + format!("{}_to_{}_Sync", P::SOURCE_NAME, P::TARGET_NAME) +} + +/// Run finality proofs synchronization loop. +pub async fn run( + source_client: impl SourceClient

, + target_client: impl TargetClient

, + sync_params: FinalitySyncParams, + metrics_params: MetricsParams, + exit_signal: impl Future + 'static + Send, +) -> Result<(), relay_utils::Error> { + let exit_signal = exit_signal.shared(); + relay_utils::relay_loop(source_client, target_client) + .with_metrics(metrics_params) + .loop_metric(SyncLoopMetrics::new( + Some(&metrics_prefix::

()), + "source", + "source_at_target", + )?)? + .expose() + .await? + .run(metrics_prefix::

(), move |source_client, target_client, metrics| { + run_until_connection_lost( + source_client, + target_client, + sync_params.clone(), + metrics, + exit_signal.clone(), + ) + }) + .await +} + +/// Unjustified headers container. Ordered by header number. +pub(crate) type UnjustifiedHeaders = Vec; +/// Finality proofs container. Ordered by target header number. +pub(crate) type FinalityProofs

= + Vec<(

::Number,

::FinalityProof)>; +/// Reference to finality proofs container. +pub(crate) type FinalityProofsRef<'a, P> = + &'a [(

::Number,

::FinalityProof)]; + +/// Error that may happen inside finality synchronization loop. +#[derive(Debug)] +pub(crate) enum Error { + /// Source client request has failed with given error. + Source(SourceError), + /// Target client request has failed with given error. + Target(TargetError), + /// Finality proof for mandatory header is missing from the source node. + MissingMandatoryFinalityProof(P::Number), +} + +impl Error +where + P: FinalitySyncPipeline, + SourceError: MaybeConnectionError, + TargetError: MaybeConnectionError, +{ + fn fail_if_connection_error(&self) -> Result<(), FailedClient> { + match *self { + Error::Source(ref error) if error.is_connection_error() => Err(FailedClient::Source), + Error::Target(ref error) if error.is_connection_error() => Err(FailedClient::Target), + _ => Ok(()), + } + } +} + +/// Information about transaction that we have submitted. +#[derive(Debug, Clone)] +pub(crate) struct Transaction { + /// Submitted transaction tracker. + pub tracker: Tracker, + /// The number of the header we have submitted. + pub submitted_header_number: Number, +} + +impl Transaction { + pub async fn submit< + C: TargetClient, + P: FinalitySyncPipeline, + >( + target_client: &C, + header: P::Header, + justification: P::FinalityProof, + ) -> Result { + let submitted_header_number = header.number(); + log::debug!( + target: "bridge", + "Going to submit finality proof of {} header #{:?} to {}", + P::SOURCE_NAME, + submitted_header_number, + P::TARGET_NAME, + ); + + let tracker = target_client.submit_finality_proof(header, justification).await?; + Ok(Transaction { tracker, submitted_header_number }) + } + + pub async fn track, P: FinalitySyncPipeline>( + self, + target_client: &C, + ) -> Result<(), String> { + match self.tracker.wait().await { + TrackedTransactionStatus::Finalized(_) => { + // The transaction has been finalized, but it may have been finalized in the + // "failed" state. So let's check if the block number was actually updated. + // If it wasn't then we are stalled. + // + // Please also note that we're returning an error if we fail to read required data + // from the target client - that's the best we can do here to avoid actual stall. + target_client + .best_finalized_source_block_id() + .await + .map_err(|e| format!("failed to read best block from target node: {e:?}")) + .and_then(|best_id_at_target| { + if self.submitted_header_number > best_id_at_target.0 { + return Err(format!( + "best block at target after tx is {:?} and we've submitted {:?}", + best_id_at_target.0, self.submitted_header_number, + )) + } + Ok(()) + }) + }, + TrackedTransactionStatus::Lost => Err("transaction failed".to_string()), + } + } +} + +/// Finality proofs stream that may be restarted. +pub(crate) struct RestartableFinalityProofsStream { + /// Flag that the stream needs to be restarted. + pub(crate) needs_restart: bool, + /// The stream itself. + stream: Pin>, +} + +impl RestartableFinalityProofsStream { + pub async fn create_raw_stream< + C: SourceClient, + P: FinalitySyncPipeline, + >( + source_client: &C, + ) -> Result { + source_client.finality_proofs().await.map_err(|error| { + log::error!( + target: "bridge", + "Failed to subscribe to {} justifications: {:?}. Going to reconnect", + P::SOURCE_NAME, + error, + ); + + FailedClient::Source + }) + } + + pub async fn restart_if_scheduled< + C: SourceClient, + P: FinalitySyncPipeline, + >( + &mut self, + source_client: &C, + ) -> Result<(), FailedClient> { + if self.needs_restart { + log::warn!(target: "bridge", "{} finality proofs stream is being restarted", P::SOURCE_NAME); + + self.needs_restart = false; + self.stream = Box::pin(Self::create_raw_stream(source_client).await?); + } + Ok(()) + } + + pub fn next(&mut self) -> Option { + match self.stream.next().now_or_never() { + Some(Some(finality_proof)) => Some(finality_proof), + Some(None) => { + self.needs_restart = true; + None + }, + None => None, + } + } +} + +impl From for RestartableFinalityProofsStream { + fn from(stream: S) -> Self { + RestartableFinalityProofsStream { needs_restart: false, stream: Box::pin(stream) } + } +} + +/// Finality synchronization loop state. +pub(crate) struct FinalityLoopState<'a, P: FinalitySyncPipeline, FinalityProofsStream> { + /// Synchronization loop progress. + pub(crate) progress: &'a mut (Instant, Option), + /// Finality proofs stream. + pub(crate) finality_proofs_stream: + &'a mut RestartableFinalityProofsStream, + /// Recent finality proofs that we have read from the stream. + pub(crate) recent_finality_proofs: &'a mut FinalityProofs

, + /// Number of the last header, submitted to the target node. + pub(crate) submitted_header_number: Option, +} + +/// Run finality relay loop until connection to one of nodes is lost. +pub(crate) async fn run_until_connection_lost( + source_client: impl SourceClient

, + target_client: impl TargetClient

, + sync_params: FinalitySyncParams, + metrics_sync: Option, + exit_signal: impl Future, +) -> Result<(), FailedClient> { + let last_transaction_tracker = futures::future::Fuse::terminated(); + let exit_signal = exit_signal.fuse(); + futures::pin_mut!(last_transaction_tracker, exit_signal); + + let mut finality_proofs_stream = + RestartableFinalityProofsStream::create_raw_stream(&source_client).await?.into(); + let mut recent_finality_proofs = Vec::new(); + + let mut progress = (Instant::now(), None); + let mut retry_backoff = retry_backoff(); + let mut last_submitted_header_number = None; + + loop { + // run loop iteration + let iteration_result = run_loop_iteration( + &source_client, + &target_client, + FinalityLoopState { + progress: &mut progress, + finality_proofs_stream: &mut finality_proofs_stream, + recent_finality_proofs: &mut recent_finality_proofs, + submitted_header_number: last_submitted_header_number, + }, + &sync_params, + &metrics_sync, + ) + .await; + + // deal with errors + let next_tick = match iteration_result { + Ok(Some(updated_transaction)) => { + last_submitted_header_number = Some(updated_transaction.submitted_header_number); + last_transaction_tracker.set(updated_transaction.track(&target_client).fuse()); + retry_backoff.reset(); + sync_params.tick + }, + Ok(None) => { + retry_backoff.reset(); + sync_params.tick + }, + Err(error) => { + log::error!(target: "bridge", "Finality sync loop iteration has failed with error: {:?}", error); + error.fail_if_connection_error()?; + retry_backoff.next_backoff().unwrap_or(relay_utils::relay_loop::RECONNECT_DELAY) + }, + }; + finality_proofs_stream.restart_if_scheduled(&source_client).await?; + + // wait till exit signal, or new source block + select! { + transaction_result = last_transaction_tracker => { + transaction_result.map_err(|e| { + log::error!( + target: "bridge", + "Finality synchronization from {} to {} has stalled with error: {}. Going to restart", + P::SOURCE_NAME, + P::TARGET_NAME, + e, + ); + + // Restart the loop if we're stalled. + FailedClient::Both + })? + }, + _ = async_std::task::sleep(next_tick).fuse() => {}, + _ = exit_signal => return Ok(()), + } + } +} + +pub(crate) async fn run_loop_iteration( + source_client: &SC, + target_client: &TC, + state: FinalityLoopState<'_, P, SC::FinalityProofsStream>, + sync_params: &FinalitySyncParams, + metrics_sync: &Option, +) -> Result>, Error> +where + P: FinalitySyncPipeline, + SC: SourceClient

, + TC: TargetClient

, +{ + // read best source headers ids from source and target nodes + let best_number_at_source = + source_client.best_finalized_block_number().await.map_err(Error::Source)?; + let best_id_at_target = + target_client.best_finalized_source_block_id().await.map_err(Error::Target)?; + let best_number_at_target = best_id_at_target.0; + + let different_hash_at_source = ensure_same_fork::(&best_id_at_target, source_client) + .await + .map_err(Error::Source)?; + let using_same_fork = different_hash_at_source.is_none(); + if let Some(ref different_hash_at_source) = different_hash_at_source { + log::error!( + target: "bridge", + "Source node ({}) and pallet at target node ({}) have different headers at the same height {:?}: \ + at-source {:?} vs at-target {:?}", + P::SOURCE_NAME, + P::TARGET_NAME, + best_number_at_target, + different_hash_at_source, + best_id_at_target.1, + ); + } + + if let Some(ref metrics_sync) = *metrics_sync { + metrics_sync.update_best_block_at_source(best_number_at_source); + metrics_sync.update_best_block_at_target(best_number_at_target); + metrics_sync.update_using_same_fork(using_same_fork); + } + *state.progress = + print_sync_progress::

(*state.progress, best_number_at_source, best_number_at_target); + + // if we have already submitted header, then we just need to wait for it + // if we're waiting too much, then we believe our transaction has been lost and restart sync + if let Some(submitted_header_number) = state.submitted_header_number { + if best_number_at_target >= submitted_header_number { + // transaction has been mined && we can continue + } else { + return Ok(None) + } + } + + // submit new header if we have something new + match select_header_to_submit( + source_client, + target_client, + state.finality_proofs_stream, + state.recent_finality_proofs, + best_number_at_source, + best_number_at_target, + sync_params, + ) + .await? + { + Some((header, justification)) => { + let transaction = Transaction::submit(target_client, header, justification) + .await + .map_err(Error::Target)?; + Ok(Some(transaction)) + }, + None => Ok(None), + } +} + +pub(crate) async fn select_header_to_submit( + source_client: &SC, + target_client: &TC, + finality_proofs_stream: &mut RestartableFinalityProofsStream, + recent_finality_proofs: &mut FinalityProofs

, + best_number_at_source: P::Number, + best_number_at_target: P::Number, + sync_params: &FinalitySyncParams, +) -> Result, Error> +where + P: FinalitySyncPipeline, + SC: SourceClient

, + TC: TargetClient

, +{ + // to see that the loop is progressing + log::trace!( + target: "bridge", + "Considering range of headers ({:?}; {:?}]", + best_number_at_target, + best_number_at_source, + ); + + // read missing headers. if we see that the header schedules GRANDPA change, we need to + // submit this header + let selected_finality_proof = read_missing_headers::( + source_client, + target_client, + best_number_at_source, + best_number_at_target, + ) + .await?; + let (mut unjustified_headers, mut selected_finality_proof) = match selected_finality_proof { + SelectedFinalityProof::Mandatory(header, finality_proof) => + return Ok(Some((header, finality_proof))), + _ if sync_params.only_mandatory_headers => { + // we are not reading finality proofs from the stream, so eventually it'll break + // but we don't care about transient proofs at all, so it is acceptable + return Ok(None) + }, + SelectedFinalityProof::Regular(unjustified_headers, header, finality_proof) => + (unjustified_headers, Some((header, finality_proof))), + SelectedFinalityProof::None(unjustified_headers) => (unjustified_headers, None), + }; + + // all headers that are missing from the target client are non-mandatory + // => even if we have already selected some header and its persistent finality proof, + // we may try to select better header by reading non-persistent proofs from the stream + read_finality_proofs_from_stream::(finality_proofs_stream, recent_finality_proofs); + selected_finality_proof = select_better_recent_finality_proof::

( + recent_finality_proofs, + &mut unjustified_headers, + selected_finality_proof, + ); + + // remove obsolete 'recent' finality proofs + keep its size under certain limit + let oldest_finality_proof_to_keep = selected_finality_proof + .as_ref() + .map(|(header, _)| header.number()) + .unwrap_or(best_number_at_target); + prune_recent_finality_proofs::

( + oldest_finality_proof_to_keep, + recent_finality_proofs, + sync_params.recent_finality_proofs_limit, + ); + + Ok(selected_finality_proof) +} + +/// Ensures that both clients are on the same fork. +/// +/// Returns `Some(_)` with header has at the source client if headers are different. +async fn ensure_same_fork>( + best_id_at_target: &HeaderId, + source_client: &SC, +) -> Result, SC::Error> { + let header_at_source = source_client.header_and_finality_proof(best_id_at_target.0).await?.0; + let header_hash_at_source = header_at_source.hash(); + Ok(if best_id_at_target.1 == header_hash_at_source { + None + } else { + Some(header_hash_at_source) + }) +} + +/// Finality proof that has been selected by the `read_missing_headers` function. +pub(crate) enum SelectedFinalityProof { + /// Mandatory header and its proof has been selected. We shall submit proof for this header. + Mandatory(Header, FinalityProof), + /// Regular header and its proof has been selected. We may submit this proof, or proof for + /// some better header. + Regular(UnjustifiedHeaders

, Header, FinalityProof), + /// We haven't found any missing header with persistent proof at the target client. + None(UnjustifiedHeaders
), +} + +/// Read missing headers and their persistent finality proofs from the target client. +/// +/// If we have found some header with known proof, it is returned. +/// Otherwise, `SelectedFinalityProof::None` is returned. +/// +/// Unless we have found mandatory header, all missing headers are collected and returned. +pub(crate) async fn read_missing_headers< + P: FinalitySyncPipeline, + SC: SourceClient

, + TC: TargetClient

, +>( + source_client: &SC, + _target_client: &TC, + best_number_at_source: P::Number, + best_number_at_target: P::Number, +) -> Result, Error> { + let mut unjustified_headers = Vec::new(); + let mut selected_finality_proof = None; + let mut header_number = best_number_at_target + One::one(); + while header_number <= best_number_at_source { + let (header, finality_proof) = source_client + .header_and_finality_proof(header_number) + .await + .map_err(Error::Source)?; + let is_mandatory = header.is_mandatory(); + + match (is_mandatory, finality_proof) { + (true, Some(finality_proof)) => { + log::trace!(target: "bridge", "Header {:?} is mandatory", header_number); + return Ok(SelectedFinalityProof::Mandatory(header, finality_proof)) + }, + (true, None) => return Err(Error::MissingMandatoryFinalityProof(header.number())), + (false, Some(finality_proof)) => { + log::trace!(target: "bridge", "Header {:?} has persistent finality proof", header_number); + unjustified_headers.clear(); + selected_finality_proof = Some((header, finality_proof)); + }, + (false, None) => { + unjustified_headers.push(header); + }, + } + + header_number = header_number + One::one(); + } + + log::trace!( + target: "bridge", + "Read {} {} headers. Selected finality proof for header: {:?}", + best_number_at_source.saturating_sub(best_number_at_target), + P::SOURCE_NAME, + selected_finality_proof.as_ref().map(|(header, _)| header), + ); + + Ok(match selected_finality_proof { + Some((header, proof)) => SelectedFinalityProof::Regular(unjustified_headers, header, proof), + None => SelectedFinalityProof::None(unjustified_headers), + }) +} + +/// Read finality proofs from the stream. +pub(crate) fn read_finality_proofs_from_stream< + P: FinalitySyncPipeline, + FPS: Stream, +>( + finality_proofs_stream: &mut RestartableFinalityProofsStream, + recent_finality_proofs: &mut FinalityProofs

, +) { + let mut proofs_count = 0; + let mut first_header_number = None; + let mut last_header_number = None; + while let Some(finality_proof) = finality_proofs_stream.next() { + let target_header_number = finality_proof.target_header_number(); + if first_header_number.is_none() { + first_header_number = Some(target_header_number); + } + last_header_number = Some(target_header_number); + proofs_count += 1; + + recent_finality_proofs.push((target_header_number, finality_proof)); + } + + if proofs_count != 0 { + log::trace!( + target: "bridge", + "Read {} finality proofs from {} finality stream for headers in range [{:?}; {:?}]", + proofs_count, + P::SOURCE_NAME, + first_header_number, + last_header_number, + ); + } +} + +/// Try to select better header and its proof, given finality proofs that we +/// have recently read from the stream. +pub(crate) fn select_better_recent_finality_proof( + recent_finality_proofs: FinalityProofsRef

, + unjustified_headers: &mut UnjustifiedHeaders, + selected_finality_proof: Option<(P::Header, P::FinalityProof)>, +) -> Option<(P::Header, P::FinalityProof)> { + if unjustified_headers.is_empty() || recent_finality_proofs.is_empty() { + log::trace!( + target: "bridge", + "Can not improve selected {} finality proof {:?}. No unjustified headers and recent proofs", + P::SOURCE_NAME, + selected_finality_proof.as_ref().map(|(h, _)| h.number()), + ); + return selected_finality_proof + } + + const NOT_EMPTY_PROOF: &str = "we have checked that the vec is not empty; qed"; + + // we need proofs for headers in range unjustified_range_begin..=unjustified_range_end + let unjustified_range_begin = unjustified_headers.first().expect(NOT_EMPTY_PROOF).number(); + let unjustified_range_end = unjustified_headers.last().expect(NOT_EMPTY_PROOF).number(); + + // we have proofs for headers in range buffered_range_begin..=buffered_range_end + let buffered_range_begin = recent_finality_proofs.first().expect(NOT_EMPTY_PROOF).0; + let buffered_range_end = recent_finality_proofs.last().expect(NOT_EMPTY_PROOF).0; + + // we have two ranges => find intersection + let intersection_begin = std::cmp::max(unjustified_range_begin, buffered_range_begin); + let intersection_end = std::cmp::min(unjustified_range_end, buffered_range_end); + let intersection = intersection_begin..=intersection_end; + + // find last proof from intersection + let selected_finality_proof_index = recent_finality_proofs + .binary_search_by_key(intersection.end(), |(number, _)| *number) + .unwrap_or_else(|index| index.saturating_sub(1)); + let (selected_header_number, finality_proof) = + &recent_finality_proofs[selected_finality_proof_index]; + let has_selected_finality_proof = intersection.contains(selected_header_number); + log::trace!( + target: "bridge", + "Trying to improve selected {} finality proof {:?}. Headers range: [{:?}; {:?}]. Proofs range: [{:?}; {:?}].\ + Trying to improve to: {:?}. Result: {}", + P::SOURCE_NAME, + selected_finality_proof.as_ref().map(|(h, _)| h.number()), + unjustified_range_begin, + unjustified_range_end, + buffered_range_begin, + buffered_range_end, + selected_header_number, + if has_selected_finality_proof { "improved" } else { "not improved" }, + ); + if !has_selected_finality_proof { + return selected_finality_proof + } + + // now remove all obsolete headers and extract selected header + let selected_header_position = unjustified_headers + .binary_search_by_key(selected_header_number, |header| header.number()) + .expect("unjustified_headers contain all headers from intersection; qed"); + let selected_header = unjustified_headers.swap_remove(selected_header_position); + Some((selected_header, finality_proof.clone())) +} + +pub(crate) fn prune_recent_finality_proofs( + justified_header_number: P::Number, + recent_finality_proofs: &mut FinalityProofs

, + recent_finality_proofs_limit: usize, +) { + let justified_header_idx = recent_finality_proofs + .binary_search_by_key(&justified_header_number, |(header_number, _)| *header_number) + .map(|idx| idx + 1) + .unwrap_or_else(|idx| idx); + let proofs_limit_idx = + recent_finality_proofs.len().saturating_sub(recent_finality_proofs_limit); + + *recent_finality_proofs = + recent_finality_proofs.split_off(std::cmp::max(justified_header_idx, proofs_limit_idx)); +} + +fn print_sync_progress( + progress_context: (Instant, Option), + best_number_at_source: P::Number, + best_number_at_target: P::Number, +) -> (Instant, Option) { + let (prev_time, prev_best_number_at_target) = progress_context; + let now = Instant::now(); + + let need_update = now - prev_time > Duration::from_secs(10) || + prev_best_number_at_target + .map(|prev_best_number_at_target| { + best_number_at_target.saturating_sub(prev_best_number_at_target) > 10.into() + }) + .unwrap_or(true); + + if !need_update { + return (prev_time, prev_best_number_at_target) + } + + log::info!( + target: "bridge", + "Synced {:?} of {:?} headers", + best_number_at_target, + best_number_at_source, + ); + (now, Some(best_number_at_target)) +} diff --git a/relays/finality/src/finality_loop_tests.rs b/relays/finality/src/finality_loop_tests.rs new file mode 100644 index 00000000000..1853c095f70 --- /dev/null +++ b/relays/finality/src/finality_loop_tests.rs @@ -0,0 +1,598 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tests for finality synchronization loop. + +#![cfg(test)] + +use crate::{ + finality_loop::{ + prune_recent_finality_proofs, read_finality_proofs_from_stream, run_loop_iteration, + run_until_connection_lost, select_better_recent_finality_proof, select_header_to_submit, + FinalityLoopState, FinalityProofs, FinalitySyncParams, RestartableFinalityProofsStream, + SourceClient, TargetClient, + }, + sync_loop_metrics::SyncLoopMetrics, + FinalityProof, FinalitySyncPipeline, SourceHeader, +}; + +use async_trait::async_trait; +use bp_header_chain::GrandpaConsensusLogReader; +use futures::{FutureExt, Stream, StreamExt}; +use parking_lot::Mutex; +use relay_utils::{ + relay_loop::Client as RelayClient, FailedClient, HeaderId, MaybeConnectionError, + TrackedTransactionStatus, TransactionTracker, +}; +use std::{ + collections::HashMap, + pin::Pin, + sync::Arc, + time::{Duration, Instant}, +}; + +type IsMandatory = bool; +type TestNumber = u64; +type TestHash = u64; + +#[derive(Clone, Debug)] +struct TestTransactionTracker(TrackedTransactionStatus>); + +impl Default for TestTransactionTracker { + fn default() -> TestTransactionTracker { + TestTransactionTracker(TrackedTransactionStatus::Finalized(Default::default())) + } +} + +#[async_trait] +impl TransactionTracker for TestTransactionTracker { + type HeaderId = HeaderId; + + async fn wait(self) -> TrackedTransactionStatus> { + self.0 + } +} + +#[derive(Debug, Clone)] +enum TestError { + NonConnection, +} + +impl MaybeConnectionError for TestError { + fn is_connection_error(&self) -> bool { + false + } +} + +#[derive(Debug, Clone)] +struct TestFinalitySyncPipeline; + +impl FinalitySyncPipeline for TestFinalitySyncPipeline { + const SOURCE_NAME: &'static str = "TestSource"; + const TARGET_NAME: &'static str = "TestTarget"; + + type Hash = TestHash; + type Number = TestNumber; + type ConsensusLogReader = GrandpaConsensusLogReader; + type Header = TestSourceHeader; + type FinalityProof = TestFinalityProof; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct TestSourceHeader(IsMandatory, TestNumber, TestHash); + +impl SourceHeader> + for TestSourceHeader +{ + fn hash(&self) -> TestHash { + self.2 + } + + fn number(&self) -> TestNumber { + self.1 + } + + fn is_mandatory(&self) -> bool { + self.0 + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct TestFinalityProof(TestNumber); + +impl FinalityProof for TestFinalityProof { + fn target_header_number(&self) -> TestNumber { + self.0 + } +} + +#[derive(Debug, Clone, Default)] +struct ClientsData { + source_best_block_number: TestNumber, + source_headers: HashMap)>, + source_proofs: Vec, + + target_best_block_id: HeaderId, + target_headers: Vec<(TestSourceHeader, TestFinalityProof)>, + target_transaction_tracker: TestTransactionTracker, +} + +#[derive(Clone)] +struct TestSourceClient { + on_method_call: Arc, + data: Arc>, +} + +#[async_trait] +impl RelayClient for TestSourceClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + unreachable!() + } +} + +#[async_trait] +impl SourceClient for TestSourceClient { + type FinalityProofsStream = Pin + 'static + Send>>; + + async fn best_finalized_block_number(&self) -> Result { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + Ok(data.source_best_block_number) + } + + async fn header_and_finality_proof( + &self, + number: TestNumber, + ) -> Result<(TestSourceHeader, Option), TestError> { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + data.source_headers.get(&number).cloned().ok_or(TestError::NonConnection) + } + + async fn finality_proofs(&self) -> Result { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + Ok(futures::stream::iter(data.source_proofs.clone()).boxed()) + } +} + +#[derive(Clone)] +struct TestTargetClient { + on_method_call: Arc, + data: Arc>, +} + +#[async_trait] +impl RelayClient for TestTargetClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + unreachable!() + } +} + +#[async_trait] +impl TargetClient for TestTargetClient { + type TransactionTracker = TestTransactionTracker; + + async fn best_finalized_source_block_id( + &self, + ) -> Result, TestError> { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + Ok(data.target_best_block_id) + } + + async fn submit_finality_proof( + &self, + header: TestSourceHeader, + proof: TestFinalityProof, + ) -> Result { + let mut data = self.data.lock(); + (self.on_method_call)(&mut data); + data.target_best_block_id = HeaderId(header.number(), header.hash()); + data.target_headers.push((header, proof)); + (self.on_method_call)(&mut data); + Ok(data.target_transaction_tracker.clone()) + } +} + +fn prepare_test_clients( + exit_sender: futures::channel::mpsc::UnboundedSender<()>, + state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static, + source_headers: HashMap)>, +) -> (TestSourceClient, TestTargetClient) { + let internal_state_function: Arc = + Arc::new(move |data| { + if state_function(data) { + exit_sender.unbounded_send(()).unwrap(); + } + }); + let clients_data = Arc::new(Mutex::new(ClientsData { + source_best_block_number: 10, + source_headers, + source_proofs: vec![TestFinalityProof(12), TestFinalityProof(14)], + + target_best_block_id: HeaderId(5, 5), + target_headers: vec![], + target_transaction_tracker: TestTransactionTracker(TrackedTransactionStatus::Finalized( + Default::default(), + )), + })); + ( + TestSourceClient { + on_method_call: internal_state_function.clone(), + data: clients_data.clone(), + }, + TestTargetClient { on_method_call: internal_state_function, data: clients_data }, + ) +} + +fn test_sync_params() -> FinalitySyncParams { + FinalitySyncParams { + tick: Duration::from_secs(0), + recent_finality_proofs_limit: 1024, + stall_timeout: Duration::from_secs(1), + only_mandatory_headers: false, + } +} + +fn run_sync_loop( + state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static, +) -> (ClientsData, Result<(), FailedClient>) { + let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded(); + let (source_client, target_client) = prepare_test_clients( + exit_sender, + state_function, + vec![ + (5, (TestSourceHeader(false, 5, 5), None)), + (6, (TestSourceHeader(false, 6, 6), None)), + (7, (TestSourceHeader(false, 7, 7), Some(TestFinalityProof(7)))), + (8, (TestSourceHeader(true, 8, 8), Some(TestFinalityProof(8)))), + (9, (TestSourceHeader(false, 9, 9), Some(TestFinalityProof(9)))), + (10, (TestSourceHeader(false, 10, 10), None)), + ] + .into_iter() + .collect(), + ); + let sync_params = test_sync_params(); + + let clients_data = source_client.data.clone(); + let result = async_std::task::block_on(run_until_connection_lost( + source_client, + target_client, + sync_params, + None, + exit_receiver.into_future().map(|(_, _)| ()), + )); + + let clients_data = clients_data.lock().clone(); + (clients_data, result) +} + +#[test] +fn finality_sync_loop_works() { + let (client_data, result) = run_sync_loop(|data| { + // header#7 has persistent finality proof, but it isn't mandatory => it isn't submitted, + // because header#8 has persistent finality proof && it is mandatory => it is submitted + // header#9 has persistent finality proof, but it isn't mandatory => it is submitted, + // because there are no more persistent finality proofs + // + // once this ^^^ is done, we generate more blocks && read proof for blocks 12 and 14 from + // the stream + if data.target_best_block_id.0 == 9 { + data.source_best_block_number = 14; + data.source_headers.insert(11, (TestSourceHeader(false, 11, 11), None)); + data.source_headers + .insert(12, (TestSourceHeader(false, 12, 12), Some(TestFinalityProof(12)))); + data.source_headers.insert(13, (TestSourceHeader(false, 13, 13), None)); + data.source_headers + .insert(14, (TestSourceHeader(false, 14, 14), Some(TestFinalityProof(14)))); + } + // once this ^^^ is done, we generate more blocks && read persistent proof for block 16 + if data.target_best_block_id.0 == 14 { + data.source_best_block_number = 17; + data.source_headers.insert(15, (TestSourceHeader(false, 15, 15), None)); + data.source_headers + .insert(16, (TestSourceHeader(false, 16, 16), Some(TestFinalityProof(16)))); + data.source_headers.insert(17, (TestSourceHeader(false, 17, 17), None)); + } + + data.target_best_block_id.0 == 16 + }); + + assert_eq!(result, Ok(())); + assert_eq!( + client_data.target_headers, + vec![ + // before adding 11..14: finality proof for mandatory header#8 + (TestSourceHeader(true, 8, 8), TestFinalityProof(8)), + // before adding 11..14: persistent finality proof for non-mandatory header#9 + (TestSourceHeader(false, 9, 9), TestFinalityProof(9)), + // after adding 11..14: ephemeral finality proof for non-mandatory header#14 + (TestSourceHeader(false, 14, 14), TestFinalityProof(14)), + // after adding 15..17: persistent finality proof for non-mandatory header#16 + (TestSourceHeader(false, 16, 16), TestFinalityProof(16)), + ], + ); +} + +fn run_only_mandatory_headers_mode_test( + only_mandatory_headers: bool, + has_mandatory_headers: bool, +) -> Option<(TestSourceHeader, TestFinalityProof)> { + let (exit_sender, _) = futures::channel::mpsc::unbounded(); + let (source_client, target_client) = prepare_test_clients( + exit_sender, + |_| false, + vec![ + (6, (TestSourceHeader(false, 6, 6), Some(TestFinalityProof(6)))), + (7, (TestSourceHeader(false, 7, 7), Some(TestFinalityProof(7)))), + (8, (TestSourceHeader(has_mandatory_headers, 8, 8), Some(TestFinalityProof(8)))), + (9, (TestSourceHeader(false, 9, 9), Some(TestFinalityProof(9)))), + (10, (TestSourceHeader(false, 10, 10), Some(TestFinalityProof(10)))), + ] + .into_iter() + .collect(), + ); + async_std::task::block_on(select_header_to_submit( + &source_client, + &target_client, + &mut RestartableFinalityProofsStream::from(futures::stream::empty().boxed()), + &mut vec![], + 10, + 5, + &FinalitySyncParams { + tick: Duration::from_secs(0), + recent_finality_proofs_limit: 0, + stall_timeout: Duration::from_secs(0), + only_mandatory_headers, + }, + )) + .unwrap() +} + +#[test] +fn select_header_to_submit_skips_non_mandatory_headers_when_only_mandatory_headers_are_required() { + assert_eq!(run_only_mandatory_headers_mode_test(true, false), None); + assert_eq!( + run_only_mandatory_headers_mode_test(false, false), + Some((TestSourceHeader(false, 10, 10), TestFinalityProof(10))), + ); +} + +#[test] +fn select_header_to_submit_selects_mandatory_headers_when_only_mandatory_headers_are_required() { + assert_eq!( + run_only_mandatory_headers_mode_test(true, true), + Some((TestSourceHeader(true, 8, 8), TestFinalityProof(8))), + ); + assert_eq!( + run_only_mandatory_headers_mode_test(false, true), + Some((TestSourceHeader(true, 8, 8), TestFinalityProof(8))), + ); +} + +#[test] +fn select_better_recent_finality_proof_works() { + // if there are no unjustified headers, nothing is changed + assert_eq!( + select_better_recent_finality_proof::( + &[(5, TestFinalityProof(5))], + &mut vec![], + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ), + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ); + + // if there are no recent finality proofs, nothing is changed + assert_eq!( + select_better_recent_finality_proof::( + &[], + &mut vec![TestSourceHeader(false, 5, 5)], + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ), + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ); + + // if there's no intersection between recent finality proofs and unjustified headers, nothing is + // changed + let mut unjustified_headers = + vec![TestSourceHeader(false, 9, 9), TestSourceHeader(false, 10, 10)]; + assert_eq!( + select_better_recent_finality_proof::( + &[(1, TestFinalityProof(1)), (4, TestFinalityProof(4))], + &mut unjustified_headers, + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ), + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ); + + // if there's intersection between recent finality proofs and unjustified headers, but there are + // no proofs in this intersection, nothing is changed + let mut unjustified_headers = vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(false, 9, 9), + TestSourceHeader(false, 10, 10), + ]; + assert_eq!( + select_better_recent_finality_proof::( + &[(7, TestFinalityProof(7)), (11, TestFinalityProof(11))], + &mut unjustified_headers, + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ), + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ); + assert_eq!( + unjustified_headers, + vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(false, 9, 9), + TestSourceHeader(false, 10, 10) + ] + ); + + // if there's intersection between recent finality proofs and unjustified headers and there's + // a proof in this intersection: + // - this better (last from intersection) proof is selected; + // - 'obsolete' unjustified headers are pruned. + let mut unjustified_headers = vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(false, 9, 9), + TestSourceHeader(false, 10, 10), + ]; + assert_eq!( + select_better_recent_finality_proof::( + &[(7, TestFinalityProof(7)), (9, TestFinalityProof(9))], + &mut unjustified_headers, + Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))), + ), + Some((TestSourceHeader(false, 9, 9), TestFinalityProof(9))), + ); +} + +#[test] +fn read_finality_proofs_from_stream_works() { + // when stream is currently empty, nothing is changed + let mut recent_finality_proofs = vec![(1, TestFinalityProof(1))]; + let mut stream = futures::stream::pending().into(); + read_finality_proofs_from_stream::( + &mut stream, + &mut recent_finality_proofs, + ); + assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1))]); + assert!(!stream.needs_restart); + + // when stream has entry with target, it is added to the recent proofs container + let mut stream = futures::stream::iter(vec![TestFinalityProof(4)]) + .chain(futures::stream::pending()) + .into(); + read_finality_proofs_from_stream::( + &mut stream, + &mut recent_finality_proofs, + ); + assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]); + assert!(!stream.needs_restart); + + // when stream has ended, we'll need to restart it + let mut stream = futures::stream::empty().into(); + read_finality_proofs_from_stream::( + &mut stream, + &mut recent_finality_proofs, + ); + assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]); + assert!(stream.needs_restart); +} + +#[test] +fn prune_recent_finality_proofs_works() { + let original_recent_finality_proofs: FinalityProofs = vec![ + (10, TestFinalityProof(10)), + (13, TestFinalityProof(13)), + (15, TestFinalityProof(15)), + (17, TestFinalityProof(17)), + (19, TestFinalityProof(19)), + ] + .into_iter() + .collect(); + + // when there's proof for justified header in the vec + let mut recent_finality_proofs = original_recent_finality_proofs.clone(); + prune_recent_finality_proofs::(10, &mut recent_finality_proofs, 1024); + assert_eq!(&original_recent_finality_proofs[1..], recent_finality_proofs,); + + // when there are no proof for justified header in the vec + let mut recent_finality_proofs = original_recent_finality_proofs.clone(); + prune_recent_finality_proofs::(11, &mut recent_finality_proofs, 1024); + assert_eq!(&original_recent_finality_proofs[1..], recent_finality_proofs,); + + // when there are too many entries after initial prune && they also need to be pruned + let mut recent_finality_proofs = original_recent_finality_proofs.clone(); + prune_recent_finality_proofs::(10, &mut recent_finality_proofs, 2); + assert_eq!(&original_recent_finality_proofs[3..], recent_finality_proofs,); + + // when last entry is pruned + let mut recent_finality_proofs = original_recent_finality_proofs.clone(); + prune_recent_finality_proofs::(19, &mut recent_finality_proofs, 2); + assert_eq!(&original_recent_finality_proofs[5..], recent_finality_proofs,); + + // when post-last entry is pruned + let mut recent_finality_proofs = original_recent_finality_proofs.clone(); + prune_recent_finality_proofs::(20, &mut recent_finality_proofs, 2); + assert_eq!(&original_recent_finality_proofs[5..], recent_finality_proofs,); +} + +#[test] +fn different_forks_at_source_and_at_target_are_detected() { + let (exit_sender, _exit_receiver) = futures::channel::mpsc::unbounded(); + let (source_client, target_client) = prepare_test_clients( + exit_sender, + |_| false, + vec![ + (5, (TestSourceHeader(false, 5, 42), None)), + (6, (TestSourceHeader(false, 6, 6), None)), + (7, (TestSourceHeader(false, 7, 7), None)), + (8, (TestSourceHeader(false, 8, 8), None)), + (9, (TestSourceHeader(false, 9, 9), None)), + (10, (TestSourceHeader(false, 10, 10), None)), + ] + .into_iter() + .collect(), + ); + + let mut progress = (Instant::now(), None); + let mut finality_proofs_stream = futures::stream::iter(vec![]).boxed().into(); + let mut recent_finality_proofs = Vec::new(); + let metrics_sync = SyncLoopMetrics::new(None, "source", "target").unwrap(); + async_std::task::block_on(run_loop_iteration::( + &source_client, + &target_client, + FinalityLoopState { + progress: &mut progress, + finality_proofs_stream: &mut finality_proofs_stream, + recent_finality_proofs: &mut recent_finality_proofs, + submitted_header_number: None, + }, + &test_sync_params(), + &Some(metrics_sync.clone()), + )) + .unwrap(); + + assert!(!metrics_sync.is_using_same_fork()); +} + +#[test] +fn stalls_when_transaction_tracker_returns_error() { + let (_, result) = run_sync_loop(|data| { + data.target_transaction_tracker = TestTransactionTracker(TrackedTransactionStatus::Lost); + data.target_best_block_id = HeaderId(5, 5); + data.target_best_block_id.0 == 16 + }); + + assert_eq!(result, Err(FailedClient::Both)); +} + +#[test] +fn stalls_when_transaction_tracker_returns_finalized_but_transaction_fails() { + let (_, result) = run_sync_loop(|data| { + data.target_best_block_id = HeaderId(5, 5); + data.target_best_block_id.0 == 16 + }); + + assert_eq!(result, Err(FailedClient::Both)); +} diff --git a/relays/finality/src/lib.rs b/relays/finality/src/lib.rs new file mode 100644 index 00000000000..dca47c6a572 --- /dev/null +++ b/relays/finality/src/lib.rs @@ -0,0 +1,61 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! This crate has single entrypoint to run synchronization loop that is built around finality +//! proofs, as opposed to headers synchronization loop, which is built around headers. The headers +//! are still submitted to the target node, but are treated as auxiliary data as we are not trying +//! to submit all source headers to the target node. + +pub use crate::{ + finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient}, + sync_loop_metrics::SyncLoopMetrics, +}; + +use bp_header_chain::{ConsensusLogReader, FinalityProof}; +use std::fmt::Debug; + +mod finality_loop; +mod finality_loop_tests; +mod sync_loop_metrics; + +/// Finality proofs synchronization pipeline. +pub trait FinalitySyncPipeline: 'static + Clone + Debug + Send + Sync { + /// Name of the finality proofs source. + const SOURCE_NAME: &'static str; + /// Name of the finality proofs target. + const TARGET_NAME: &'static str; + + /// Headers we're syncing are identified by this hash. + type Hash: Eq + Clone + Copy + Send + Sync + Debug; + /// Headers we're syncing are identified by this number. + type Number: relay_utils::BlockNumberBase; + /// A reader that can extract the consensus log from the header digest and interpret it. + type ConsensusLogReader: ConsensusLogReader; + /// Type of header that we're syncing. + type Header: SourceHeader; + /// Finality proof type. + type FinalityProof: FinalityProof; +} + +/// Header that we're receiving from source node. +pub trait SourceHeader: Clone + Debug + PartialEq + Send + Sync { + /// Returns hash of header. + fn hash(&self) -> Hash; + /// Returns number of header. + fn number(&self) -> Number; + /// Returns true if this header needs to be submitted to target node. + fn is_mandatory(&self) -> bool; +} diff --git a/relays/finality/src/sync_loop_metrics.rs b/relays/finality/src/sync_loop_metrics.rs new file mode 100644 index 00000000000..ae73bbedc4f --- /dev/null +++ b/relays/finality/src/sync_loop_metrics.rs @@ -0,0 +1,86 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Metrics for headers synchronization relay loop. + +use relay_utils::metrics::{metric_name, register, IntGauge, Metric, PrometheusError, Registry}; + +/// Headers sync metrics. +#[derive(Clone)] +pub struct SyncLoopMetrics { + /// Best syncing header at the source. + best_source_block_number: IntGauge, + /// Best syncing header at the target. + best_target_block_number: IntGauge, + /// Flag that has `0` value when best source headers at the source node and at-target-chain + /// are matching and `1` otherwise. + using_different_forks: IntGauge, +} + +impl SyncLoopMetrics { + /// Create and register headers loop metrics. + pub fn new( + prefix: Option<&str>, + at_source_chain_label: &str, + at_target_chain_label: &str, + ) -> Result { + Ok(SyncLoopMetrics { + best_source_block_number: IntGauge::new( + metric_name(prefix, &format!("best_{at_source_chain_label}_block_number")), + format!("Best block number at the {at_source_chain_label}"), + )?, + best_target_block_number: IntGauge::new( + metric_name(prefix, &format!("best_{at_target_chain_label}_block_number")), + format!("Best block number at the {at_target_chain_label}"), + )?, + using_different_forks: IntGauge::new( + metric_name(prefix, &format!("is_{at_source_chain_label}_and_{at_target_chain_label}_using_different_forks")), + "Whether the best finalized source block at target node is different (value 1) from the \ + corresponding block at the source node", + )?, + }) + } + + /// Returns current value of the using-same-fork flag. + #[cfg(test)] + pub(crate) fn is_using_same_fork(&self) -> bool { + self.using_different_forks.get() == 0 + } + + /// Update best block number at source. + pub fn update_best_block_at_source>(&self, source_best_number: Number) { + self.best_source_block_number.set(source_best_number.into()); + } + + /// Update best block number at target. + pub fn update_best_block_at_target>(&self, target_best_number: Number) { + self.best_target_block_number.set(target_best_number.into()); + } + + /// Update using-same-fork flag. + pub fn update_using_same_fork(&self, using_same_fork: bool) { + self.using_different_forks.set((!using_same_fork).into()) + } +} + +impl Metric for SyncLoopMetrics { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.best_source_block_number.clone(), registry)?; + register(self.best_target_block_number.clone(), registry)?; + register(self.using_different_forks.clone(), registry)?; + Ok(()) + } +} diff --git a/relays/lib-substrate-relay/Cargo.toml b/relays/lib-substrate-relay/Cargo.toml new file mode 100644 index 00000000000..bdf49d42eab --- /dev/null +++ b/relays/lib-substrate-relay/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "substrate-relay-helper" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +anyhow = "1.0" +thiserror = "1.0.26" +async-std = "1.9.0" +async-trait = "0.1" +codec = { package = "parity-scale-codec", version = "3.1.5" } +futures = "0.3.12" +num-traits = "0.2" +log = "0.4.17" + +# Bridge dependencies + +bp-header-chain = { path = "../../primitives/header-chain" } +bp-parachains = { path = "../../primitives/parachains" } +bp-polkadot-core = { path = "../../primitives/polkadot-core" } +bridge-runtime-common = { path = "../../bin/runtime-common" } + +finality-grandpa = { version = "0.16.0" } +finality-relay = { path = "../finality" } +parachains-relay = { path = "../parachains" } +relay-utils = { path = "../utils" } +messages-relay = { path = "../messages" } +relay-substrate-client = { path = "../client-substrate" } + +pallet-bridge-grandpa = { path = "../../modules/grandpa" } +pallet-bridge-messages = { path = "../../modules/messages" } +pallet-bridge-parachains = { path = "../../modules/parachains" } + +bp-runtime = { path = "../../primitives/runtime" } +bp-messages = { path = "../../primitives/messages" } + +# Substrate Dependencies + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } + +[dev-dependencies] +bp-millau = { path = "../../primitives/chain-millau" } +bp-rialto = { path = "../../primitives/chain-rialto" } +bp-rococo = { path = "../../primitives/chain-rococo" } +bp-wococo = { path = "../../primitives/chain-wococo" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } +relay-rialto-client = { path = "../client-rialto" } +relay-rococo-client = { path = "../client-rococo" } +relay-wococo-client = { path = "../client-wococo" } +rialto-runtime = { path = "../../bin/rialto/runtime" } diff --git a/relays/lib-substrate-relay/src/error.rs b/relays/lib-substrate-relay/src/error.rs new file mode 100644 index 00000000000..b41870a181d --- /dev/null +++ b/relays/lib-substrate-relay/src/error.rs @@ -0,0 +1,63 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Relay errors. + +use relay_substrate_client as client; +use sp_finality_grandpa::AuthorityList; +use sp_runtime::traits::MaybeDisplay; +use std::fmt::Debug; +use thiserror::Error; + +/// Relay errors. +#[derive(Error, Debug)] +pub enum Error { + /// Failed to submit signed extrinsic from to the target chain. + #[error("Failed to submit {0} transaction: {1:?}")] + SubmitTransaction(&'static str, client::Error), + /// Failed subscribe to justification stream of the source chain. + #[error("Failed to subscribe to {0} justifications: {1:?}")] + Subscribe(&'static str, client::Error), + /// Failed subscribe to read justification from the source chain (client error). + #[error("Failed to read {0} justification from the stream: {1}")] + ReadJustification(&'static str, client::Error), + /// Failed subscribe to read justification from the source chain (stream ended). + #[error("Failed to read {0} justification from the stream: stream has ended unexpectedly")] + ReadJustificationStreamEnded(&'static str), + /// Failed subscribe to decode justification from the source chain. + #[error("Failed to decode {0} justification: {1:?}")] + DecodeJustification(&'static str, codec::Error), + /// GRANDPA authorities read from the source chain are invalid. + #[error("Read invalid {0} authorities set: {1:?}")] + ReadInvalidAuthorities(&'static str, AuthorityList), + /// Failed to guess initial GRANDPA authorities at the given header of the source chain. + #[error("Failed to guess initial {0} GRANDPA authorities set id: checked all possible ids in range [0; {1}]")] + GuessInitialAuthorities(&'static str, HeaderNumber), + /// Failed to retrieve GRANDPA authorities at the given header from the source chain. + #[error("Failed to retrive {0} GRANDPA authorities set at header {1}: {2:?}")] + RetrieveAuthorities(&'static str, Hash, client::Error), + /// Failed to decode GRANDPA authorities at the given header of the source chain. + #[error("Failed to decode {0} GRANDPA authorities set at header {1}: {2:?}")] + DecodeAuthorities(&'static str, Hash, codec::Error), + /// Failed to retrieve header by the hash from the source chain. + #[error("Failed to retrieve {0} header with hash {1}: {2:?}")] + RetrieveHeader(&'static str, Hash, client::Error), + /// Failed to submit signed extrinsic from to the target chain. + #[error( + "Failed to retrieve `is_initialized` flag of the with-{0} finality pallet at {1}: {2:?}" + )] + IsInitializedRetrieve(&'static str, &'static str, client::Error), +} diff --git a/relays/lib-substrate-relay/src/finality/engine.rs b/relays/lib-substrate-relay/src/finality/engine.rs new file mode 100644 index 00000000000..4c2da5a5319 --- /dev/null +++ b/relays/lib-substrate-relay/src/finality/engine.rs @@ -0,0 +1,259 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Support of different finality engines, available in Substrate. + +use crate::error::Error; +use async_trait::async_trait; +use bp_header_chain::{ + justification::{verify_justification, GrandpaJustification}, + ConsensusLogReader, FinalityProof, GrandpaConsensusLogReader, +}; +use bp_runtime::{BasicOperatingMode, OperatingMode}; +use codec::{Decode, Encode}; +use finality_grandpa::voter_set::VoterSet; +use num_traits::{One, Zero}; +use relay_substrate_client::{ + BlockNumberOf, Chain, ChainWithGrandpa, Client, Error as SubstrateError, HashOf, HeaderOf, + Subscription, SubstrateFinalityClient, SubstrateGrandpaFinalityClient, +}; +use sp_core::{storage::StorageKey, Bytes}; +use sp_finality_grandpa::{AuthorityList as GrandpaAuthoritiesSet, GRANDPA_ENGINE_ID}; +use sp_runtime::{traits::Header, ConsensusEngineId}; +use std::marker::PhantomData; + +/// Finality engine, used by the Substrate chain. +#[async_trait] +pub trait Engine: Send { + /// Unique consensus engine identifier. + const ID: ConsensusEngineId; + /// A reader that can extract the consensus log from the header digest and interpret it. + type ConsensusLogReader: ConsensusLogReader; + /// Type of Finality RPC client used by this engine. + type FinalityClient: SubstrateFinalityClient; + /// Type of finality proofs, used by consensus engine. + type FinalityProof: FinalityProof> + Decode + Encode; + /// Type of bridge pallet initialization data. + type InitializationData: std::fmt::Debug + Send + Sync + 'static; + /// Type of bridge pallet operating mode. + type OperatingMode: OperatingMode + 'static; + + /// Returns storage at the bridged (target) chain that corresponds to some value that is + /// missing from the storage until bridge pallet is initialized. + /// + /// Note that we don't care about type of the value - just if it present or not. + fn is_initialized_key() -> StorageKey; + + /// Returns `Ok(true)` if finality pallet at the bridged chain has already been initialized. + async fn is_initialized( + target_client: &Client, + ) -> Result { + Ok(target_client + .raw_storage_value(Self::is_initialized_key(), None) + .await? + .is_some()) + } + + /// Returns storage key at the bridged (target) chain that corresponds to the variable + /// that holds the operating mode of the pallet. + fn pallet_operating_mode_key() -> StorageKey; + + /// Returns `Ok(true)` if finality pallet at the bridged chain is halted. + async fn is_halted( + target_client: &Client, + ) -> Result { + Ok(target_client + .storage_value::(Self::pallet_operating_mode_key(), None) + .await? + .map(|operating_mode| operating_mode.is_halted()) + .unwrap_or(false)) + } + + /// A method to subscribe to encoded finality proofs, given source client. + async fn finality_proofs(client: &Client) -> Result, SubstrateError> { + client.subscribe_finality_justifications::().await + } + + /// Prepare initialization data for the finality bridge pallet. + async fn prepare_initialization_data( + client: Client, + ) -> Result, BlockNumberOf>>; +} + +/// GRANDPA finality engine. +pub struct Grandpa(PhantomData); + +impl Grandpa { + /// Read header by hash from the source client. + async fn source_header( + source_client: &Client, + header_hash: C::Hash, + ) -> Result, BlockNumberOf>> { + source_client + .header_by_hash(header_hash) + .await + .map_err(|err| Error::RetrieveHeader(C::NAME, header_hash, err)) + } + + /// Read GRANDPA authorities set at given header. + async fn source_authorities_set( + source_client: &Client, + header_hash: C::Hash, + ) -> Result, BlockNumberOf>> { + let raw_authorities_set = source_client + .grandpa_authorities_set(header_hash) + .await + .map_err(|err| Error::RetrieveAuthorities(C::NAME, header_hash, err))?; + GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..]) + .map_err(|err| Error::DecodeAuthorities(C::NAME, header_hash, err)) + } +} + +#[async_trait] +impl Engine for Grandpa { + const ID: ConsensusEngineId = GRANDPA_ENGINE_ID; + type ConsensusLogReader = GrandpaConsensusLogReader<::Number>; + type FinalityClient = SubstrateGrandpaFinalityClient; + type FinalityProof = GrandpaJustification>; + type InitializationData = bp_header_chain::InitializationData; + type OperatingMode = BasicOperatingMode; + + fn is_initialized_key() -> StorageKey { + bp_header_chain::storage_keys::best_finalized_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME) + } + + fn pallet_operating_mode_key() -> StorageKey { + bp_header_chain::storage_keys::pallet_operating_mode_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME) + } + + /// Prepare initialization data for the GRANDPA verifier pallet. + async fn prepare_initialization_data( + source_client: Client, + ) -> Result, BlockNumberOf>> { + // In ideal world we just need to get best finalized header and then to read GRANDPA + // authorities set (`pallet_grandpa::CurrentSetId` + `GrandpaApi::grandpa_authorities()`) at + // this header. + // + // But now there are problems with this approach - `CurrentSetId` may return invalid value. + // So here we're waiting for the next justification, read the authorities set and then try + // to figure out the set id with bruteforce. + let justifications = Self::finality_proofs(&source_client) + .await + .map_err(|err| Error::Subscribe(C::NAME, err))?; + // Read next justification - the header that it finalizes will be used as initial header. + let justification = justifications + .next() + .await + .map_err(|e| Error::ReadJustification(C::NAME, e)) + .and_then(|justification| { + justification.ok_or(Error::ReadJustificationStreamEnded(C::NAME)) + })?; + + // Read initial header. + let justification: GrandpaJustification = + Decode::decode(&mut &justification.0[..]) + .map_err(|err| Error::DecodeJustification(C::NAME, err))?; + + let (initial_header_hash, initial_header_number) = + (justification.commit.target_hash, justification.commit.target_number); + + let initial_header = Self::source_header(&source_client, initial_header_hash).await?; + log::trace!(target: "bridge", "Selected {} initial header: {}/{}", + C::NAME, + initial_header_number, + initial_header_hash, + ); + + // Read GRANDPA authorities set at initial header. + let initial_authorities_set = + Self::source_authorities_set(&source_client, initial_header_hash).await?; + log::trace!(target: "bridge", "Selected {} initial authorities set: {:?}", + C::NAME, + initial_authorities_set, + ); + + // If initial header changes the GRANDPA authorities set, then we need previous authorities + // to verify justification. + let mut authorities_for_verification = initial_authorities_set.clone(); + let scheduled_change = + GrandpaConsensusLogReader::>::find_authorities_change( + initial_header.digest(), + ); + assert!( + scheduled_change.as_ref().map(|c| c.delay.is_zero()).unwrap_or(true), + "GRANDPA authorities change at {} scheduled to happen in {:?} blocks. We expect\ + regular change to have zero delay", + initial_header_hash, + scheduled_change.as_ref().map(|c| c.delay), + ); + let schedules_change = scheduled_change.is_some(); + if schedules_change { + authorities_for_verification = + Self::source_authorities_set(&source_client, *initial_header.parent_hash()).await?; + log::trace!( + target: "bridge", + "Selected {} header is scheduling GRANDPA authorities set changes. Using previous set: {:?}", + C::NAME, + authorities_for_verification, + ); + } + + // Now let's try to guess authorities set id by verifying justification. + let mut initial_authorities_set_id = 0; + let mut min_possible_block_number = C::BlockNumber::zero(); + let authorities_for_verification = VoterSet::new(authorities_for_verification.clone()) + .ok_or(Error::ReadInvalidAuthorities(C::NAME, authorities_for_verification))?; + loop { + log::trace!( + target: "bridge", "Trying {} GRANDPA authorities set id: {}", + C::NAME, + initial_authorities_set_id, + ); + + let is_valid_set_id = verify_justification::( + (initial_header_hash, initial_header_number), + initial_authorities_set_id, + &authorities_for_verification, + &justification, + ) + .is_ok(); + + if is_valid_set_id { + break + } + + initial_authorities_set_id += 1; + min_possible_block_number += One::one(); + if min_possible_block_number > initial_header_number { + // there can't be more authorities set changes than headers => if we have reached + // `initial_block_number` and still have not found correct value of + // `initial_authorities_set_id`, then something else is broken => fail + return Err(Error::GuessInitialAuthorities(C::NAME, initial_header_number)) + } + } + + Ok(bp_header_chain::InitializationData { + header: Box::new(initial_header), + authority_list: initial_authorities_set, + set_id: if schedules_change { + initial_authorities_set_id + 1 + } else { + initial_authorities_set_id + }, + operating_mode: BasicOperatingMode::Normal, + }) + } +} diff --git a/relays/lib-substrate-relay/src/finality/guards.rs b/relays/lib-substrate-relay/src/finality/guards.rs new file mode 100644 index 00000000000..1451b549cc0 --- /dev/null +++ b/relays/lib-substrate-relay/src/finality/guards.rs @@ -0,0 +1,48 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tools for starting guards of finality relays. + +use crate::TransactionParams; + +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, ChainWithBalances, ChainWithTransactions, +}; +use sp_core::Pair; + +/// Start finality relay guards. +pub async fn start( + target_client: &relay_substrate_client::Client, + transaction_params: &TransactionParams>, + enable_version_guard: bool, + maximal_balance_decrease_per_day: C::Balance, +) -> relay_substrate_client::Result<()> +where + AccountIdOf: From< as Pair>::Public>, +{ + if enable_version_guard { + relay_substrate_client::guard::abort_on_spec_version_change( + target_client.clone(), + target_client.simple_runtime_version().await?.0, + ); + } + relay_substrate_client::guard::abort_when_account_balance_decreased( + target_client.clone(), + transaction_params.signer.public().into(), + maximal_balance_decrease_per_day, + ); + Ok(()) +} diff --git a/relays/lib-substrate-relay/src/finality/initialize.rs b/relays/lib-substrate-relay/src/finality/initialize.rs new file mode 100644 index 00000000000..6ff185800cc --- /dev/null +++ b/relays/lib-substrate-relay/src/finality/initialize.rs @@ -0,0 +1,137 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Initialize Substrate -> Substrate finality bridge. +//! +//! Initialization is a transaction that calls `initialize()` function of the +//! finality pallet (GRANDPA/BEEFY/...). This transaction brings initial header +//! and authorities set from source to target chain. The finality sync starts +//! with this header. + +use crate::{error::Error, finality::engine::Engine}; + +use relay_substrate_client::{ + Chain, ChainWithTransactions, Client, Error as SubstrateError, SignParam, UnsignedTransaction, +}; +use sp_runtime::traits::Header as HeaderT; + +/// Submit headers-bridge initialization transaction. +pub async fn initialize< + E: Engine, + SourceChain: Chain, + TargetChain: ChainWithTransactions, + F, +>( + source_client: Client, + target_client: Client, + target_transactions_signer: TargetChain::AccountId, + target_signing_data: SignParam, + prepare_initialize_transaction: F, +) where + F: FnOnce( + TargetChain::Index, + E::InitializationData, + ) -> Result, SubstrateError> + + Send + + 'static, +{ + let result = do_initialize::( + source_client, + target_client, + target_transactions_signer, + target_signing_data, + prepare_initialize_transaction, + ) + .await; + + match result { + Ok(Some(tx_hash)) => log::info!( + target: "bridge", + "Successfully submitted {}-headers bridge initialization transaction to {}: {:?}", + SourceChain::NAME, + TargetChain::NAME, + tx_hash, + ), + Ok(None) => (), + Err(err) => log::error!( + target: "bridge", + "Failed to submit {}-headers bridge initialization transaction to {}: {:?}", + SourceChain::NAME, + TargetChain::NAME, + err, + ), + } +} + +/// Craft and submit initialization transaction, returning any error that may occur. +async fn do_initialize< + E: Engine, + SourceChain: Chain, + TargetChain: ChainWithTransactions, + F, +>( + source_client: Client, + target_client: Client, + target_transactions_signer: TargetChain::AccountId, + target_signing_data: SignParam, + prepare_initialize_transaction: F, +) -> Result< + Option, + Error::Number>, +> +where + F: FnOnce( + TargetChain::Index, + E::InitializationData, + ) -> Result, SubstrateError> + + Send + + 'static, +{ + let is_initialized = E::is_initialized(&target_client) + .await + .map_err(|e| Error::IsInitializedRetrieve(SourceChain::NAME, TargetChain::NAME, e))?; + if is_initialized { + log::info!( + target: "bridge", + "{}-headers bridge at {} is already initialized. Skipping", + SourceChain::NAME, + TargetChain::NAME, + ); + return Ok(None) + } + + let initialization_data = E::prepare_initialization_data(source_client).await?; + log::info!( + target: "bridge", + "Prepared initialization data for {}-headers bridge at {}: {:?}", + SourceChain::NAME, + TargetChain::NAME, + initialization_data, + ); + + let initialization_tx_hash = target_client + .submit_signed_extrinsic( + target_transactions_signer, + target_signing_data, + move |_, transaction_nonce| { + prepare_initialize_transaction(transaction_nonce, initialization_data) + }, + ) + .await + .map_err(|err| Error::SubmitTransaction(TargetChain::NAME, err))?; + + Ok(Some(initialization_tx_hash)) +} diff --git a/relays/lib-substrate-relay/src/finality/mod.rs b/relays/lib-substrate-relay/src/finality/mod.rs new file mode 100644 index 00000000000..5d7b52cd1de --- /dev/null +++ b/relays/lib-substrate-relay/src/finality/mod.rs @@ -0,0 +1,204 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types and functions intended to ease adding of new Substrate -> Substrate +//! finality proofs synchronization pipelines. + +use crate::{ + finality::{ + engine::Engine, + source::{SubstrateFinalityProof, SubstrateFinalitySource}, + target::SubstrateFinalityTarget, + }, + TransactionParams, +}; + +use async_trait::async_trait; +use bp_header_chain::justification::GrandpaJustification; +use finality_relay::FinalitySyncPipeline; +use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig}; +use relay_substrate_client::{ + transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, + ChainWithTransactions, Client, HashOf, HeaderOf, SyncHeader, +}; +use relay_utils::metrics::MetricsParams; +use sp_core::Pair; +use std::{fmt::Debug, marker::PhantomData}; + +pub mod engine; +pub mod guards; +pub mod initialize; +pub mod source; +pub mod target; + +/// Default limit of recent finality proofs. +/// +/// Finality delay of 4096 blocks is unlikely to happen in practice in +/// Substrate+GRANDPA based chains (good to know). +pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096; + +/// Substrate -> Substrate finality proofs synchronization pipeline. +#[async_trait] +pub trait SubstrateFinalitySyncPipeline: 'static + Clone + Debug + Send + Sync { + /// Headers of this chain are submitted to the `TargetChain`. + type SourceChain: Chain; + /// Headers of the `SourceChain` are submitted to this chain. + type TargetChain: ChainWithTransactions; + + /// Finality engine. + type FinalityEngine: Engine; + /// How submit finality proof call is built? + type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder; + + /// Add relay guards if required. + async fn start_relay_guards( + _target_client: &Client, + _transaction_params: &TransactionParams>, + _enable_version_guard: bool, + ) -> relay_substrate_client::Result<()> { + Ok(()) + } +} + +/// Adapter that allows all `SubstrateFinalitySyncPipeline` to act as `FinalitySyncPipeline`. +#[derive(Clone, Debug)] +pub struct FinalitySyncPipelineAdapter { + _phantom: PhantomData

, +} + +impl FinalitySyncPipeline for FinalitySyncPipelineAdapter

{ + const SOURCE_NAME: &'static str = P::SourceChain::NAME; + const TARGET_NAME: &'static str = P::TargetChain::NAME; + + type Hash = HashOf; + type Number = BlockNumberOf; + type ConsensusLogReader = >::ConsensusLogReader; + type Header = SyncHeader>; + type FinalityProof = SubstrateFinalityProof

; +} + +/// Different ways of building `submit_finality_proof` calls. +pub trait SubmitFinalityProofCallBuilder { + /// Given source chain header and its finality proofs, build call of `submit_finality_proof` + /// function of bridge GRANDPA module at the target chain. + fn build_submit_finality_proof_call( + header: SyncHeader>, + proof: SubstrateFinalityProof

, + ) -> CallOf; +} + +/// Building `submit_finality_proof` call when you have direct access to the target +/// chain runtime. +pub struct DirectSubmitGrandpaFinalityProofCallBuilder { + _phantom: PhantomData<(P, R, I)>, +} + +impl SubmitFinalityProofCallBuilder

+ for DirectSubmitGrandpaFinalityProofCallBuilder +where + P: SubstrateFinalitySyncPipeline, + R: BridgeGrandpaConfig, + I: 'static, + R::BridgedChain: bp_runtime::Chain

>, + CallOf: From>, + P::FinalityEngine: + Engine>>, +{ + fn build_submit_finality_proof_call( + header: SyncHeader>, + proof: GrandpaJustification>, + ) -> CallOf { + BridgeGrandpaCall::::submit_finality_proof { + finality_target: Box::new(header.into_inner()), + justification: proof, + } + .into() + } +} + +/// Macro that generates `SubmitFinalityProofCallBuilder` implementation for the case when +/// you only have an access to the mocked version of target chain runtime. In this case you +/// should provide "name" of the call variant for the bridge GRANDPA calls and the "name" of +/// the variant for the `submit_finality_proof` call within that first option. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_mocked_submit_finality_proof_call_builder { + ($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => { + pub struct $mocked_builder; + + impl $crate::finality::SubmitFinalityProofCallBuilder<$pipeline> + for $mocked_builder + { + fn build_submit_finality_proof_call( + header: relay_substrate_client::SyncHeader< + relay_substrate_client::HeaderOf< + <$pipeline as $crate::finality::SubstrateFinalitySyncPipeline>::SourceChain + > + >, + proof: bp_header_chain::justification::GrandpaJustification< + relay_substrate_client::HeaderOf< + <$pipeline as $crate::finality::SubstrateFinalitySyncPipeline>::SourceChain + > + >, + ) -> relay_substrate_client::CallOf< + <$pipeline as $crate::finality::SubstrateFinalitySyncPipeline>::TargetChain + > { + $bridge_grandpa($submit_finality_proof(Box::new(header.into_inner()), proof)) + } + } + }; +} + +/// Run Substrate-to-Substrate finality sync loop. +pub async fn run( + source_client: Client, + target_client: Client, + only_mandatory_headers: bool, + transaction_params: TransactionParams>, + metrics_params: MetricsParams, +) -> anyhow::Result<()> +where + AccountIdOf: From< as Pair>::Public>, +{ + log::info!( + target: "bridge", + "Starting {} -> {} finality proof relay", + P::SourceChain::NAME, + P::TargetChain::NAME, + ); + + finality_relay::run( + SubstrateFinalitySource::

::new(source_client, None), + SubstrateFinalityTarget::

::new(target_client, transaction_params.clone()), + finality_relay::FinalitySyncParams { + tick: std::cmp::max( + P::SourceChain::AVERAGE_BLOCK_INTERVAL, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + ), + recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT, + stall_timeout: transaction_stall_timeout( + transaction_params.mortality, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + relay_utils::STALL_TIMEOUT, + ), + only_mandatory_headers, + }, + metrics_params, + futures::future::pending(), + ) + .await + .map_err(|e| anyhow::format_err!("{}", e)) +} diff --git a/relays/lib-substrate-relay/src/finality/source.rs b/relays/lib-substrate-relay/src/finality/source.rs new file mode 100644 index 00000000000..e75862a8227 --- /dev/null +++ b/relays/lib-substrate-relay/src/finality/source.rs @@ -0,0 +1,175 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Default generic implementation of finality source for basic Substrate client. + +use crate::finality::{engine::Engine, FinalitySyncPipelineAdapter, SubstrateFinalitySyncPipeline}; + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use codec::Decode; +use finality_relay::SourceClient; +use futures::stream::{unfold, Stream, StreamExt}; +use relay_substrate_client::{ + BlockNumberOf, BlockWithJustification, Chain, Client, Error, HeaderOf, +}; +use relay_utils::relay_loop::Client as RelayClient; +use std::pin::Pin; + +/// Shared updatable reference to the maximal header number that we want to sync from the source. +pub type RequiredHeaderNumberRef = Arc::BlockNumber>>; + +/// Substrate finality proofs stream. +pub type SubstrateFinalityProofsStream

= + Pin> + Send>>; + +/// Substrate finality proof. Specific to the used `FinalityEngine`. +pub type SubstrateFinalityProof

= + <

::FinalityEngine as Engine< +

::SourceChain, + >>::FinalityProof; + +/// Substrate node as finality source. +pub struct SubstrateFinalitySource { + client: Client, + maximal_header_number: Option>, +} + +impl SubstrateFinalitySource

{ + /// Create new headers source using given client. + pub fn new( + client: Client, + maximal_header_number: Option>, + ) -> Self { + SubstrateFinalitySource { client, maximal_header_number } + } + + /// Returns reference to the underlying RPC client. + pub fn client(&self) -> &Client { + &self.client + } + + /// Returns best finalized block number. + pub async fn on_chain_best_finalized_block_number( + &self, + ) -> Result, Error> { + // we **CAN** continue to relay finality proofs if source node is out of sync, because + // target node may be missing proofs that are already available at the source + self.client.best_finalized_header_number().await + } +} + +impl Clone for SubstrateFinalitySource

{ + fn clone(&self) -> Self { + SubstrateFinalitySource { + client: self.client.clone(), + maximal_header_number: self.maximal_header_number.clone(), + } + } +} + +#[async_trait] +impl RelayClient for SubstrateFinalitySource

{ + type Error = Error; + + async fn reconnect(&mut self) -> Result<(), Error> { + self.client.reconnect().await + } +} + +#[async_trait] +impl SourceClient> + for SubstrateFinalitySource

+{ + type FinalityProofsStream = SubstrateFinalityProofsStream

; + + async fn best_finalized_block_number(&self) -> Result, Error> { + let mut finalized_header_number = self.on_chain_best_finalized_block_number().await?; + // never return block number larger than requested. This way we'll never sync headers + // past `maximal_header_number` + if let Some(ref maximal_header_number) = self.maximal_header_number { + let maximal_header_number = *maximal_header_number.lock().await; + if finalized_header_number > maximal_header_number { + finalized_header_number = maximal_header_number; + } + } + Ok(finalized_header_number) + } + + async fn header_and_finality_proof( + &self, + number: BlockNumberOf, + ) -> Result< + ( + relay_substrate_client::SyncHeader>, + Option>, + ), + Error, + > { + let header_hash = self.client.block_hash_by_number(number).await?; + let signed_block = self.client.get_block(Some(header_hash)).await?; + + let justification = signed_block + .justification(P::FinalityEngine::ID) + .map(|raw_justification| { + SubstrateFinalityProof::

::decode(&mut raw_justification.as_slice()) + }) + .transpose() + .map_err(Error::ResponseParseFailed)?; + + Ok((signed_block.header().into(), justification)) + } + + async fn finality_proofs(&self) -> Result { + Ok(unfold( + P::FinalityEngine::finality_proofs(&self.client).await?, + move |subscription| async move { + loop { + let log_error = |err| { + log::error!( + target: "bridge", + "Failed to read justification target from the {} justifications stream: {:?}", + P::SourceChain::NAME, + err, + ); + }; + + let next_justification = subscription + .next() + .await + .map_err(|err| log_error(err.to_string())) + .ok()??; + + let decoded_justification = + >::FinalityProof::decode( + &mut &next_justification[..], + ); + + let justification = match decoded_justification { + Ok(j) => j, + Err(err) => { + log_error(format!("decode failed with error {err:?}")); + continue + }, + }; + + return Some((justification, subscription)) + } + }, + ) + .boxed()) + } +} diff --git a/relays/lib-substrate-relay/src/finality/target.rs b/relays/lib-substrate-relay/src/finality/target.rs new file mode 100644 index 00000000000..09d9ad15f54 --- /dev/null +++ b/relays/lib-substrate-relay/src/finality/target.rs @@ -0,0 +1,135 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate client as Substrate finality proof target. + +use crate::{ + finality::{ + engine::Engine, source::SubstrateFinalityProof, FinalitySyncPipelineAdapter, + SubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline, + }, + TransactionParams, +}; + +use async_trait::async_trait; +use finality_relay::TargetClient; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, Chain, Client, Error, HeaderIdOf, HeaderOf, SignParam, + SyncHeader, TransactionEra, TransactionTracker, UnsignedTransaction, +}; +use relay_utils::relay_loop::Client as RelayClient; +use sp_core::Pair; + +/// Substrate client as Substrate finality target. +pub struct SubstrateFinalityTarget { + client: Client, + transaction_params: TransactionParams>, +} + +impl SubstrateFinalityTarget

{ + /// Create new Substrate headers target. + pub fn new( + client: Client, + transaction_params: TransactionParams>, + ) -> Self { + SubstrateFinalityTarget { client, transaction_params } + } + + /// Ensure that the bridge pallet at target chain is active. + pub async fn ensure_pallet_active(&self) -> Result<(), Error> { + let is_halted = P::FinalityEngine::is_halted(&self.client).await?; + if is_halted { + return Err(Error::BridgePalletIsHalted) + } + + let is_initialized = P::FinalityEngine::is_initialized(&self.client).await?; + if !is_initialized { + return Err(Error::BridgePalletIsNotInitialized) + } + + Ok(()) + } +} + +impl Clone for SubstrateFinalityTarget

{ + fn clone(&self) -> Self { + SubstrateFinalityTarget { + client: self.client.clone(), + transaction_params: self.transaction_params.clone(), + } + } +} + +#[async_trait] +impl RelayClient for SubstrateFinalityTarget

{ + type Error = Error; + + async fn reconnect(&mut self) -> Result<(), Error> { + self.client.reconnect().await + } +} + +#[async_trait] +impl TargetClient> + for SubstrateFinalityTarget

+where + AccountIdOf: From< as Pair>::Public>, +{ + type TransactionTracker = TransactionTracker>; + + async fn best_finalized_source_block_id(&self) -> Result, Error> { + // we can't continue to relay finality if target node is out of sync, because + // it may have already received (some of) headers that we're going to relay + self.client.ensure_synced().await?; + // we can't relay finality if bridge pallet at target chain is halted + self.ensure_pallet_active().await?; + + Ok(crate::messages_source::read_client_state::( + &self.client, + None, + P::SourceChain::BEST_FINALIZED_HEADER_ID_METHOD, + ) + .await? + .best_finalized_peer_at_best_self) + } + + async fn submit_finality_proof( + &self, + header: SyncHeader>, + proof: SubstrateFinalityProof

, + ) -> Result { + let genesis_hash = *self.client.genesis_hash(); + let transaction_params = self.transaction_params.clone(); + let call = + P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(header, proof); + let (spec_version, transaction_version) = self.client.simple_runtime_version().await?; + self.client + .submit_and_watch_signed_extrinsic( + self.transaction_params.signer.public().into(), + SignParam:: { + spec_version, + transaction_version, + genesis_hash, + signer: transaction_params.signer.clone(), + }, + move |best_block_id, transaction_nonce| { + Ok(UnsignedTransaction::new(call.into(), transaction_nonce) + .era(TransactionEra::new(best_block_id, transaction_params.mortality))) + }, + ) + .await + } +} diff --git a/relays/lib-substrate-relay/src/lib.rs b/relays/lib-substrate-relay/src/lib.rs new file mode 100644 index 00000000000..62ae756e003 --- /dev/null +++ b/relays/lib-substrate-relay/src/lib.rs @@ -0,0 +1,98 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! The library of substrate relay. contains some public codes to provide to substrate relay. + +#![warn(missing_docs)] + +pub mod error; +pub mod finality; +pub mod messages_lane; +pub mod messages_metrics; +pub mod messages_source; +pub mod messages_target; +pub mod on_demand; +pub mod parachains; + +/// Transaction creation parameters. +#[derive(Clone, Debug)] +pub struct TransactionParams { + /// Transactions author. + pub signer: TS, + /// Transactions mortality. + pub mortality: Option, +} + +/// Tagged relay account, which balance may be exposed as metrics by the relay. +#[derive(Clone, Debug)] +pub enum TaggedAccount { + /// Account, used to sign headers relay transactions from given bridged chain. + Headers { + /// Account id. + id: AccountId, + /// Name of the bridged chain, which headers are relayed. + bridged_chain: String, + }, + /// Account, used to sign parachains relay transactions from given bridged relay chain. + Parachains { + /// Account id. + id: AccountId, + /// Name of the bridged relay chain with parachain heads. + bridged_chain: String, + }, + /// Account, used to sign message relay transactions from given bridged chain. + Messages { + /// Account id. + id: AccountId, + /// Name of the bridged chain, which sends us messages or delivery confirmations. + bridged_chain: String, + }, + /// Account, used to sign messages with-bridged-chain pallet parameters update transactions. + MessagesPalletOwner { + /// Account id. + id: AccountId, + /// Name of the chain, bridged using messages pallet at our chain. + bridged_chain: String, + }, +} + +impl TaggedAccount { + /// Returns reference to the account id. + pub fn id(&self) -> &AccountId { + match *self { + TaggedAccount::Headers { ref id, .. } => id, + TaggedAccount::Parachains { ref id, .. } => id, + TaggedAccount::Messages { ref id, .. } => id, + TaggedAccount::MessagesPalletOwner { ref id, .. } => id, + } + } + + /// Returns stringified account tag. + pub fn tag(&self) -> String { + match *self { + TaggedAccount::Headers { ref bridged_chain, .. } => format!("{bridged_chain}Headers"), + TaggedAccount::Parachains { ref bridged_chain, .. } => { + format!("{bridged_chain}Parachains") + }, + TaggedAccount::Messages { ref bridged_chain, .. } => { + format!("{bridged_chain}Messages") + }, + TaggedAccount::MessagesPalletOwner { ref bridged_chain, .. } => { + format!("{bridged_chain}MessagesPalletOwner") + }, + } + } +} diff --git a/relays/lib-substrate-relay/src/messages_lane.rs b/relays/lib-substrate-relay/src/messages_lane.rs new file mode 100644 index 00000000000..da138a3d125 --- /dev/null +++ b/relays/lib-substrate-relay/src/messages_lane.rs @@ -0,0 +1,449 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tools for supporting message lanes between two Substrate-based chains. + +use crate::{ + messages_source::{SubstrateMessagesProof, SubstrateMessagesSource}, + messages_target::{SubstrateMessagesDeliveryProof, SubstrateMessagesTarget}, + on_demand::OnDemandRelay, + TransactionParams, +}; + +use async_std::sync::Arc; +use bp_messages::{LaneId, MessageNonce}; +use bp_runtime::{AccountIdOf, Chain as _, WeightExtraOps}; +use bridge_runtime_common::messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, +}; +use codec::Encode; +use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; +use messages_relay::message_lane::MessageLane; +use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig}; +use relay_substrate_client::{ + transaction_stall_timeout, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain, + ChainWithMessages, ChainWithTransactions, Client, HashOf, +}; +use relay_utils::{ + metrics::{GlobalMetrics, MetricsParams, StandaloneMetric}, + STALL_TIMEOUT, +}; +use sp_core::Pair; +use std::{convert::TryFrom, fmt::Debug, marker::PhantomData}; + +/// Substrate -> Substrate messages synchronization pipeline. +pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync { + /// Messages of this chain are relayed to the `TargetChain`. + type SourceChain: ChainWithMessages + ChainWithTransactions; + /// Messages from the `SourceChain` are dispatched on this chain. + type TargetChain: ChainWithMessages + ChainWithTransactions; + + /// How receive messages proof call is built? + type ReceiveMessagesProofCallBuilder: ReceiveMessagesProofCallBuilder; + /// How receive messages delivery proof call is built? + type ReceiveMessagesDeliveryProofCallBuilder: ReceiveMessagesDeliveryProofCallBuilder; +} + +/// Adapter that allows all `SubstrateMessageLane` to act as `MessageLane`. +#[derive(Clone, Debug)] +pub(crate) struct MessageLaneAdapter { + _phantom: PhantomData

, +} + +impl MessageLane for MessageLaneAdapter

{ + const SOURCE_NAME: &'static str = P::SourceChain::NAME; + const TARGET_NAME: &'static str = P::TargetChain::NAME; + + type MessagesProof = SubstrateMessagesProof; + type MessagesReceivingProof = SubstrateMessagesDeliveryProof; + + type SourceChainBalance = BalanceOf; + type SourceHeaderNumber = BlockNumberOf; + type SourceHeaderHash = HashOf; + + type TargetHeaderNumber = BlockNumberOf; + type TargetHeaderHash = HashOf; +} + +/// Substrate <-> Substrate messages relay parameters. +pub struct MessagesRelayParams { + /// Messages source client. + pub source_client: Client, + /// Source transaction params. + pub source_transaction_params: TransactionParams>, + /// Messages target client. + pub target_client: Client, + /// Target transaction params. + pub target_transaction_params: TransactionParams>, + /// Optional on-demand source to target headers relay. + pub source_to_target_headers_relay: + Option>>>, + /// Optional on-demand target to source headers relay. + pub target_to_source_headers_relay: + Option>>>, + /// Identifier of lane that needs to be served. + pub lane_id: LaneId, + /// Metrics parameters. + pub metrics_params: MetricsParams, +} + +/// Run Substrate-to-Substrate messages sync loop. +pub async fn run(params: MessagesRelayParams

) -> anyhow::Result<()> +where + AccountIdOf: From< as Pair>::Public>, + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom>, +{ + let source_client = params.source_client; + let target_client = params.target_client; + let relayer_id_at_source: AccountIdOf = + params.source_transaction_params.signer.public().into(); + + // 2/3 is reserved for proofs and tx overhead + let max_messages_size_in_single_batch = P::TargetChain::max_extrinsic_size() / 3; + // we don't know exact weights of the Polkadot runtime. So to guess weights we'll be using + // weights from Rialto and then simply dividing it by x2. + let (max_messages_in_single_batch, max_messages_weight_in_single_batch) = + crate::messages_lane::select_delivery_transaction_limits::< + ::WeightInfo, + >( + P::TargetChain::max_extrinsic_weight(), + P::SourceChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + ); + let (max_messages_in_single_batch, max_messages_weight_in_single_batch) = + (max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2); + + log::info!( + target: "bridge", + "Starting {} -> {} messages relay.\n\t\ + {} relayer account id: {:?}\n\t\ + Max messages in single transaction: {}\n\t\ + Max messages size in single transaction: {}\n\t\ + Max messages weight in single transaction: {}\n\t\ + Tx mortality: {:?} (~{}m)/{:?} (~{}m)", + P::SourceChain::NAME, + P::TargetChain::NAME, + P::SourceChain::NAME, + relayer_id_at_source, + max_messages_in_single_batch, + max_messages_size_in_single_batch, + max_messages_weight_in_single_batch, + params.source_transaction_params.mortality, + transaction_stall_timeout( + params.source_transaction_params.mortality, + P::SourceChain::AVERAGE_BLOCK_INTERVAL, + STALL_TIMEOUT, + ).as_secs_f64() / 60.0f64, + params.target_transaction_params.mortality, + transaction_stall_timeout( + params.target_transaction_params.mortality, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + STALL_TIMEOUT, + ).as_secs_f64() / 60.0f64, + ); + + messages_relay::message_lane_loop::run( + messages_relay::message_lane_loop::Params { + lane: params.lane_id, + source_tick: P::SourceChain::AVERAGE_BLOCK_INTERVAL, + target_tick: P::TargetChain::AVERAGE_BLOCK_INTERVAL, + reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY, + delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams { + max_unrewarded_relayer_entries_at_target: + P::SourceChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + max_unconfirmed_nonces_at_target: + P::SourceChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + max_messages_in_single_batch, + max_messages_weight_in_single_batch, + max_messages_size_in_single_batch, + }, + }, + SubstrateMessagesSource::

::new( + source_client.clone(), + target_client.clone(), + params.lane_id, + params.source_transaction_params, + params.target_to_source_headers_relay, + ), + SubstrateMessagesTarget::

::new( + target_client, + source_client, + params.lane_id, + relayer_id_at_source, + params.target_transaction_params, + params.source_to_target_headers_relay, + ), + { + GlobalMetrics::new()?.register_and_spawn(¶ms.metrics_params.registry)?; + params.metrics_params + }, + futures::future::pending(), + ) + .await + .map_err(Into::into) +} + +/// Different ways of building `receive_messages_proof` calls. +pub trait ReceiveMessagesProofCallBuilder { + /// Given messages proof, build call of `receive_messages_proof` function of bridge + /// messages module at the target chain. + fn build_receive_messages_proof_call( + relayer_id_at_source: AccountIdOf, + proof: SubstrateMessagesProof, + messages_count: u32, + dispatch_weight: Weight, + trace_call: bool, + ) -> CallOf; +} + +/// Building `receive_messages_proof` call when you have direct access to the target +/// chain runtime. +pub struct DirectReceiveMessagesProofCallBuilder { + _phantom: PhantomData<(P, R, I)>, +} + +impl ReceiveMessagesProofCallBuilder

for DirectReceiveMessagesProofCallBuilder +where + P: SubstrateMessageLane, + R: BridgeMessagesConfig>, + I: 'static, + R::SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof>, + >, + CallOf: From> + GetDispatchInfo, +{ + fn build_receive_messages_proof_call( + relayer_id_at_source: AccountIdOf, + proof: SubstrateMessagesProof, + messages_count: u32, + dispatch_weight: Weight, + trace_call: bool, + ) -> CallOf { + let call: CallOf = BridgeMessagesCall::::receive_messages_proof { + relayer_id_at_bridged_chain: relayer_id_at_source, + proof: proof.1, + messages_count, + dispatch_weight, + } + .into(); + if trace_call { + // this trace isn't super-accurate, because limits are for transactions and we + // have a call here, but it provides required information + log::trace!( + target: "bridge", + "Prepared {} -> {} messages delivery call. Weight: {}/{}, size: {}/{}", + P::SourceChain::NAME, + P::TargetChain::NAME, + call.get_dispatch_info().weight, + P::TargetChain::max_extrinsic_weight(), + call.encode().len(), + P::TargetChain::max_extrinsic_size(), + ); + } + call + } +} + +/// Macro that generates `ReceiveMessagesProofCallBuilder` implementation for the case when +/// you only have an access to the mocked version of target chain runtime. In this case you +/// should provide "name" of the call variant for the bridge messages calls and the "name" of +/// the variant for the `receive_messages_proof` call within that first option. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_mocked_receive_message_proof_call_builder { + ($pipeline:ident, $mocked_builder:ident, $bridge_messages:path, $receive_messages_proof:path) => { + pub struct $mocked_builder; + + impl $crate::messages_lane::ReceiveMessagesProofCallBuilder<$pipeline> + for $mocked_builder + { + fn build_receive_messages_proof_call( + relayer_id_at_source: relay_substrate_client::AccountIdOf< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain + >, + proof: $crate::messages_source::SubstrateMessagesProof< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain + >, + messages_count: u32, + dispatch_weight: Weight, + _trace_call: bool, + ) -> relay_substrate_client::CallOf< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::TargetChain + > { + $bridge_messages($receive_messages_proof( + relayer_id_at_source, + proof.1, + messages_count, + dispatch_weight, + )) + } + } + }; +} + +/// Different ways of building `receive_messages_delivery_proof` calls. +pub trait ReceiveMessagesDeliveryProofCallBuilder { + /// Given messages delivery proof, build call of `receive_messages_delivery_proof` function of + /// bridge messages module at the source chain. + fn build_receive_messages_delivery_proof_call( + proof: SubstrateMessagesDeliveryProof, + trace_call: bool, + ) -> CallOf; +} + +/// Building `receive_messages_delivery_proof` call when you have direct access to the source +/// chain runtime. +pub struct DirectReceiveMessagesDeliveryProofCallBuilder { + _phantom: PhantomData<(P, R, I)>, +} + +impl ReceiveMessagesDeliveryProofCallBuilder

+ for DirectReceiveMessagesDeliveryProofCallBuilder +where + P: SubstrateMessageLane, + R: BridgeMessagesConfig, + I: 'static, + R::TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain< + R::OutboundPayload, + R::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof>, + >, + CallOf: From> + GetDispatchInfo, +{ + fn build_receive_messages_delivery_proof_call( + proof: SubstrateMessagesDeliveryProof, + trace_call: bool, + ) -> CallOf { + let call: CallOf = + BridgeMessagesCall::::receive_messages_delivery_proof { + proof: proof.1, + relayers_state: proof.0, + } + .into(); + if trace_call { + // this trace isn't super-accurate, because limits are for transactions and we + // have a call here, but it provides required information + log::trace!( + target: "bridge", + "Prepared {} -> {} delivery confirmation transaction. Weight: {}/{}, size: {}/{}", + P::TargetChain::NAME, + P::SourceChain::NAME, + call.get_dispatch_info().weight, + P::SourceChain::max_extrinsic_weight(), + call.encode().len(), + P::SourceChain::max_extrinsic_size(), + ); + } + call + } +} + +/// Macro that generates `ReceiveMessagesDeliveryProofCallBuilder` implementation for the case when +/// you only have an access to the mocked version of source chain runtime. In this case you +/// should provide "name" of the call variant for the bridge messages calls and the "name" of +/// the variant for the `receive_messages_delivery_proof` call within that first option. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_mocked_receive_message_delivery_proof_call_builder { + ($pipeline:ident, $mocked_builder:ident, $bridge_messages:path, $receive_messages_delivery_proof:path) => { + pub struct $mocked_builder; + + impl $crate::messages_lane::ReceiveMessagesDeliveryProofCallBuilder<$pipeline> + for $mocked_builder + { + fn build_receive_messages_delivery_proof_call( + proof: $crate::messages_target::SubstrateMessagesDeliveryProof< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::TargetChain + >, + _trace_call: bool, + ) -> relay_substrate_client::CallOf< + <$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain + > { + $bridge_messages($receive_messages_delivery_proof(proof.1, proof.0)) + } + } + }; +} + +/// Returns maximal number of messages and their maximal cumulative dispatch weight, based +/// on given chain parameters. +pub fn select_delivery_transaction_limits( + max_extrinsic_weight: Weight, + max_unconfirmed_messages_at_inbound_lane: MessageNonce, +) -> (MessageNonce, Weight) { + // We may try to guess accurate value, based on maximal number of messages and per-message + // weight overhead, but the relay loop isn't using this info in a super-accurate way anyway. + // So just a rough guess: let's say 1/3 of max tx weight is for tx itself and the rest is + // for messages dispatch. + + // Another thing to keep in mind is that our runtimes (when this code was written) accept + // messages with dispatch weight <= max_extrinsic_weight/2. So we can't reserve less than + // that for dispatch. + + let weight_for_delivery_tx = max_extrinsic_weight / 3; + let weight_for_messages_dispatch = max_extrinsic_weight - weight_for_delivery_tx; + + let delivery_tx_base_weight = W::receive_messages_proof_overhead() + + W::receive_messages_proof_outbound_lane_state_overhead(); + let delivery_tx_weight_rest = weight_for_delivery_tx - delivery_tx_base_weight; + + let max_number_of_messages = std::cmp::min( + delivery_tx_weight_rest + .min_components_checked_div(W::receive_messages_proof_messages_overhead(1)) + .unwrap_or(u64::MAX), + max_unconfirmed_messages_at_inbound_lane, + ); + + assert!( + max_number_of_messages > 0, + "Relay should fit at least one message in every delivery transaction", + ); + assert!( + weight_for_messages_dispatch.ref_time() >= max_extrinsic_weight.ref_time() / 2, + "Relay shall be able to deliver messages with dispatch weight = max_extrinsic_weight / 2", + ); + + (max_number_of_messages, weight_for_messages_dispatch) +} + +#[cfg(test)] +mod tests { + use super::*; + use bp_runtime::Chain; + + type RialtoToMillauMessagesWeights = + pallet_bridge_messages::weights::BridgeWeight; + + #[test] + fn select_delivery_transaction_limits_works() { + let (max_count, max_weight) = + select_delivery_transaction_limits::( + bp_millau::Millau::max_extrinsic_weight(), + bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + ); + assert_eq!( + (max_count, max_weight), + // We don't actually care about these values, so feel free to update them whenever test + // fails. The only thing to do before that is to ensure that new values looks sane: + // i.e. weight reserved for messages dispatch allows dispatch of non-trivial messages. + // + // Any significant change in this values should attract additional attention. + // + // TODO: https://github.com/paritytech/parity-bridges-common/issues/1543 - remove `set_proof_size` + (1024, Weight::from_ref_time(216_609_134_667).set_proof_size(217)), + ); + } +} diff --git a/relays/lib-substrate-relay/src/messages_metrics.rs b/relays/lib-substrate-relay/src/messages_metrics.rs new file mode 100644 index 00000000000..37a6d67baae --- /dev/null +++ b/relays/lib-substrate-relay/src/messages_metrics.rs @@ -0,0 +1,129 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Tools for supporting message lanes between two Substrate-based chains. + +use crate::TaggedAccount; + +use codec::Decode; +use frame_system::AccountInfo; +use pallet_balances::AccountData; +use relay_substrate_client::{ + metrics::{FloatStorageValue, FloatStorageValueMetric}, + AccountIdOf, BalanceOf, Chain, ChainWithBalances, Client, Error as SubstrateError, IndexOf, +}; +use relay_utils::metrics::{MetricsParams, StandaloneMetric}; +use sp_core::storage::StorageData; +use sp_runtime::{FixedPointNumber, FixedU128}; +use std::{convert::TryFrom, fmt::Debug, marker::PhantomData}; + +/// Add relay accounts balance metrics. +pub async fn add_relay_balances_metrics( + client: Client, + metrics: &mut MetricsParams, + relay_accounts: &Vec>>, +) -> anyhow::Result<()> +where + BalanceOf: Into + std::fmt::Debug, +{ + if relay_accounts.is_empty() { + return Ok(()) + } + + // if `tokenDecimals` is missing from system properties, we'll be using + let token_decimals = client + .token_decimals() + .await? + .map(|token_decimals| { + log::info!(target: "bridge", "Read `tokenDecimals` for {}: {}", C::NAME, token_decimals); + token_decimals + }) + .unwrap_or_else(|| { + // turns out it is normal not to have this property - e.g. when polkadot binary is + // started using `polkadot-local` chain. Let's use minimal nominal here + log::info!(target: "bridge", "Using default (zero) `tokenDecimals` value for {}", C::NAME); + 0 + }); + let token_decimals = u32::try_from(token_decimals).map_err(|e| { + anyhow::format_err!( + "Token decimals value ({}) of {} doesn't fit into u32: {:?}", + token_decimals, + C::NAME, + e, + ) + })?; + + for account in relay_accounts { + let relay_account_balance_metric = FloatStorageValueMetric::new( + FreeAccountBalance:: { token_decimals, _phantom: Default::default() }, + client.clone(), + C::account_info_storage_key(account.id()), + format!("at_{}_relay_{}_balance", C::NAME, account.tag()), + format!("Balance of the {} relay account at the {}", account.tag(), C::NAME), + )?; + relay_account_balance_metric.register_and_spawn(&metrics.registry)?; + } + + Ok(()) +} + +/// Adapter for `FloatStorageValueMetric` to decode account free balance. +#[derive(Clone, Debug)] +struct FreeAccountBalance { + token_decimals: u32, + _phantom: PhantomData, +} + +impl FloatStorageValue for FreeAccountBalance +where + C: Chain, + BalanceOf: Into, +{ + type Value = FixedU128; + + fn decode( + &self, + maybe_raw_value: Option, + ) -> Result, SubstrateError> { + maybe_raw_value + .map(|raw_value| { + AccountInfo::, AccountData>>::decode(&mut &raw_value.0[..]) + .map_err(SubstrateError::ResponseParseFailed) + .map(|account_data| { + convert_to_token_balance(account_data.data.free.into(), self.token_decimals) + }) + }) + .transpose() + } +} + +/// Convert from raw `u128` balance (nominated in smallest chain token units) to the float regular +/// tokens value. +fn convert_to_token_balance(balance: u128, token_decimals: u32) -> FixedU128 { + FixedU128::from_inner(balance.saturating_mul(FixedU128::DIV / 10u128.pow(token_decimals))) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn token_decimals_used_properly() { + let plancks = 425_000_000_000; + let token_decimals = 10; + let dots = convert_to_token_balance(plancks, token_decimals); + assert_eq!(dots, FixedU128::saturating_from_rational(425, 10)); + } +} diff --git a/relays/lib-substrate-relay/src/messages_source.rs b/relays/lib-substrate-relay/src/messages_source.rs new file mode 100644 index 00000000000..57f84eb6bb1 --- /dev/null +++ b/relays/lib-substrate-relay/src/messages_source.rs @@ -0,0 +1,736 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate client as Substrate messages source. The chain we connect to should have +//! runtime that implements `HeaderApi` to allow bridging with +//! chain. + +use crate::{ + messages_lane::{ + MessageLaneAdapter, ReceiveMessagesDeliveryProofCallBuilder, SubstrateMessageLane, + }, + messages_target::SubstrateMessagesDeliveryProof, + on_demand::OnDemandRelay, + TransactionParams, +}; + +use async_std::sync::Arc; +use async_trait::async_trait; +use bp_messages::{ + storage_keys::{operating_mode_key, outbound_lane_data_key}, + InboundMessageDetails, LaneId, MessageNonce, MessagePayload, MessagesOperatingMode, + OutboundLaneData, OutboundMessageDetails, +}; +use bp_runtime::{BasicOperatingMode, HeaderIdProvider}; +use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; +use codec::{Decode, Encode}; +use frame_support::weights::Weight; +use messages_relay::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::{ + ClientState, MessageDetails, MessageDetailsMap, MessageProofParameters, SourceClient, + SourceClientState, + }, +}; +use num_traits::Zero; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages, Client, + Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra, + TransactionTracker, UnsignedTransaction, +}; +use relay_utils::{relay_loop::Client as RelayClient, HeaderId}; +use sp_core::{Bytes, Pair}; +use sp_runtime::{traits::Header as HeaderT, DeserializeOwned}; +use std::ops::RangeInclusive; + +/// Intermediate message proof returned by the source Substrate node. Includes everything +/// required to submit to the target node: cumulative dispatch weight of bundled messages and +/// the proof itself. +pub type SubstrateMessagesProof = (Weight, FromBridgedChainMessagesProof>); +type MessagesToRefine<'a> = Vec<(MessagePayload, &'a mut OutboundMessageDetails)>; + +/// Substrate client as Substrate messages source. +pub struct SubstrateMessagesSource { + source_client: Client, + target_client: Client, + lane_id: LaneId, + transaction_params: TransactionParams>, + target_to_source_headers_relay: Option>>>, +} + +impl SubstrateMessagesSource

{ + /// Create new Substrate headers source. + pub fn new( + source_client: Client, + target_client: Client, + lane_id: LaneId, + transaction_params: TransactionParams>, + target_to_source_headers_relay: Option< + Arc>>, + >, + ) -> Self { + SubstrateMessagesSource { + source_client, + target_client, + lane_id, + transaction_params, + target_to_source_headers_relay, + } + } + + /// Read outbound lane state from the on-chain storage at given block. + async fn outbound_lane_data( + &self, + id: SourceHeaderIdOf>, + ) -> Result, SubstrateError> { + self.source_client + .storage_value( + outbound_lane_data_key( + P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + ), + Some(id.1), + ) + .await + } + + /// Ensure that the messages pallet at source chain is active. + async fn ensure_pallet_active(&self) -> Result<(), SubstrateError> { + ensure_messages_pallet_active::(&self.source_client).await + } +} + +impl Clone for SubstrateMessagesSource

{ + fn clone(&self) -> Self { + Self { + source_client: self.source_client.clone(), + target_client: self.target_client.clone(), + lane_id: self.lane_id, + transaction_params: self.transaction_params.clone(), + target_to_source_headers_relay: self.target_to_source_headers_relay.clone(), + } + } +} + +#[async_trait] +impl RelayClient for SubstrateMessagesSource

{ + type Error = SubstrateError; + + async fn reconnect(&mut self) -> Result<(), SubstrateError> { + self.source_client.reconnect().await?; + self.target_client.reconnect().await + } +} + +#[async_trait] +impl SourceClient> for SubstrateMessagesSource

+where + AccountIdOf: From< as Pair>::Public>, +{ + type TransactionTracker = TransactionTracker>; + + async fn state(&self) -> Result>, SubstrateError> { + // we can't continue to deliver confirmations if source node is out of sync, because + // it may have already received confirmations that we're going to deliver + // + // we can't continue to deliver messages if target node is out of sync, because + // it may have already received (some of) messages that we're going to deliver + self.source_client.ensure_synced().await?; + self.target_client.ensure_synced().await?; + // we can't relay confirmations if messages pallet at source chain is halted + self.ensure_pallet_active().await?; + + read_client_state( + &self.source_client, + Some(&self.target_client), + P::TargetChain::BEST_FINALIZED_HEADER_ID_METHOD, + ) + .await + } + + async fn latest_generated_nonce( + &self, + id: SourceHeaderIdOf>, + ) -> Result<(SourceHeaderIdOf>, MessageNonce), SubstrateError> { + // lane data missing from the storage is fine until first message is sent + let latest_generated_nonce = self + .outbound_lane_data(id) + .await? + .map(|data| data.latest_generated_nonce) + .unwrap_or(0); + Ok((id, latest_generated_nonce)) + } + + async fn latest_confirmed_received_nonce( + &self, + id: SourceHeaderIdOf>, + ) -> Result<(SourceHeaderIdOf>, MessageNonce), SubstrateError> { + // lane data missing from the storage is fine until first message is sent + let latest_received_nonce = self + .outbound_lane_data(id) + .await? + .map(|data| data.latest_received_nonce) + .unwrap_or(0); + Ok((id, latest_received_nonce)) + } + + async fn generated_message_details( + &self, + id: SourceHeaderIdOf>, + nonces: RangeInclusive, + ) -> Result>, SubstrateError> { + let mut out_msgs_details = self + .source_client + .typed_state_call::<_, Vec<_>>( + P::TargetChain::TO_CHAIN_MESSAGE_DETAILS_METHOD.into(), + (self.lane_id, *nonces.start(), *nonces.end()), + Some(id.1), + ) + .await?; + validate_out_msgs_details::(&out_msgs_details, nonces)?; + + // prepare arguments of the inbound message details call (if we need it) + let mut msgs_to_refine = vec![]; + for out_msg_details in out_msgs_details.iter_mut() { + // in our current strategy all messages are supposed to be paid at the target chain + + // for pay-at-target messages we may want to ask target chain for + // refined dispatch weight + let msg_key = bp_messages::storage_keys::message_key( + P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + out_msg_details.nonce, + ); + let msg_payload: MessagePayload = + self.source_client.storage_value(msg_key, Some(id.1)).await?.ok_or_else(|| { + SubstrateError::Custom(format!( + "Message to {} {:?}/{} is missing from runtime the storage of {} at {:?}", + P::TargetChain::NAME, + self.lane_id, + out_msg_details.nonce, + P::SourceChain::NAME, + id, + )) + })?; + + msgs_to_refine.push((msg_payload, out_msg_details)); + } + + for mut msgs_to_refine_batch in + split_msgs_to_refine::(self.lane_id, msgs_to_refine)? + { + let in_msgs_details = self + .target_client + .typed_state_call::<_, Vec>( + P::SourceChain::FROM_CHAIN_MESSAGE_DETAILS_METHOD.into(), + (self.lane_id, &msgs_to_refine_batch), + None, + ) + .await?; + if in_msgs_details.len() != msgs_to_refine_batch.len() { + return Err(SubstrateError::Custom(format!( + "Call of {} at {} has returned {} entries instead of expected {}", + P::SourceChain::FROM_CHAIN_MESSAGE_DETAILS_METHOD, + P::TargetChain::NAME, + in_msgs_details.len(), + msgs_to_refine_batch.len(), + ))) + } + for ((_, out_msg_details), in_msg_details) in + msgs_to_refine_batch.iter_mut().zip(in_msgs_details) + { + log::trace!( + target: "bridge", + "Refined weight of {}->{} message {:?}/{}: at-source: {}, at-target: {}", + P::SourceChain::NAME, + P::TargetChain::NAME, + self.lane_id, + out_msg_details.nonce, + out_msg_details.dispatch_weight, + in_msg_details.dispatch_weight, + ); + out_msg_details.dispatch_weight = in_msg_details.dispatch_weight; + } + } + + let mut msgs_details_map = MessageDetailsMap::new(); + for out_msg_details in out_msgs_details { + msgs_details_map.insert( + out_msg_details.nonce, + MessageDetails { + dispatch_weight: out_msg_details.dispatch_weight, + size: out_msg_details.size as _, + reward: Zero::zero(), + }, + ); + } + + Ok(msgs_details_map) + } + + async fn prove_messages( + &self, + id: SourceHeaderIdOf>, + nonces: RangeInclusive, + proof_parameters: MessageProofParameters, + ) -> Result< + ( + SourceHeaderIdOf>, + RangeInclusive, + as MessageLane>::MessagesProof, + ), + SubstrateError, + > { + let mut storage_keys = + Vec::with_capacity(nonces.end().saturating_sub(*nonces.start()) as usize + 1); + let mut message_nonce = *nonces.start(); + while message_nonce <= *nonces.end() { + let message_key = bp_messages::storage_keys::message_key( + P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + message_nonce, + ); + storage_keys.push(message_key); + message_nonce += 1; + } + if proof_parameters.outbound_state_proof_required { + storage_keys.push(bp_messages::storage_keys::outbound_lane_data_key( + P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + )); + } + + let proof = self + .source_client + .prove_storage(storage_keys, id.1) + .await? + .into_iter_nodes() + .collect(); + let proof = FromBridgedChainMessagesProof { + bridged_header_hash: id.1, + storage_proof: proof, + lane: self.lane_id, + nonces_start: *nonces.start(), + nonces_end: *nonces.end(), + }; + Ok((id, nonces, (proof_parameters.dispatch_weight, proof))) + } + + async fn submit_messages_receiving_proof( + &self, + _generated_at_block: TargetHeaderIdOf>, + proof: as MessageLane>::MessagesReceivingProof, + ) -> Result { + let genesis_hash = *self.source_client.genesis_hash(); + let transaction_params = self.transaction_params.clone(); + let (spec_version, transaction_version) = + self.source_client.simple_runtime_version().await?; + self.source_client + .submit_and_watch_signed_extrinsic( + self.transaction_params.signer.public().into(), + SignParam:: { + spec_version, + transaction_version, + genesis_hash, + signer: self.transaction_params.signer.clone(), + }, + move |best_block_id, transaction_nonce| { + make_messages_delivery_proof_transaction::

( + &transaction_params, + best_block_id, + transaction_nonce, + proof, + true, + ) + }, + ) + .await + } + + async fn require_target_header_on_source(&self, id: TargetHeaderIdOf>) { + if let Some(ref target_to_source_headers_relay) = self.target_to_source_headers_relay { + target_to_source_headers_relay.require_more_headers(id.0).await; + } + } +} + +/// Ensure that the messages pallet at source chain is active. +pub(crate) async fn ensure_messages_pallet_active( + client: &Client, +) -> Result<(), SubstrateError> +where + AtChain: ChainWithMessages, + WithChain: ChainWithMessages, +{ + let operating_mode = client + .storage_value(operating_mode_key(WithChain::WITH_CHAIN_MESSAGES_PALLET_NAME), None) + .await?; + let is_halted = + operating_mode == Some(MessagesOperatingMode::Basic(BasicOperatingMode::Halted)); + if is_halted { + Err(SubstrateError::BridgePalletIsHalted) + } else { + Ok(()) + } +} + +/// Make messages delivery proof transaction from given proof. +fn make_messages_delivery_proof_transaction( + source_transaction_params: &TransactionParams>, + source_best_block_id: HeaderIdOf, + transaction_nonce: IndexOf, + proof: SubstrateMessagesDeliveryProof, + trace_call: bool, +) -> Result, SubstrateError> { + let call = + P::ReceiveMessagesDeliveryProofCallBuilder::build_receive_messages_delivery_proof_call( + proof, trace_call, + ); + Ok(UnsignedTransaction::new(call.into(), transaction_nonce) + .era(TransactionEra::new(source_best_block_id, source_transaction_params.mortality))) +} + +/// Read best blocks from given client. +/// +/// This function assumes that the chain that is followed by the `self_client` has +/// bridge GRANDPA pallet deployed and it provides `best_finalized_header_id_method_name` +/// runtime API to read the best finalized Bridged chain header. +/// +/// If `peer_client` is `None`, the value of `actual_best_finalized_peer_at_best_self` will +/// always match the `best_finalized_peer_at_best_self`. +pub async fn read_client_state( + self_client: &Client, + peer_client: Option<&Client>, + best_finalized_header_id_method_name: &str, +) -> Result, HeaderIdOf>, SubstrateError> +where + SelfChain: Chain, + SelfChain::Header: DeserializeOwned, + SelfChain::Index: DeserializeOwned, + PeerChain: Chain, +{ + // let's read our state first: we need best finalized header hash on **this** chain + let self_best_finalized_header_hash = self_client.best_finalized_header_hash().await?; + let self_best_finalized_header = + self_client.header_by_hash(self_best_finalized_header_hash).await?; + let self_best_finalized_id = self_best_finalized_header.id(); + + // now let's read our best header on **this** chain + let self_best_header = self_client.best_header().await?; + let self_best_hash = self_best_header.hash(); + let self_best_id = self_best_header.id(); + + // now let's read id of best finalized peer header at our best finalized block + let peer_on_self_best_finalized_id = + best_finalized_peer_header_at_self::( + self_client, + self_best_hash, + best_finalized_header_id_method_name, + ) + .await?; + + // read actual header, matching the `peer_on_self_best_finalized_id` from the peer chain + let actual_peer_on_self_best_finalized_id = match peer_client { + Some(peer_client) => { + let actual_peer_on_self_best_finalized = + peer_client.header_by_number(peer_on_self_best_finalized_id.0).await?; + actual_peer_on_self_best_finalized.id() + }, + None => peer_on_self_best_finalized_id, + }; + + Ok(ClientState { + best_self: self_best_id, + best_finalized_self: self_best_finalized_id, + best_finalized_peer_at_best_self: peer_on_self_best_finalized_id, + actual_best_finalized_peer_at_best_self: actual_peer_on_self_best_finalized_id, + }) +} + +/// Reads best `PeerChain` header known to the `SelfChain` using provided runtime API method. +/// +/// Method is supposed to be the `FinalityApi::best_finalized()` method. +pub async fn best_finalized_peer_header_at_self( + self_client: &Client, + at_self_hash: HashOf, + best_finalized_header_id_method_name: &str, +) -> Result, SubstrateError> +where + SelfChain: Chain, + PeerChain: Chain, +{ + // now let's read id of best finalized peer header at our best finalized block + let encoded_best_finalized_peer_on_self = self_client + .state_call( + best_finalized_header_id_method_name.into(), + Bytes(Vec::new()), + Some(at_self_hash), + ) + .await?; + + Option::, BlockNumberOf>>::decode( + &mut &encoded_best_finalized_peer_on_self.0[..], + ) + .map_err(SubstrateError::ResponseParseFailed)? + .map(Ok) + .unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized)) +} + +fn validate_out_msgs_details( + out_msgs_details: &[OutboundMessageDetails], + nonces: RangeInclusive, +) -> Result<(), SubstrateError> { + let make_missing_nonce_error = |expected_nonce| { + Err(SubstrateError::Custom(format!( + "Missing nonce {} in message_details call result. Expected all nonces from {:?}", + expected_nonce, nonces, + ))) + }; + + if out_msgs_details.len() > nonces.clone().count() { + return Err(SubstrateError::Custom( + "More messages than requested returned by the message_details call.".into(), + )) + } + + // Check if last nonce is missing. The loop below is not checking this. + if out_msgs_details.is_empty() && !nonces.is_empty() { + return make_missing_nonce_error(*nonces.end()) + } + + let mut nonces_iter = nonces.clone().rev().peekable(); + let mut out_msgs_details_iter = out_msgs_details.iter().rev(); + while let Some((out_msg_details, &nonce)) = out_msgs_details_iter.next().zip(nonces_iter.peek()) + { + nonces_iter.next(); + if out_msg_details.nonce != nonce { + // Some nonces are missing from the middle/tail of the range. This is critical error. + return make_missing_nonce_error(nonce) + } + } + + // Check if some nonces from the beginning of the range are missing. This may happen if + // some messages were already pruned from the source node. This is not a critical error + // and will be auto-resolved by messages lane (and target node). + if nonces_iter.peek().is_some() { + log::info!( + target: "bridge", + "Some messages are missing from the {} node: {:?}. Target node may be out of sync?", + C::NAME, + nonces_iter.rev().collect::>(), + ); + } + + Ok(()) +} + +fn split_msgs_to_refine( + lane_id: LaneId, + msgs_to_refine: MessagesToRefine, +) -> Result, SubstrateError> { + let max_batch_size = Target::max_extrinsic_size() as usize; + let mut batches = vec![]; + + let mut current_msgs_batch = msgs_to_refine; + while !current_msgs_batch.is_empty() { + let mut next_msgs_batch = vec![]; + while (lane_id, ¤t_msgs_batch).encoded_size() > max_batch_size { + if current_msgs_batch.len() <= 1 { + return Err(SubstrateError::Custom(format!( + "Call of {} at {} can't be executed even if only one message is supplied. \ + max_extrinsic_size(): {}", + Source::FROM_CHAIN_MESSAGE_DETAILS_METHOD, + Target::NAME, + Target::max_extrinsic_size(), + ))) + } + + if let Some(msg) = current_msgs_batch.pop() { + next_msgs_batch.insert(0, msg); + } + } + + batches.push(current_msgs_batch); + current_msgs_batch = next_msgs_batch; + } + + Ok(batches) +} + +#[cfg(test)] +mod tests { + use super::*; + use bp_runtime::Chain as ChainBase; + use relay_rialto_client::Rialto; + use relay_rococo_client::Rococo; + use relay_wococo_client::Wococo; + + fn message_details_from_rpc( + nonces: RangeInclusive, + ) -> Vec { + nonces + .into_iter() + .map(|nonce| bp_messages::OutboundMessageDetails { + nonce, + dispatch_weight: Weight::zero(), + size: 0, + }) + .collect() + } + + #[test] + fn validate_out_msgs_details_succeeds_if_no_messages_are_missing() { + assert!( + validate_out_msgs_details::(&message_details_from_rpc(1..=3), 1..=3,).is_ok() + ); + } + + #[test] + fn validate_out_msgs_details_succeeds_if_head_messages_are_missing() { + assert!( + validate_out_msgs_details::(&message_details_from_rpc(2..=3), 1..=3,).is_ok() + ) + } + + #[test] + fn validate_out_msgs_details_fails_if_mid_messages_are_missing() { + let mut message_details_from_rpc = message_details_from_rpc(1..=3); + message_details_from_rpc.remove(1); + assert!(matches!( + validate_out_msgs_details::(&message_details_from_rpc, 1..=3,), + Err(SubstrateError::Custom(_)) + )); + } + + #[test] + fn validate_out_msgs_details_map_fails_if_tail_messages_are_missing() { + assert!(matches!( + validate_out_msgs_details::(&message_details_from_rpc(1..=2), 1..=3,), + Err(SubstrateError::Custom(_)) + )); + } + + #[test] + fn validate_out_msgs_details_fails_if_all_messages_are_missing() { + assert!(matches!( + validate_out_msgs_details::(&[], 1..=3), + Err(SubstrateError::Custom(_)) + )); + } + + #[test] + fn validate_out_msgs_details_fails_if_more_messages_than_nonces() { + assert!(matches!( + validate_out_msgs_details::(&message_details_from_rpc(1..=5), 2..=5,), + Err(SubstrateError::Custom(_)) + )); + } + + fn check_split_msgs_to_refine( + payload_sizes: Vec, + expected_batches: Result, ()>, + ) { + let mut out_msgs_details = vec![]; + for (idx, _) in payload_sizes.iter().enumerate() { + out_msgs_details.push(OutboundMessageDetails { + nonce: idx as MessageNonce, + dispatch_weight: Weight::zero(), + size: 0, + }); + } + + let mut msgs_to_refine = vec![]; + for (&payload_size, out_msg_details) in + payload_sizes.iter().zip(out_msgs_details.iter_mut()) + { + let payload = vec![1u8; payload_size]; + msgs_to_refine.push((payload, out_msg_details)); + } + + let maybe_batches = split_msgs_to_refine::([0, 0, 0, 0], msgs_to_refine); + match expected_batches { + Ok(expected_batches) => { + let batches = maybe_batches.unwrap(); + let mut idx = 0; + assert_eq!(batches.len(), expected_batches.len()); + for (batch, &expected_batch_size) in batches.iter().zip(expected_batches.iter()) { + assert_eq!(batch.len(), expected_batch_size); + for msg_to_refine in batch { + assert_eq!(msg_to_refine.0.len(), payload_sizes[idx]); + idx += 1; + } + } + }, + Err(_) => { + matches!(maybe_batches, Err(SubstrateError::Custom(_))); + }, + } + } + + #[test] + fn test_split_msgs_to_refine() { + let max_extrinsic_size = Rococo::max_extrinsic_size() as usize; + + // Check that an error is returned when one of the messages is too big. + check_split_msgs_to_refine(vec![max_extrinsic_size], Err(())); + check_split_msgs_to_refine(vec![50, 100, max_extrinsic_size, 200], Err(())); + + // Otherwise check that the split is valid. + check_split_msgs_to_refine(vec![100, 200, 300, 400], Ok(vec![4])); + check_split_msgs_to_refine( + vec![ + 50, + 100, + max_extrinsic_size - 500, + 500, + 1000, + 1500, + max_extrinsic_size - 3500, + 5000, + 10000, + ], + Ok(vec![3, 4, 2]), + ); + check_split_msgs_to_refine( + vec![ + 50, + 100, + max_extrinsic_size - 150, + 500, + 1000, + 1500, + max_extrinsic_size - 3000, + 5000, + 10000, + ], + Ok(vec![2, 1, 3, 1, 2]), + ); + check_split_msgs_to_refine( + vec![ + 5000, + 10000, + max_extrinsic_size - 3500, + 500, + 1000, + 1500, + max_extrinsic_size - 500, + 50, + 100, + ], + Ok(vec![2, 4, 3]), + ); + } +} diff --git a/relays/lib-substrate-relay/src/messages_target.rs b/relays/lib-substrate-relay/src/messages_target.rs new file mode 100644 index 00000000000..22a50acf37e --- /dev/null +++ b/relays/lib-substrate-relay/src/messages_target.rs @@ -0,0 +1,298 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Substrate client as Substrate messages target. The chain we connect to should have +//! runtime that implements `HeaderApi` to allow bridging with +//! chain. + +use crate::{ + messages_lane::{MessageLaneAdapter, ReceiveMessagesProofCallBuilder, SubstrateMessageLane}, + messages_source::{ensure_messages_pallet_active, read_client_state, SubstrateMessagesProof}, + on_demand::OnDemandRelay, + TransactionParams, +}; + +use async_std::sync::Arc; +use async_trait::async_trait; +use bp_messages::{ + storage_keys::inbound_lane_data_key, total_unrewarded_messages, InboundLaneData, LaneId, + MessageNonce, UnrewardedRelayersState, +}; +use bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof; +use messages_relay::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::{NoncesSubmitArtifacts, TargetClient, TargetClientState}, +}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages, Client, + Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra, + TransactionTracker, UnsignedTransaction, +}; +use relay_utils::relay_loop::Client as RelayClient; +use sp_core::Pair; +use std::{collections::VecDeque, convert::TryFrom, ops::RangeInclusive}; + +/// Message receiving proof returned by the target Substrate node. +pub type SubstrateMessagesDeliveryProof = + (UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof>); + +/// Substrate client as Substrate messages target. +pub struct SubstrateMessagesTarget { + target_client: Client, + source_client: Client, + lane_id: LaneId, + relayer_id_at_source: AccountIdOf, + transaction_params: TransactionParams>, + source_to_target_headers_relay: Option>>>, +} + +impl SubstrateMessagesTarget

{ + /// Create new Substrate headers target. + pub fn new( + target_client: Client, + source_client: Client, + lane_id: LaneId, + relayer_id_at_source: AccountIdOf, + transaction_params: TransactionParams>, + source_to_target_headers_relay: Option< + Arc>>, + >, + ) -> Self { + SubstrateMessagesTarget { + target_client, + source_client, + lane_id, + relayer_id_at_source, + transaction_params, + source_to_target_headers_relay, + } + } + + /// Read inbound lane state from the on-chain storage at given block. + async fn inbound_lane_data( + &self, + id: TargetHeaderIdOf>, + ) -> Result>>, SubstrateError> { + self.target_client + .storage_value( + inbound_lane_data_key( + P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + ), + Some(id.1), + ) + .await + } + + /// Ensure that the messages pallet at target chain is active. + async fn ensure_pallet_active(&self) -> Result<(), SubstrateError> { + ensure_messages_pallet_active::(&self.target_client).await + } +} + +impl Clone for SubstrateMessagesTarget

{ + fn clone(&self) -> Self { + Self { + target_client: self.target_client.clone(), + source_client: self.source_client.clone(), + lane_id: self.lane_id, + relayer_id_at_source: self.relayer_id_at_source.clone(), + transaction_params: self.transaction_params.clone(), + source_to_target_headers_relay: self.source_to_target_headers_relay.clone(), + } + } +} + +#[async_trait] +impl RelayClient for SubstrateMessagesTarget

{ + type Error = SubstrateError; + + async fn reconnect(&mut self) -> Result<(), SubstrateError> { + self.target_client.reconnect().await?; + self.source_client.reconnect().await + } +} + +#[async_trait] +impl TargetClient> for SubstrateMessagesTarget

+where + AccountIdOf: From< as Pair>::Public>, + BalanceOf: TryFrom>, +{ + type TransactionTracker = TransactionTracker>; + + async fn state(&self) -> Result>, SubstrateError> { + // we can't continue to deliver confirmations if source node is out of sync, because + // it may have already received confirmations that we're going to deliver + // + // we can't continue to deliver messages if target node is out of sync, because + // it may have already received (some of) messages that we're going to deliver + self.source_client.ensure_synced().await?; + self.target_client.ensure_synced().await?; + // we can't relay messages if messages pallet at target chain is halted + self.ensure_pallet_active().await?; + + read_client_state( + &self.target_client, + Some(&self.source_client), + P::SourceChain::BEST_FINALIZED_HEADER_ID_METHOD, + ) + .await + } + + async fn latest_received_nonce( + &self, + id: TargetHeaderIdOf>, + ) -> Result<(TargetHeaderIdOf>, MessageNonce), SubstrateError> { + // lane data missing from the storage is fine until first message is received + let latest_received_nonce = self + .inbound_lane_data(id) + .await? + .map(|data| data.last_delivered_nonce()) + .unwrap_or(0); + Ok((id, latest_received_nonce)) + } + + async fn latest_confirmed_received_nonce( + &self, + id: TargetHeaderIdOf>, + ) -> Result<(TargetHeaderIdOf>, MessageNonce), SubstrateError> { + // lane data missing from the storage is fine until first message is received + let last_confirmed_nonce = self + .inbound_lane_data(id) + .await? + .map(|data| data.last_confirmed_nonce) + .unwrap_or(0); + Ok((id, last_confirmed_nonce)) + } + + async fn unrewarded_relayers_state( + &self, + id: TargetHeaderIdOf>, + ) -> Result<(TargetHeaderIdOf>, UnrewardedRelayersState), SubstrateError> + { + let inbound_lane_data = self.inbound_lane_data(id).await?; + let last_delivered_nonce = + inbound_lane_data.as_ref().map(|data| data.last_delivered_nonce()).unwrap_or(0); + let relayers = inbound_lane_data.map(|data| data.relayers).unwrap_or_else(VecDeque::new); + let unrewarded_relayers_state = bp_messages::UnrewardedRelayersState { + unrewarded_relayer_entries: relayers.len() as _, + messages_in_oldest_entry: relayers + .front() + .map(|entry| 1 + entry.messages.end - entry.messages.begin) + .unwrap_or(0), + total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX), + last_delivered_nonce, + }; + Ok((id, unrewarded_relayers_state)) + } + + async fn prove_messages_receiving( + &self, + id: TargetHeaderIdOf>, + ) -> Result< + ( + TargetHeaderIdOf>, + as MessageLane>::MessagesReceivingProof, + ), + SubstrateError, + > { + let (id, relayers_state) = self.unrewarded_relayers_state(id).await?; + let inbound_data_key = bp_messages::storage_keys::inbound_lane_data_key( + P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + &self.lane_id, + ); + let proof = self + .target_client + .prove_storage(vec![inbound_data_key], id.1) + .await? + .into_iter_nodes() + .collect(); + let proof = FromBridgedChainMessagesDeliveryProof { + bridged_header_hash: id.1, + storage_proof: proof, + lane: self.lane_id, + }; + Ok((id, (relayers_state, proof))) + } + + async fn submit_messages_proof( + &self, + _generated_at_header: SourceHeaderIdOf>, + nonces: RangeInclusive, + proof: as MessageLane>::MessagesProof, + ) -> Result, SubstrateError> { + let genesis_hash = *self.target_client.genesis_hash(); + let transaction_params = self.transaction_params.clone(); + let relayer_id_at_source = self.relayer_id_at_source.clone(); + let nonces_clone = nonces.clone(); + let (spec_version, transaction_version) = + self.target_client.simple_runtime_version().await?; + let tx_tracker = self + .target_client + .submit_and_watch_signed_extrinsic( + self.transaction_params.signer.public().into(), + SignParam:: { + spec_version, + transaction_version, + genesis_hash, + signer: self.transaction_params.signer.clone(), + }, + move |best_block_id, transaction_nonce| { + make_messages_delivery_transaction::

( + &transaction_params, + best_block_id, + transaction_nonce, + relayer_id_at_source, + nonces_clone, + proof, + true, + ) + }, + ) + .await?; + Ok(NoncesSubmitArtifacts { nonces, tx_tracker }) + } + + async fn require_source_header_on_target(&self, id: SourceHeaderIdOf>) { + if let Some(ref source_to_target_headers_relay) = self.source_to_target_headers_relay { + source_to_target_headers_relay.require_more_headers(id.0).await; + } + } +} + +/// Make messages delivery transaction from given proof. +fn make_messages_delivery_transaction( + target_transaction_params: &TransactionParams>, + target_best_block_id: HeaderIdOf, + transaction_nonce: IndexOf, + relayer_id_at_source: AccountIdOf, + nonces: RangeInclusive, + proof: SubstrateMessagesProof, + trace_call: bool, +) -> Result, SubstrateError> { + let messages_count = nonces.end() - nonces.start() + 1; + let dispatch_weight = proof.0; + let call = P::ReceiveMessagesProofCallBuilder::build_receive_messages_proof_call( + relayer_id_at_source, + proof, + messages_count as _, + dispatch_weight, + trace_call, + ); + Ok(UnsignedTransaction::new(call.into(), transaction_nonce) + .era(TransactionEra::new(target_best_block_id, target_transaction_params.mortality))) +} diff --git a/relays/lib-substrate-relay/src/on_demand/headers.rs b/relays/lib-substrate-relay/src/on_demand/headers.rs new file mode 100644 index 00000000000..c0603cda8cd --- /dev/null +++ b/relays/lib-substrate-relay/src/on_demand/headers.rs @@ -0,0 +1,457 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! On-demand Substrate -> Substrate header finality relay. + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use bp_header_chain::ConsensusLogReader; +use futures::{select, FutureExt}; +use num_traits::{One, Zero}; +use sp_runtime::traits::Header; + +use finality_relay::{FinalitySyncParams, TargetClient as FinalityTargetClient}; +use relay_substrate_client::{AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client}; +use relay_utils::{ + metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError, + STALL_TIMEOUT, +}; + +use crate::{ + finality::{ + engine::Engine, + source::{RequiredHeaderNumberRef, SubstrateFinalitySource}, + target::SubstrateFinalityTarget, + SubstrateFinalitySyncPipeline, RECENT_FINALITY_PROOFS_LIMIT, + }, + on_demand::OnDemandRelay, + TransactionParams, +}; + +/// On-demand Substrate <-> Substrate header finality relay. +/// +/// This relay may be requested to sync more headers, whenever some other relay (e.g. messages +/// relay) needs it to continue its regular work. When enough headers are relayed, on-demand stops +/// syncing headers. +#[derive(Clone)] +pub struct OnDemandHeadersRelay { + /// Relay task name. + relay_task_name: String, + /// Shared reference to maximal required finalized header number. + required_header_number: RequiredHeaderNumberRef, +} + +impl OnDemandHeadersRelay { + /// Create new on-demand headers relay. + pub fn new>( + source_client: Client, + target_client: Client, + target_transaction_params: TransactionParams>, + only_mandatory_headers: bool, + ) -> Self + where + AccountIdOf: + From< as sp_core::Pair>::Public>, + { + let required_header_number = Arc::new(Mutex::new(Zero::zero())); + let this = OnDemandHeadersRelay { + relay_task_name: on_demand_headers_relay_name::(), + required_header_number: required_header_number.clone(), + }; + async_std::task::spawn(async move { + background_task::

( + source_client, + target_client, + target_transaction_params, + only_mandatory_headers, + required_header_number, + ) + .await; + }); + + this + } +} + +#[async_trait] +impl OnDemandRelay> + for OnDemandHeadersRelay +{ + async fn require_more_headers(&self, required_header: BlockNumberOf) { + let mut required_header_number = self.required_header_number.lock().await; + if required_header > *required_header_number { + log::trace!( + target: "bridge", + "[{}] More {} headers required. Going to sync up to the {}", + self.relay_task_name, + SourceChain::NAME, + required_header, + ); + + *required_header_number = required_header; + } + } +} + +/// Background task that is responsible for starting headers relay. +async fn background_task( + source_client: Client, + target_client: Client, + target_transaction_params: TransactionParams>, + only_mandatory_headers: bool, + required_header_number: RequiredHeaderNumberRef, +) where + AccountIdOf: From< as sp_core::Pair>::Public>, +{ + let relay_task_name = on_demand_headers_relay_name::(); + let target_transactions_mortality = target_transaction_params.mortality; + let mut finality_source = SubstrateFinalitySource::

::new( + source_client.clone(), + Some(required_header_number.clone()), + ); + let mut finality_target = + SubstrateFinalityTarget::new(target_client.clone(), target_transaction_params); + let mut latest_non_mandatory_at_source = Zero::zero(); + + let mut restart_relay = true; + let finality_relay_task = futures::future::Fuse::terminated(); + futures::pin_mut!(finality_relay_task); + + loop { + select! { + _ = async_std::task::sleep(P::TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {}, + _ = finality_relay_task => { + // this should never happen in practice given the current code + restart_relay = true; + }, + } + + // read best finalized source header number from source + let best_finalized_source_header_at_source = + best_finalized_source_header_at_source(&finality_source, &relay_task_name).await; + if matches!(best_finalized_source_header_at_source, Err(ref e) if e.is_connection_error()) { + relay_utils::relay_loop::reconnect_failed_client( + FailedClient::Source, + relay_utils::relay_loop::RECONNECT_DELAY, + &mut finality_source, + &mut finality_target, + ) + .await; + continue + } + + // read best finalized source header number from target + let best_finalized_source_header_at_target = + best_finalized_source_header_at_target::

(&finality_target, &relay_task_name).await; + if matches!(best_finalized_source_header_at_target, Err(ref e) if e.is_connection_error()) { + relay_utils::relay_loop::reconnect_failed_client( + FailedClient::Target, + relay_utils::relay_loop::RECONNECT_DELAY, + &mut finality_source, + &mut finality_target, + ) + .await; + continue + } + + // submit mandatory header if some headers are missing + let best_finalized_source_header_at_source_fmt = + format!("{best_finalized_source_header_at_source:?}"); + let best_finalized_source_header_at_target_fmt = + format!("{best_finalized_source_header_at_target:?}"); + let required_header_number_value = *required_header_number.lock().await; + let mandatory_scan_range = mandatory_headers_scan_range::( + best_finalized_source_header_at_source.ok(), + best_finalized_source_header_at_target.ok(), + required_header_number_value, + ) + .await; + + log::trace!( + target: "bridge", + "[{}] Mandatory headers scan range: ({:?}, {:?}, {:?}) -> {:?}", + relay_task_name, + required_header_number_value, + best_finalized_source_header_at_source_fmt, + best_finalized_source_header_at_target_fmt, + mandatory_scan_range, + ); + + if let Some(mandatory_scan_range) = mandatory_scan_range { + let relay_mandatory_header_result = relay_mandatory_header_from_range( + &finality_source, + &required_header_number, + best_finalized_source_header_at_target_fmt, + ( + std::cmp::max(mandatory_scan_range.0, latest_non_mandatory_at_source), + mandatory_scan_range.1, + ), + &relay_task_name, + ) + .await; + match relay_mandatory_header_result { + Ok(true) => (), + Ok(false) => { + // there are no (or we don't need to relay them) mandatory headers in the range + // => to avoid scanning the same headers over and over again, remember that + latest_non_mandatory_at_source = mandatory_scan_range.1; + + log::trace!( + target: "bridge", + "[{}] No mandatory {} headers in the range {:?}", + relay_task_name, + P::SourceChain::NAME, + mandatory_scan_range, + ); + }, + Err(e) => { + log::warn!( + target: "bridge", + "[{}] Failed to scan mandatory {} headers range ({:?}): {:?}", + relay_task_name, + P::SourceChain::NAME, + mandatory_scan_range, + e, + ); + + if e.is_connection_error() { + relay_utils::relay_loop::reconnect_failed_client( + FailedClient::Source, + relay_utils::relay_loop::RECONNECT_DELAY, + &mut finality_source, + &mut finality_target, + ) + .await; + continue + } + }, + } + } + + // start/restart relay + if restart_relay { + let stall_timeout = relay_substrate_client::transaction_stall_timeout( + target_transactions_mortality, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + STALL_TIMEOUT, + ); + + log::info!( + target: "bridge", + "[{}] Starting on-demand headers relay task\n\t\ + Only mandatory headers: {}\n\t\ + Tx mortality: {:?} (~{}m)\n\t\ + Stall timeout: {:?}", + relay_task_name, + only_mandatory_headers, + target_transactions_mortality, + stall_timeout.as_secs_f64() / 60.0f64, + stall_timeout, + ); + + finality_relay_task.set( + finality_relay::run( + finality_source.clone(), + finality_target.clone(), + FinalitySyncParams { + tick: std::cmp::max( + P::SourceChain::AVERAGE_BLOCK_INTERVAL, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + ), + recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT, + stall_timeout, + only_mandatory_headers, + }, + MetricsParams::disabled(), + futures::future::pending(), + ) + .fuse(), + ); + + restart_relay = false; + } + } +} + +/// Returns `Some()` with inclusive range of headers which must be scanned for mandatory headers +/// and the first of such headers must be submitted to the target node. +async fn mandatory_headers_scan_range( + best_finalized_source_header_at_source: Option, + best_finalized_source_header_at_target: Option, + required_header_number: BlockNumberOf, +) -> Option<(C::BlockNumber, C::BlockNumber)> { + // if we have been unable to read header number from the target, then let's assume + // that it is the same as required header number. Otherwise we risk submitting + // unneeded transactions + let best_finalized_source_header_at_target = + best_finalized_source_header_at_target.unwrap_or(required_header_number); + + // if we have been unable to read header number from the source, then let's assume + // that it is the same as at the target + let best_finalized_source_header_at_source = + best_finalized_source_header_at_source.unwrap_or(best_finalized_source_header_at_target); + + // if relay is already asked to sync more headers than we have at source, don't do anything yet + if required_header_number >= best_finalized_source_header_at_source { + return None + } + + Some(( + best_finalized_source_header_at_target + One::one(), + best_finalized_source_header_at_source, + )) +} + +/// Try to find mandatory header in the inclusive headers range and, if one is found, ask to relay +/// it. +/// +/// Returns `true` if header was found and (asked to be) relayed and `false` otherwise. +async fn relay_mandatory_header_from_range( + finality_source: &SubstrateFinalitySource

, + required_header_number: &RequiredHeaderNumberRef, + best_finalized_source_header_at_target: String, + range: (BlockNumberOf, BlockNumberOf), + relay_task_name: &str, +) -> Result { + // search for mandatory header first + let mandatory_source_header_number = + find_mandatory_header_in_range(finality_source, range).await?; + + // if there are no mandatory headers - we have nothing to do + let mandatory_source_header_number = match mandatory_source_header_number { + Some(mandatory_source_header_number) => mandatory_source_header_number, + None => return Ok(false), + }; + + // `find_mandatory_header` call may take a while => check if `required_header_number` is still + // less than our `mandatory_source_header_number` before logging anything + let mut required_header_number = required_header_number.lock().await; + if *required_header_number >= mandatory_source_header_number { + return Ok(false) + } + + log::trace!( + target: "bridge", + "[{}] Too many {} headers missing at target ({} vs {}). Going to sync up to the mandatory {}", + relay_task_name, + P::SourceChain::NAME, + best_finalized_source_header_at_target, + range.1, + mandatory_source_header_number, + ); + + *required_header_number = mandatory_source_header_number; + Ok(true) +} + +/// Read best finalized source block number from source client. +/// +/// Returns `None` if we have failed to read the number. +async fn best_finalized_source_header_at_source( + finality_source: &SubstrateFinalitySource

, + relay_task_name: &str, +) -> Result, relay_substrate_client::Error> { + finality_source.on_chain_best_finalized_block_number().await.map_err(|error| { + log::error!( + target: "bridge", + "[{}] Failed to read best finalized source header from source: {:?}", + relay_task_name, + error, + ); + + error + }) +} + +/// Read best finalized source block number from target client. +/// +/// Returns `None` if we have failed to read the number. +async fn best_finalized_source_header_at_target( + finality_target: &SubstrateFinalityTarget

, + relay_task_name: &str, +) -> Result, as RelayClient>::Error> +where + AccountIdOf: From< as sp_core::Pair>::Public>, +{ + finality_target + .best_finalized_source_block_id() + .await + .map_err(|error| { + log::error!( + target: "bridge", + "[{}] Failed to read best finalized source header from target: {:?}", + relay_task_name, + error, + ); + + error + }) + .map(|id| id.0) +} + +/// Read first mandatory header in given inclusive range. +/// +/// Returns `Ok(None)` if there were no mandatory headers in the range. +async fn find_mandatory_header_in_range( + finality_source: &SubstrateFinalitySource

, + range: (BlockNumberOf, BlockNumberOf), +) -> Result>, relay_substrate_client::Error> { + let mut current = range.0; + while current <= range.1 { + let header = finality_source.client().header_by_number(current).await?; + if >::ConsensusLogReader::schedules_authorities_change( + header.digest(), + ) { + return Ok(Some(current)) + } + + current += One::one(); + } + + Ok(None) +} + +/// On-demand headers relay task name. +fn on_demand_headers_relay_name() -> String { + format!("{}-to-{}-on-demand-headers", SourceChain::NAME, TargetChain::NAME) +} + +#[cfg(test)] +mod tests { + use super::*; + + type TestChain = relay_rococo_client::Rococo; + + const AT_SOURCE: Option = Some(10); + const AT_TARGET: Option = Some(1); + + #[async_std::test] + async fn mandatory_headers_scan_range_selects_range_if_some_headers_are_missing() { + assert_eq!( + mandatory_headers_scan_range::(AT_SOURCE, AT_TARGET, 0,).await, + Some((AT_TARGET.unwrap() + 1, AT_SOURCE.unwrap())), + ); + } + + #[async_std::test] + async fn mandatory_headers_scan_range_selects_nothing_if_already_queued() { + assert_eq!( + mandatory_headers_scan_range::(AT_SOURCE, AT_TARGET, AT_SOURCE.unwrap(),) + .await, + None, + ); + } +} diff --git a/relays/lib-substrate-relay/src/on_demand/mod.rs b/relays/lib-substrate-relay/src/on_demand/mod.rs new file mode 100644 index 00000000000..7a2dfc9c153 --- /dev/null +++ b/relays/lib-substrate-relay/src/on_demand/mod.rs @@ -0,0 +1,35 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types and functions intended to ease adding of new Substrate -> Substrate +//! on-demand pipelines. + +use async_trait::async_trait; + +pub mod headers; +pub mod parachains; + +/// On-demand headers relay that is relaying finalizing headers only when requested. +#[async_trait] +pub trait OnDemandRelay: Send + Sync { + /// Ask relay to relay source header with given number to the target chain. + /// + /// Depending on implementation, on-demand relay may also relay `required_header` ancestors + /// (e.g. if they're mandatory), or its descendants. The request is considered complete if + /// the best avbailable header at the target chain has number that is larger than or equal + /// to the `required_header`. + async fn require_more_headers(&self, required_header: SourceHeaderNumber); +} diff --git a/relays/lib-substrate-relay/src/on_demand/parachains.rs b/relays/lib-substrate-relay/src/on_demand/parachains.rs new file mode 100644 index 00000000000..35ef8244ae6 --- /dev/null +++ b/relays/lib-substrate-relay/src/on_demand/parachains.rs @@ -0,0 +1,704 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! On-demand Substrate -> Substrate parachain finality relay. + +use crate::{ + messages_source::best_finalized_peer_header_at_self, + on_demand::OnDemandRelay, + parachains::{ + source::ParachainsSource, target::ParachainsTarget, ParachainsPipelineAdapter, + SubstrateParachainsPipeline, + }, + TransactionParams, +}; + +use async_std::{ + channel::{unbounded, Receiver, Sender}, + sync::{Arc, Mutex}, +}; +use async_trait::async_trait; +use bp_polkadot_core::parachains::ParaHash; +use bp_runtime::HeaderIdProvider; +use futures::{select, FutureExt}; +use num_traits::Zero; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use parachains_relay::parachains_loop::{AvailableHeader, ParachainSyncParams, TargetClient}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, +}; +use relay_utils::{ + metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, HeaderId, +}; +use std::fmt::Debug; + +/// On-demand Substrate <-> Substrate parachain finality relay. +/// +/// This relay may be requested to sync more parachain headers, whenever some other relay +/// (e.g. messages relay) needs it to continue its regular work. When enough parachain headers +/// are relayed, on-demand stops syncing headers. +#[derive(Clone)] +pub struct OnDemandParachainsRelay { + /// Relay task name. + relay_task_name: String, + /// Channel used to communicate with background task and ask for relay of parachain heads. + required_header_number_sender: Sender>, +} + +impl OnDemandParachainsRelay { + /// Create new on-demand parachains relay. + /// + /// Note that the argument is the source relay chain client, not the parachain client. + /// That's because parachain finality is determined by the relay chain and we don't + /// need to connect to the parachain itself here. + pub fn new>( + source_relay_client: Client, + target_client: Client, + target_transaction_params: TransactionParams>, + on_demand_source_relay_to_target_headers: Arc< + dyn OnDemandRelay>, + >, + ) -> Self + where + P::SourceParachain: Chain, + P::SourceRelayChain: + Chain, + AccountIdOf: + From< as sp_core::Pair>::Public>, + { + let (required_header_number_sender, required_header_number_receiver) = unbounded(); + let this = OnDemandParachainsRelay { + relay_task_name: on_demand_parachains_relay_name::(), + required_header_number_sender, + }; + async_std::task::spawn(async move { + background_task::

( + source_relay_client, + target_client, + target_transaction_params, + on_demand_source_relay_to_target_headers, + required_header_number_receiver, + ) + .await; + }); + + this + } +} + +#[async_trait] +impl OnDemandRelay> + for OnDemandParachainsRelay +where + SourceParachain: Chain, +{ + async fn require_more_headers(&self, required_header: BlockNumberOf) { + if let Err(e) = self.required_header_number_sender.send(required_header).await { + log::trace!( + target: "bridge", + "[{}] Failed to request {} header {:?}: {:?}", + self.relay_task_name, + SourceParachain::NAME, + required_header, + e, + ); + } + } +} + +/// Background task that is responsible for starting parachain headers relay. +async fn background_task( + source_relay_client: Client, + target_client: Client, + target_transaction_params: TransactionParams>, + on_demand_source_relay_to_target_headers: Arc< + dyn OnDemandRelay>, + >, + required_parachain_header_number_receiver: Receiver>, +) where + P::SourceParachain: Chain, + P::SourceRelayChain: + Chain, + AccountIdOf: From< as sp_core::Pair>::Public>, +{ + let relay_task_name = on_demand_parachains_relay_name::(); + let target_transactions_mortality = target_transaction_params.mortality; + + let mut relay_state = RelayState::Idle; + let mut required_parachain_header_number = Zero::zero(); + let required_para_header_number_ref = Arc::new(Mutex::new(AvailableHeader::Unavailable)); + + let mut restart_relay = true; + let parachains_relay_task = futures::future::Fuse::terminated(); + futures::pin_mut!(parachains_relay_task); + + let mut parachains_source = ParachainsSource::

::new( + source_relay_client.clone(), + required_para_header_number_ref.clone(), + ); + let mut parachains_target = + ParachainsTarget::

::new(target_client.clone(), target_transaction_params.clone()); + + loop { + select! { + new_required_parachain_header_number = required_parachain_header_number_receiver.recv().fuse() => { + let new_required_parachain_header_number = match new_required_parachain_header_number { + Ok(new_required_parachain_header_number) => new_required_parachain_header_number, + Err(e) => { + log::error!( + target: "bridge", + "[{}] Background task has exited with error: {:?}", + relay_task_name, + e, + ); + + return; + }, + }; + + // keep in mind that we are not updating `required_para_header_number_ref` here, because + // then we'll be submitting all previous headers as well (while required relay headers are + // delivered) and we want to avoid that (to reduce cost) + required_parachain_header_number = std::cmp::max( + required_parachain_header_number, + new_required_parachain_header_number, + ); + }, + _ = async_std::task::sleep(P::TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {}, + _ = parachains_relay_task => { + // this should never happen in practice given the current code + restart_relay = true; + }, + } + + // the workflow of the on-demand parachains relay is: + // + // 1) message relay (or any other dependent relay) sees new message at parachain header + // `PH`; + // + // 2) it sees that the target chain does not know `PH`; + // + // 3) it asks on-demand parachains relay to relay `PH` to the target chain; + // + // Phase#1: relaying relay chain header + // + // 4) on-demand parachains relay waits for GRANDPA-finalized block of the source relay chain + // `RH` that is storing `PH` or its descendant. Let it be `PH'`; + // 5) it asks on-demand headers relay to relay `RH` to the target chain; + // 6) it waits until `RH` (or its descendant) is relayed to the target chain; + // + // Phase#2: relaying parachain header + // + // 7) on-demand parachains relay sets `ParachainsSource::maximal_header_number` to the + // `PH'.number()`. + // 8) parachains finality relay sees that the parachain head has been + // updated and relays `PH'` to the target chain. + + // select headers to relay + let relay_data = read_relay_data( + ¶chains_source, + ¶chains_target, + required_parachain_header_number, + ) + .await; + match relay_data { + Ok(relay_data) => { + let prev_relay_state = relay_state; + relay_state = select_headers_to_relay(&relay_data, relay_state); + log::trace!( + target: "bridge", + "[{}] Selected new relay state: {:?} using old state {:?} and data {:?}", + relay_task_name, + relay_state, + prev_relay_state, + relay_data, + ); + }, + Err(failed_client) => { + relay_utils::relay_loop::reconnect_failed_client( + failed_client, + relay_utils::relay_loop::RECONNECT_DELAY, + &mut parachains_source, + &mut parachains_target, + ) + .await; + continue + }, + } + + // we have selected our new 'state' => let's notify our source clients about our new + // requirements + match relay_state { + RelayState::Idle => (), + RelayState::RelayingRelayHeader(required_relay_header) => { + on_demand_source_relay_to_target_headers + .require_more_headers(required_relay_header) + .await; + }, + RelayState::RelayingParaHeader(required_para_header) => { + *required_para_header_number_ref.lock().await = + AvailableHeader::Available(required_para_header); + }, + } + + // start/restart relay + if restart_relay { + let stall_timeout = relay_substrate_client::transaction_stall_timeout( + target_transactions_mortality, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + relay_utils::STALL_TIMEOUT, + ); + + log::info!( + target: "bridge", + "[{}] Starting on-demand-parachains relay task\n\t\ + Tx mortality: {:?} (~{}m)\n\t\ + Stall timeout: {:?}", + relay_task_name, + target_transactions_mortality, + stall_timeout.as_secs_f64() / 60.0f64, + stall_timeout, + ); + + parachains_relay_task.set( + parachains_relay::parachains_loop::run( + parachains_source.clone(), + parachains_target.clone(), + ParachainSyncParams { + parachains: vec![P::SOURCE_PARACHAIN_PARA_ID.into()], + stall_timeout: std::time::Duration::from_secs(60), + strategy: parachains_relay::parachains_loop::ParachainSyncStrategy::Any, + }, + MetricsParams::disabled(), + futures::future::pending(), + ) + .fuse(), + ); + + restart_relay = false; + } + } +} + +/// On-demand parachains relay task name. +fn on_demand_parachains_relay_name() -> String { + format!("{}-to-{}-on-demand-parachain", SourceChain::NAME, TargetChain::NAME) +} + +/// On-demand relay state. +#[derive(Clone, Copy, Debug, PartialEq)] +enum RelayState { + /// On-demand relay is not doing anything. + Idle, + /// Relaying given relay header to relay given parachain header later. + RelayingRelayHeader(RelayNumber), + /// Relaying given parachain header. + RelayingParaHeader(HeaderId), +} + +/// Data gathered from source and target clients, used by on-demand relay. +#[derive(Debug)] +struct RelayData { + /// Parachain header number that is required at the target chain. + pub required_para_header: ParaNumber, + /// Parachain header number, known to the target chain. + pub para_header_at_target: Option, + /// Parachain header id, known to the source (relay) chain. + pub para_header_at_source: Option>, + /// Parachain header, that is available at the source relay chain at `relay_header_at_target` + /// block. + pub para_header_at_relay_header_at_target: Option>, + /// Relay header number at the source chain. + pub relay_header_at_source: RelayNumber, + /// Relay header number at the target chain. + pub relay_header_at_target: RelayNumber, +} + +/// Read required data from source and target clients. +async fn read_relay_data( + source: &ParachainsSource

, + target: &ParachainsTarget

, + required_header_number: BlockNumberOf, +) -> Result< + RelayData< + HashOf, + BlockNumberOf, + BlockNumberOf, + >, + FailedClient, +> +where + ParachainsTarget

: + TargetClient> + RelayClient, +{ + let map_target_err = |e| { + log::error!( + target: "bridge", + "[{}] Failed to read relay data from {} client: {:?}", + on_demand_parachains_relay_name::(), + P::TargetChain::NAME, + e, + ); + FailedClient::Target + }; + let map_source_err = |e| { + log::error!( + target: "bridge", + "[{}] Failed to read relay data from {} client: {:?}", + on_demand_parachains_relay_name::(), + P::SourceRelayChain::NAME, + e, + ); + FailedClient::Source + }; + + let best_target_block_hash = target.best_block().await.map_err(map_target_err)?.1; + let para_header_at_target = + best_finalized_peer_header_at_self::( + target.client(), + best_target_block_hash, + P::SourceParachain::BEST_FINALIZED_HEADER_ID_METHOD, + ) + .await; + // if there are no parachain heads at the target (`BridgePalletIsNotInitialized`), we'll need + // to submit at least one. Otherwise the pallet will be treated as uninitialized and messages + // sync will stall. + let para_header_at_target = match para_header_at_target { + Ok(para_header_at_target) => Some(para_header_at_target.0), + Err(SubstrateError::BridgePalletIsNotInitialized) => None, + Err(e) => return Err(map_target_err(e)), + }; + + let best_finalized_relay_header = + source.client().best_finalized_header().await.map_err(map_source_err)?; + let best_finalized_relay_block_id = best_finalized_relay_header.id(); + let para_header_at_source = source + .on_chain_para_head_id(best_finalized_relay_block_id, P::SOURCE_PARACHAIN_PARA_ID.into()) + .await + .map_err(map_source_err)?; + + let relay_header_at_source = best_finalized_relay_block_id.0; + let relay_header_at_target = + best_finalized_peer_header_at_self::( + target.client(), + best_target_block_hash, + P::SourceRelayChain::BEST_FINALIZED_HEADER_ID_METHOD, + ) + .await + .map_err(map_target_err)?; + + let para_header_at_relay_header_at_target = source + .on_chain_para_head_id(relay_header_at_target, P::SOURCE_PARACHAIN_PARA_ID.into()) + .await + .map_err(map_source_err)?; + + Ok(RelayData { + required_para_header: required_header_number, + para_header_at_target, + para_header_at_source, + relay_header_at_source, + relay_header_at_target: relay_header_at_target.0, + para_header_at_relay_header_at_target, + }) +} + +/// Select relay and parachain headers that need to be relayed. +fn select_headers_to_relay( + data: &RelayData, + mut state: RelayState, +) -> RelayState +where + ParaHash: Clone, + ParaNumber: Copy + PartialOrd + Zero, + RelayNumber: Copy + Debug + Ord, +{ + // Process the `RelayingRelayHeader` state. + if let &RelayState::RelayingRelayHeader(relay_header_number) = &state { + if data.relay_header_at_target < relay_header_number { + // The required relay header hasn't yet been relayed. Ask / wait for it. + return state + } + + // We may switch to `RelayingParaHeader` if parachain head is available. + state = data + .para_header_at_relay_header_at_target + .clone() + .map_or(RelayState::Idle, RelayState::RelayingParaHeader); + } + + // Process the `RelayingParaHeader` state. + if let RelayState::RelayingParaHeader(para_header_id) = &state { + let para_header_at_target_or_zero = data.para_header_at_target.unwrap_or_else(Zero::zero); + if para_header_at_target_or_zero < para_header_id.0 { + // The required parachain header hasn't yet been relayed. Ask / wait for it. + return state + } + } + + // if we haven't read para head from the source, we can't yet do anything + let para_header_at_source = match data.para_header_at_source { + Some(ref para_header_at_source) => para_header_at_source.clone(), + None => return RelayState::Idle, + }; + + // if we have parachain head at the source, but no parachain heads at the target, we'll need + // to deliver at least one parachain head + let (required_para_header, para_header_at_target) = match data.para_header_at_target { + Some(para_header_at_target) => (data.required_para_header, para_header_at_target), + None => (para_header_at_source.0, Zero::zero()), + }; + + // if we have already satisfied our "customer", do nothing + if required_para_header <= para_header_at_target { + return RelayState::Idle + } + + // if required header is not available even at the source chain, let's wait + if required_para_header > para_header_at_source.0 { + return RelayState::Idle + } + + // we will always try to sync latest parachain/relay header, even if we've been asked for some + // its ancestor + + // we need relay chain header first + if data.relay_header_at_target < data.relay_header_at_source { + return RelayState::RelayingRelayHeader(data.relay_header_at_source) + } + + // if all relay headers synced, we may start directly with parachain header + RelayState::RelayingParaHeader(para_header_at_source) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn relay_waits_for_relay_header_to_be_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(50), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: 700, + para_header_at_relay_header_at_target: Some(HeaderId(100, 100)), + }, + RelayState::RelayingRelayHeader(750), + ), + RelayState::RelayingRelayHeader(750), + ); + } + + #[test] + fn relay_starts_relaying_requested_para_header_after_relay_header_is_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(50), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: 750, + para_header_at_relay_header_at_target: Some(HeaderId(100, 100)), + }, + RelayState::RelayingRelayHeader(750), + ), + RelayState::RelayingParaHeader(HeaderId(100, 100)), + ); + } + + #[test] + fn relay_selects_better_para_header_after_better_relay_header_is_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(50), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: 780, + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::RelayingRelayHeader(750), + ), + RelayState::RelayingParaHeader(HeaderId(105, 105)), + ); + } + #[test] + fn relay_waits_for_para_header_to_be_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(50), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: 780, + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::RelayingParaHeader(HeaderId(105, 105)), + ), + RelayState::RelayingParaHeader(HeaderId(105, 105)), + ); + } + + #[test] + fn relay_stays_idle_if_required_para_header_is_already_delivered() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 90, + para_header_at_target: Some(105), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: 780, + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::Idle, + ), + RelayState::Idle, + ); + } + + #[test] + fn relay_waits_for_required_para_header_to_appear_at_source_1() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: None, + relay_header_at_source: 800, + relay_header_at_target: 780, + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::Idle, + ), + RelayState::Idle, + ); + } + + #[test] + fn relay_waits_for_required_para_header_to_appear_at_source_2() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: Some(HeaderId(110, 110)), + relay_header_at_source: 800, + relay_header_at_target: 780, + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::Idle, + ), + RelayState::Idle, + ); + } + + #[test] + fn relay_starts_relaying_relay_header_when_new_para_header_is_requested() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: Some(HeaderId(125, 125)), + relay_header_at_source: 800, + relay_header_at_target: 780, + para_header_at_relay_header_at_target: Some(HeaderId(105, 105)), + }, + RelayState::Idle, + ), + RelayState::RelayingRelayHeader(800), + ); + } + + #[test] + fn relay_starts_relaying_para_header_when_new_para_header_is_requested() { + assert_eq!( + select_headers_to_relay( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: Some(HeaderId(125, 125)), + relay_header_at_source: 800, + relay_header_at_target: 800, + para_header_at_relay_header_at_target: Some(HeaderId(125, 125)), + }, + RelayState::Idle, + ), + RelayState::RelayingParaHeader(HeaderId(125, 125)), + ); + } + + #[test] + fn relay_goes_idle_when_parachain_is_deregistered() { + assert_eq!( + select_headers_to_relay::( + &RelayData { + required_para_header: 120, + para_header_at_target: Some(105), + para_header_at_source: None, + relay_header_at_source: 800, + relay_header_at_target: 800, + para_header_at_relay_header_at_target: None, + }, + RelayState::RelayingRelayHeader(800), + ), + RelayState::Idle, + ); + } + + #[test] + fn relay_starts_relaying_first_parachain_header() { + assert_eq!( + select_headers_to_relay::( + &RelayData { + required_para_header: 0, + para_header_at_target: None, + para_header_at_source: Some(HeaderId(125, 125)), + relay_header_at_source: 800, + relay_header_at_target: 800, + para_header_at_relay_header_at_target: Some(HeaderId(125, 125)), + }, + RelayState::Idle, + ), + RelayState::RelayingParaHeader(HeaderId(125, 125)), + ); + } + + #[test] + fn relay_starts_relaying_relay_header_to_relay_first_parachain_header() { + assert_eq!( + select_headers_to_relay::( + &RelayData { + required_para_header: 0, + para_header_at_target: None, + para_header_at_source: Some(HeaderId(125, 125)), + relay_header_at_source: 800, + relay_header_at_target: 700, + para_header_at_relay_header_at_target: Some(HeaderId(125, 125)), + }, + RelayState::Idle, + ), + RelayState::RelayingRelayHeader(800), + ); + } +} diff --git a/relays/lib-substrate-relay/src/parachains/mod.rs b/relays/lib-substrate-relay/src/parachains/mod.rs new file mode 100644 index 00000000000..1d744a30e4e --- /dev/null +++ b/relays/lib-substrate-relay/src/parachains/mod.rs @@ -0,0 +1,108 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Types and functions intended to ease adding of new Substrate -> Substrate +//! parachain finality proofs synchronization pipelines. + +use async_trait::async_trait; +use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; +use pallet_bridge_parachains::{ + Call as BridgeParachainsCall, Config as BridgeParachainsConfig, RelayBlockHash, + RelayBlockHasher, RelayBlockNumber, +}; +use parachains_relay::ParachainsPipeline; +use relay_substrate_client::{CallOf, Chain, ChainWithTransactions, HeaderIdOf, RelayChain}; +use std::{fmt::Debug, marker::PhantomData}; + +pub mod source; +pub mod target; + +/// Substrate -> Substrate parachain finality proofs synchronization pipeline. +/// +/// This is currently restricted to the single parachain, because it is how it +/// will be used (at least) initially. +#[async_trait] +pub trait SubstrateParachainsPipeline: 'static + Clone + Debug + Send + Sync { + /// Headers of this parachain are submitted to the `Self::TargetChain`. + type SourceParachain: Chain; + /// Relay chain that is storing headers of `Self::SourceParachain`. + type SourceRelayChain: RelayChain; + /// Target chain where `Self::SourceParachain` headers are submitted. + type TargetChain: ChainWithTransactions; + + /// How submit parachains heads call is built? + type SubmitParachainHeadsCallBuilder: SubmitParachainHeadsCallBuilder; + + /// Id of the `Self::SourceParachain`, used for registration in `Self::SourceRelayChain`. + const SOURCE_PARACHAIN_PARA_ID: u32; +} + +/// Adapter that allows all `SubstrateParachainsPipeline` to act as `ParachainsPipeline`. +#[derive(Clone, Debug)] +pub struct ParachainsPipelineAdapter { + _phantom: PhantomData

, +} + +impl ParachainsPipeline for ParachainsPipelineAdapter

{ + type SourceChain = P::SourceRelayChain; + type TargetChain = P::TargetChain; +} + +/// Different ways of building `submit_parachain_heads` calls. +pub trait SubmitParachainHeadsCallBuilder: + 'static + Send + Sync +{ + /// Given parachains and their heads proof, build call of `submit_parachain_heads` + /// function of bridge parachains module at the target chain. + fn build_submit_parachain_heads_call( + at_relay_block: HeaderIdOf, + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + ) -> CallOf; +} + +/// Building `submit_parachain_heads` call when you have direct access to the target +/// chain runtime. +pub struct DirectSubmitParachainHeadsCallBuilder { + _phantom: PhantomData<(P, R, I)>, +} + +impl SubmitParachainHeadsCallBuilder

for DirectSubmitParachainHeadsCallBuilder +where + P: SubstrateParachainsPipeline, + P::SourceRelayChain: Chain, + R: BridgeParachainsConfig + Send + Sync, + I: 'static + Send + Sync, + R::BridgedChain: bp_runtime::Chain< + BlockNumber = RelayBlockNumber, + Hash = RelayBlockHash, + Hasher = RelayBlockHasher, + >, + CallOf: From>, +{ + fn build_submit_parachain_heads_call( + at_relay_block: HeaderIdOf, + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + ) -> CallOf { + BridgeParachainsCall::::submit_parachain_heads { + at_relay_block: (at_relay_block.0, at_relay_block.1), + parachains, + parachain_heads_proof, + } + .into() + } +} diff --git a/relays/lib-substrate-relay/src/parachains/source.rs b/relays/lib-substrate-relay/src/parachains/source.rs new file mode 100644 index 00000000000..2cae7f1a224 --- /dev/null +++ b/relays/lib-substrate-relay/src/parachains/source.rs @@ -0,0 +1,188 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Parachain heads source. + +use crate::parachains::{ParachainsPipelineAdapter, SubstrateParachainsPipeline}; + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use bp_parachains::parachain_head_storage_key_at_source; +use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; +use bp_runtime::HeaderIdProvider; +use codec::Decode; +use parachains_relay::{ + parachains_loop::{AvailableHeader, SourceClient}, + parachains_loop_metrics::ParachainsLoopMetrics, +}; +use relay_substrate_client::{ + Chain, Client, Error as SubstrateError, HeaderIdOf, HeaderOf, RelayChain, +}; +use relay_utils::relay_loop::Client as RelayClient; + +/// Shared updatable reference to the maximal parachain header id that we want to sync from the +/// source. +pub type RequiredHeaderIdRef = Arc>>>; + +/// Substrate client as parachain heads source. +#[derive(Clone)] +pub struct ParachainsSource { + client: Client, + max_head_id: RequiredHeaderIdRef, +} + +impl ParachainsSource

{ + /// Creates new parachains source client. + pub fn new( + client: Client, + max_head_id: RequiredHeaderIdRef, + ) -> Self { + ParachainsSource { client, max_head_id } + } + + /// Returns reference to the underlying RPC client. + pub fn client(&self) -> &Client { + &self.client + } + + /// Return decoded head of given parachain. + pub async fn on_chain_para_head_id( + &self, + at_block: HeaderIdOf, + para_id: ParaId, + ) -> Result>, SubstrateError> { + let storage_key = + parachain_head_storage_key_at_source(P::SourceRelayChain::PARAS_PALLET_NAME, para_id); + let para_head = self.client.raw_storage_value(storage_key, Some(at_block.1)).await?; + let para_head = para_head.map(|h| ParaHead::decode(&mut &h.0[..])).transpose()?; + let para_head = match para_head { + Some(para_head) => para_head, + None => return Ok(None), + }; + let para_head: HeaderOf = Decode::decode(&mut ¶_head.0[..])?; + Ok(Some(para_head.id())) + } +} + +#[async_trait] +impl RelayClient for ParachainsSource

{ + type Error = SubstrateError; + + async fn reconnect(&mut self) -> Result<(), SubstrateError> { + self.client.reconnect().await + } +} + +#[async_trait] +impl SourceClient> + for ParachainsSource

+where + P::SourceParachain: Chain, +{ + async fn ensure_synced(&self) -> Result { + match self.client.ensure_synced().await { + Ok(_) => Ok(true), + Err(SubstrateError::ClientNotSynced(_)) => Ok(false), + Err(e) => Err(e), + } + } + + async fn parachain_head( + &self, + at_block: HeaderIdOf, + metrics: Option<&ParachainsLoopMetrics>, + para_id: ParaId, + ) -> Result, Self::Error> { + // we don't need to support many parachains now + if para_id.0 != P::SOURCE_PARACHAIN_PARA_ID { + return Err(SubstrateError::Custom(format!( + "Parachain id {} is not matching expected {}", + para_id.0, + P::SOURCE_PARACHAIN_PARA_ID, + ))) + } + + let mut para_head_id = AvailableHeader::Missing; + if let Some(on_chain_para_head_id) = self.on_chain_para_head_id(at_block, para_id).await? { + // Never return head that is larger than requested. This way we'll never sync + // headers past `max_header_id`. + para_head_id = match *self.max_head_id.lock().await { + AvailableHeader::Unavailable => AvailableHeader::Unavailable, + AvailableHeader::Missing => { + // `max_header_id` is not set. There is no limit. + AvailableHeader::Available(on_chain_para_head_id) + }, + AvailableHeader::Available(max_head_id) => { + // We report at most `max_header_id`. + AvailableHeader::Available(std::cmp::min(on_chain_para_head_id, max_head_id)) + }, + } + } + + if let (Some(metrics), AvailableHeader::Available(para_head_id)) = (metrics, para_head_id) { + metrics.update_best_parachain_block_at_source(para_id, para_head_id.0); + } + + Ok(para_head_id.map(|para_head_id| para_head_id.1)) + } + + async fn prove_parachain_heads( + &self, + at_block: HeaderIdOf, + parachains: &[ParaId], + ) -> Result<(ParaHeadsProof, Vec), Self::Error> { + let parachain = ParaId(P::SOURCE_PARACHAIN_PARA_ID); + if parachains != [parachain] { + return Err(SubstrateError::Custom(format!( + "Trying to prove unexpected parachains {:?}. Expected {:?}", + parachains, parachain, + ))) + } + + let parachain = parachains[0]; + let storage_key = + parachain_head_storage_key_at_source(P::SourceRelayChain::PARAS_PALLET_NAME, parachain); + let parachain_heads_proof = self + .client + .prove_storage(vec![storage_key.clone()], at_block.1) + .await? + .into_iter_nodes() + .collect(); + + // why we're reading parachain head here once again (it has already been read at the + // `parachain_head`)? that's because `parachain_head` sometimes returns obsolete parachain + // head and loop sometimes asks to prove this obsolete head and gets other (actual) head + // instead + // + // => since we want to provide proper hashes in our `submit_parachain_heads` call, we're + // rereading actual value here + let parachain_head = self + .client + .raw_storage_value(storage_key, Some(at_block.1)) + .await? + .map(|h| ParaHead::decode(&mut &h.0[..])) + .transpose()? + .ok_or_else(|| { + SubstrateError::Custom(format!( + "Failed to read expected parachain {:?} head at {:?}", + parachain, at_block + )) + })?; + let parachain_head_hash = parachain_head.hash(); + + Ok((ParaHeadsProof(parachain_heads_proof), vec![parachain_head_hash])) + } +} diff --git a/relays/lib-substrate-relay/src/parachains/target.rs b/relays/lib-substrate-relay/src/parachains/target.rs new file mode 100644 index 00000000000..c4022f12f5e --- /dev/null +++ b/relays/lib-substrate-relay/src/parachains/target.rs @@ -0,0 +1,201 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Parachain heads target. + +use crate::{ + parachains::{ + ParachainsPipelineAdapter, SubmitParachainHeadsCallBuilder, SubstrateParachainsPipeline, + }, + TransactionParams, +}; + +use async_trait::async_trait; +use bp_parachains::{BestParaHeadHash, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider}; +use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; +use bp_runtime::HeaderIdProvider; +use codec::Decode; +use parachains_relay::{ + parachains_loop::TargetClient, parachains_loop_metrics::ParachainsLoopMetrics, +}; +use relay_substrate_client::{ + AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, + HeaderIdOf, HeaderOf, RelayChain, SignParam, TransactionEra, TransactionTracker, + UnsignedTransaction, +}; +use relay_utils::{relay_loop::Client as RelayClient, HeaderId}; +use sp_core::{Bytes, Pair}; +use sp_runtime::traits::Header as HeaderT; + +/// Substrate client as parachain heads source. +pub struct ParachainsTarget { + client: Client, + transaction_params: TransactionParams>, +} + +impl ParachainsTarget

{ + /// Creates new parachains target client. + pub fn new( + client: Client, + transaction_params: TransactionParams>, + ) -> Self { + ParachainsTarget { client, transaction_params } + } + + /// Returns reference to the underlying RPC client. + pub fn client(&self) -> &Client { + &self.client + } +} + +impl Clone for ParachainsTarget

{ + fn clone(&self) -> Self { + ParachainsTarget { + client: self.client.clone(), + transaction_params: self.transaction_params.clone(), + } + } +} + +#[async_trait] +impl RelayClient for ParachainsTarget

{ + type Error = SubstrateError; + + async fn reconnect(&mut self) -> Result<(), SubstrateError> { + self.client.reconnect().await + } +} + +#[async_trait] +impl

TargetClient> for ParachainsTarget

+where + P: SubstrateParachainsPipeline, + AccountIdOf: From< as Pair>::Public>, +{ + type TransactionTracker = TransactionTracker>; + + async fn best_block(&self) -> Result, Self::Error> { + let best_header = self.client.best_header().await?; + let best_id = best_header.id(); + + Ok(best_id) + } + + async fn best_finalized_source_block( + &self, + at_block: &HeaderIdOf, + ) -> Result, Self::Error> { + let encoded_best_finalized_source_block = self + .client + .state_call( + P::SourceRelayChain::BEST_FINALIZED_HEADER_ID_METHOD.into(), + Bytes(Vec::new()), + Some(at_block.1), + ) + .await?; + + Option::, BlockNumberOf>>::decode( + &mut &encoded_best_finalized_source_block.0[..], + ) + .map_err(SubstrateError::ResponseParseFailed)? + .map(Ok) + .unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized)) + } + + async fn parachain_head( + &self, + at_block: HeaderIdOf, + metrics: Option<&ParachainsLoopMetrics>, + para_id: ParaId, + ) -> Result, Self::Error> { + let best_para_head_hash: Option = self + .client + .storage_map_value::( + P::SourceRelayChain::PARACHAINS_FINALITY_PALLET_NAME, + ¶_id, + Some(at_block.1), + ) + .await? + .map(|para_info| para_info.best_head_hash); + + if let (Some(metrics), &Some(ref best_para_head_hash)) = (metrics, &best_para_head_hash) { + let imported_para_head = self + .client + .storage_double_map_value::( + P::SourceRelayChain::PARACHAINS_FINALITY_PALLET_NAME, + ¶_id, + &best_para_head_hash.head_hash, + Some(at_block.1), + ) + .await + .and_then(|maybe_encoded_head| match maybe_encoded_head { + Some(encoded_head) => + HeaderOf::::decode(&mut &encoded_head.0[..]) + .map(Some) + .map_err(Self::Error::ResponseParseFailed), + None => Ok(None), + }) + .map_err(|e| { + log::error!( + target: "bridge-metrics", + "Failed to read or decode {} parachain header at {}: {:?}. Metric will have obsolete value", + P::SourceParachain::NAME, + P::TargetChain::NAME, + e, + ); + e + }) + .unwrap_or(None); + if let Some(imported_para_head) = imported_para_head { + metrics + .update_best_parachain_block_at_target(para_id, *imported_para_head.number()); + } + } + + Ok(best_para_head_hash) + } + + async fn submit_parachain_heads_proof( + &self, + at_relay_block: HeaderIdOf, + updated_parachains: Vec<(ParaId, ParaHash)>, + proof: ParaHeadsProof, + ) -> Result { + let genesis_hash = *self.client.genesis_hash(); + let transaction_params = self.transaction_params.clone(); + let (spec_version, transaction_version) = self.client.simple_runtime_version().await?; + let call = P::SubmitParachainHeadsCallBuilder::build_submit_parachain_heads_call( + at_relay_block, + updated_parachains, + proof, + ); + self.client + .submit_and_watch_signed_extrinsic( + self.transaction_params.signer.public().into(), + SignParam:: { + spec_version, + transaction_version, + genesis_hash, + signer: transaction_params.signer, + }, + move |best_block_id, transaction_nonce| { + Ok(UnsignedTransaction::new(call.into(), transaction_nonce) + .era(TransactionEra::new(best_block_id, transaction_params.mortality))) + }, + ) + .await + } +} diff --git a/relays/messages/Cargo.toml b/relays/messages/Cargo.toml new file mode 100644 index 00000000000..02e453b1c32 --- /dev/null +++ b/relays/messages/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "messages-relay" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +async-std = { version = "1.6.5", features = ["attributes"] } +async-trait = "0.1" +futures = "0.3.5" +hex = "0.4" +log = "0.4.17" +num-traits = "0.2" +parking_lot = "0.11.0" + +# Bridge Dependencies + +bp-messages = { path = "../../primitives/messages" } +bp-runtime = { path = "../../primitives/runtime" } +finality-relay = { path = "../finality" } +relay-utils = { path = "../utils" } + +sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/messages/src/lib.rs b/relays/messages/src/lib.rs new file mode 100644 index 00000000000..9c62cee5ee3 --- /dev/null +++ b/relays/messages/src/lib.rs @@ -0,0 +1,37 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Relaying [`pallet-bridge-messages`](../pallet_bridge_messages/index.html) application specific +//! data. Message lane allows sending arbitrary messages between bridged chains. This +//! module provides entrypoint that starts reading messages from given message lane +//! of source chain and submits proof-of-message-at-source-chain transactions to the +//! target chain. Additionally, proofs-of-messages-delivery are sent back from the +//! target chain to the source chain. + +// required for futures::select! +#![recursion_limit = "1024"] +#![warn(missing_docs)] + +mod metrics; + +pub mod message_lane; +pub mod message_lane_loop; + +mod message_race_delivery; +mod message_race_limits; +mod message_race_loop; +mod message_race_receiving; +mod message_race_strategy; diff --git a/relays/messages/src/message_lane.rs b/relays/messages/src/message_lane.rs new file mode 100644 index 00000000000..5c9728ad93a --- /dev/null +++ b/relays/messages/src/message_lane.rs @@ -0,0 +1,71 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! One-way message lane types. Within single one-way lane we have three 'races' where we try to: +//! +//! 1) relay new messages from source to target node; +//! 2) relay proof-of-delivery from target to source node. + +use num_traits::{SaturatingAdd, Zero}; +use relay_utils::{BlockNumberBase, HeaderId}; +use sp_arithmetic::traits::AtLeast32BitUnsigned; +use std::{fmt::Debug, ops::Sub}; + +/// One-way message lane. +pub trait MessageLane: 'static + Clone + Send + Sync { + /// Name of the messages source. + const SOURCE_NAME: &'static str; + /// Name of the messages target. + const TARGET_NAME: &'static str; + + /// Messages proof. + type MessagesProof: Clone + Debug + Send + Sync; + /// Messages receiving proof. + type MessagesReceivingProof: Clone + Debug + Send + Sync; + + /// The type of the source chain token balance, that is used to: + /// + /// 1) pay transaction fees; + /// 2) pay message delivery and dispatch fee; + /// 3) pay relayer rewards. + type SourceChainBalance: AtLeast32BitUnsigned + + Clone + + Copy + + Debug + + PartialOrd + + Sub + + SaturatingAdd + + Zero + + Send + + Sync; + /// Number of the source header. + type SourceHeaderNumber: BlockNumberBase; + /// Hash of the source header. + type SourceHeaderHash: Clone + Debug + Default + PartialEq + Send + Sync; + + /// Number of the target header. + type TargetHeaderNumber: BlockNumberBase; + /// Hash of the target header. + type TargetHeaderHash: Clone + Debug + Default + PartialEq + Send + Sync; +} + +/// Source header id within given one-way message lane. +pub type SourceHeaderIdOf

= + HeaderId<

::SourceHeaderHash,

::SourceHeaderNumber>; + +/// Target header id within given one-way message lane. +pub type TargetHeaderIdOf

= + HeaderId<

::TargetHeaderHash,

::TargetHeaderNumber>; diff --git a/relays/messages/src/message_lane_loop.rs b/relays/messages/src/message_lane_loop.rs new file mode 100644 index 00000000000..6b28dcbaa60 --- /dev/null +++ b/relays/messages/src/message_lane_loop.rs @@ -0,0 +1,1136 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Message delivery loop. Designed to work with messages pallet. +//! +//! Single relay instance delivers messages of single lane in single direction. +//! To serve two-way lane, you would need two instances of relay. +//! To serve N two-way lanes, you would need N*2 instances of relay. +//! +//! Please keep in mind that the best header in this file is actually best +//! finalized header. I.e. when talking about headers in lane context, we +//! only care about finalized headers. + +use std::{collections::BTreeMap, fmt::Debug, future::Future, ops::RangeInclusive, time::Duration}; + +use async_trait::async_trait; +use futures::{channel::mpsc::unbounded, future::FutureExt, stream::StreamExt}; + +use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight}; +use relay_utils::{ + interval, metrics::MetricsParams, process_future_result, relay_loop::Client as RelayClient, + retry_backoff, FailedClient, TransactionTracker, +}; + +use crate::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_race_delivery::run as run_message_delivery_race, + message_race_receiving::run as run_message_receiving_race, + metrics::MessageLaneLoopMetrics, +}; + +/// Message lane loop configuration params. +#[derive(Debug, Clone)] +pub struct Params { + /// Id of lane this loop is servicing. + pub lane: LaneId, + /// Interval at which we ask target node about its updates. + pub source_tick: Duration, + /// Interval at which we ask target node about its updates. + pub target_tick: Duration, + /// Delay between moments when connection error happens and our reconnect attempt. + pub reconnect_delay: Duration, + /// Message delivery race parameters. + pub delivery_params: MessageDeliveryParams, +} + +/// Message delivery race parameters. +#[derive(Debug, Clone)] +pub struct MessageDeliveryParams { + /// Maximal number of unconfirmed relayer entries at the inbound lane. If there's that number + /// of entries in the `InboundLaneData::relayers` set, all new messages will be rejected until + /// reward payment will be proved (by including outbound lane state to the message delivery + /// transaction). + pub max_unrewarded_relayer_entries_at_target: MessageNonce, + /// Message delivery race will stop delivering messages if there are + /// `max_unconfirmed_nonces_at_target` unconfirmed nonces on the target node. The race would + /// continue once they're confirmed by the receiving race. + pub max_unconfirmed_nonces_at_target: MessageNonce, + /// Maximal number of relayed messages in single delivery transaction. + pub max_messages_in_single_batch: MessageNonce, + /// Maximal cumulative dispatch weight of relayed messages in single delivery transaction. + pub max_messages_weight_in_single_batch: Weight, + /// Maximal cumulative size of relayed messages in single delivery transaction. + pub max_messages_size_in_single_batch: u32, +} + +/// Message details. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MessageDetails { + /// Message dispatch weight. + pub dispatch_weight: Weight, + /// Message size (number of bytes in encoded payload). + pub size: u32, + /// The relayer reward paid in the source chain tokens. + pub reward: SourceChainBalance, +} + +/// Messages details map. +pub type MessageDetailsMap = + BTreeMap>; + +/// Message delivery race proof parameters. +#[derive(Debug, PartialEq, Eq)] +pub struct MessageProofParameters { + /// Include outbound lane state proof? + pub outbound_state_proof_required: bool, + /// Cumulative dispatch weight of messages that we're building proof for. + pub dispatch_weight: Weight, +} + +/// Artifacts of submitting nonces proof. +pub struct NoncesSubmitArtifacts { + /// Submitted nonces range. + pub nonces: RangeInclusive, + /// Submitted transaction tracker. + pub tx_tracker: T, +} + +/// Source client trait. +#[async_trait] +pub trait SourceClient: RelayClient { + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker>; + + /// Returns state of the client. + async fn state(&self) -> Result, Self::Error>; + + /// Get nonce of instance of latest generated message. + async fn latest_generated_nonce( + &self, + id: SourceHeaderIdOf

, + ) -> Result<(SourceHeaderIdOf

, MessageNonce), Self::Error>; + + /// Get nonce of the latest message, which receiving has been confirmed by the target chain. + async fn latest_confirmed_received_nonce( + &self, + id: SourceHeaderIdOf

, + ) -> Result<(SourceHeaderIdOf

, MessageNonce), Self::Error>; + + /// Returns mapping of message nonces, generated on this client, to their weights. + /// + /// Some messages may be missing from returned map, if corresponding messages were pruned at + /// the source chain. + async fn generated_message_details( + &self, + id: SourceHeaderIdOf

, + nonces: RangeInclusive, + ) -> Result, Self::Error>; + + /// Prove messages in inclusive range [begin; end]. + async fn prove_messages( + &self, + id: SourceHeaderIdOf

, + nonces: RangeInclusive, + proof_parameters: MessageProofParameters, + ) -> Result<(SourceHeaderIdOf

, RangeInclusive, P::MessagesProof), Self::Error>; + + /// Submit messages receiving proof. + async fn submit_messages_receiving_proof( + &self, + generated_at_block: TargetHeaderIdOf

, + proof: P::MessagesReceivingProof, + ) -> Result; + + /// We need given finalized target header on source to continue synchronization. + async fn require_target_header_on_source(&self, id: TargetHeaderIdOf

); +} + +/// Target client trait. +#[async_trait] +pub trait TargetClient: RelayClient { + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker>; + + /// Returns state of the client. + async fn state(&self) -> Result, Self::Error>; + + /// Get nonce of latest received message. + async fn latest_received_nonce( + &self, + id: TargetHeaderIdOf

, + ) -> Result<(TargetHeaderIdOf

, MessageNonce), Self::Error>; + + /// Get nonce of the latest confirmed message. + async fn latest_confirmed_received_nonce( + &self, + id: TargetHeaderIdOf

, + ) -> Result<(TargetHeaderIdOf

, MessageNonce), Self::Error>; + + /// Get state of unrewarded relayers set at the inbound lane. + async fn unrewarded_relayers_state( + &self, + id: TargetHeaderIdOf

, + ) -> Result<(TargetHeaderIdOf

, UnrewardedRelayersState), Self::Error>; + + /// Prove messages receiving at given block. + async fn prove_messages_receiving( + &self, + id: TargetHeaderIdOf

, + ) -> Result<(TargetHeaderIdOf

, P::MessagesReceivingProof), Self::Error>; + + /// Submit messages proof. + async fn submit_messages_proof( + &self, + generated_at_header: SourceHeaderIdOf

, + nonces: RangeInclusive, + proof: P::MessagesProof, + ) -> Result, Self::Error>; + + /// We need given finalized source header on target to continue synchronization. + async fn require_source_header_on_target(&self, id: SourceHeaderIdOf

); +} + +/// State of the client. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ClientState { + /// The best header id of this chain. + pub best_self: SelfHeaderId, + /// Best finalized header id of this chain. + pub best_finalized_self: SelfHeaderId, + /// Best finalized header id of the peer chain read at the best block of this chain (at + /// `best_finalized_self`). + pub best_finalized_peer_at_best_self: PeerHeaderId, + /// Header id of the peer chain with the number, matching the + /// `best_finalized_peer_at_best_self`. + pub actual_best_finalized_peer_at_best_self: PeerHeaderId, +} + +/// State of source client in one-way message lane. +pub type SourceClientState

= ClientState, TargetHeaderIdOf

>; + +/// State of target client in one-way message lane. +pub type TargetClientState

= ClientState, SourceHeaderIdOf

>; + +/// Both clients state. +#[derive(Debug, Default)] +pub struct ClientsState { + /// Source client state. + pub source: Option>, + /// Target client state. + pub target: Option>, +} + +/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs +/// sync loop. +pub fn metrics_prefix(lane: &LaneId) -> String { + format!("{}_to_{}_MessageLane_{}", P::SOURCE_NAME, P::TARGET_NAME, hex::encode(lane)) +} + +/// Run message lane service loop. +pub async fn run( + params: Params, + source_client: impl SourceClient

, + target_client: impl TargetClient

, + metrics_params: MetricsParams, + exit_signal: impl Future + Send + 'static, +) -> Result<(), relay_utils::Error> { + let exit_signal = exit_signal.shared(); + relay_utils::relay_loop(source_client, target_client) + .reconnect_delay(params.reconnect_delay) + .with_metrics(metrics_params) + .loop_metric(MessageLaneLoopMetrics::new(Some(&metrics_prefix::

(¶ms.lane)))?)? + .expose() + .await? + .run(metrics_prefix::

(¶ms.lane), move |source_client, target_client, metrics| { + run_until_connection_lost( + params.clone(), + source_client, + target_client, + metrics, + exit_signal.clone(), + ) + }) + .await +} + +/// Run one-way message delivery loop until connection with target or source node is lost, or exit +/// signal is received. +async fn run_until_connection_lost, TC: TargetClient

>( + params: Params, + source_client: SC, + target_client: TC, + metrics_msg: Option, + exit_signal: impl Future, +) -> Result<(), FailedClient> { + let mut source_retry_backoff = retry_backoff(); + let mut source_client_is_online = false; + let mut source_state_required = true; + let source_state = source_client.state().fuse(); + let source_go_offline_future = futures::future::Fuse::terminated(); + let source_tick_stream = interval(params.source_tick).fuse(); + + let mut target_retry_backoff = retry_backoff(); + let mut target_client_is_online = false; + let mut target_state_required = true; + let target_state = target_client.state().fuse(); + let target_go_offline_future = futures::future::Fuse::terminated(); + let target_tick_stream = interval(params.target_tick).fuse(); + + let ( + (delivery_source_state_sender, delivery_source_state_receiver), + (delivery_target_state_sender, delivery_target_state_receiver), + ) = (unbounded(), unbounded()); + let delivery_race_loop = run_message_delivery_race( + source_client.clone(), + delivery_source_state_receiver, + target_client.clone(), + delivery_target_state_receiver, + metrics_msg.clone(), + params.delivery_params, + ) + .fuse(); + + let ( + (receiving_source_state_sender, receiving_source_state_receiver), + (receiving_target_state_sender, receiving_target_state_receiver), + ) = (unbounded(), unbounded()); + let receiving_race_loop = run_message_receiving_race( + source_client.clone(), + receiving_source_state_receiver, + target_client.clone(), + receiving_target_state_receiver, + metrics_msg.clone(), + ) + .fuse(); + + let exit_signal = exit_signal.fuse(); + + futures::pin_mut!( + source_state, + source_go_offline_future, + source_tick_stream, + target_state, + target_go_offline_future, + target_tick_stream, + delivery_race_loop, + receiving_race_loop, + exit_signal + ); + + loop { + futures::select! { + new_source_state = source_state => { + source_state_required = false; + + source_client_is_online = process_future_result( + new_source_state, + &mut source_retry_backoff, + |new_source_state| { + log::debug!( + target: "bridge", + "Received state from {} node: {:?}", + P::SOURCE_NAME, + new_source_state, + ); + let _ = delivery_source_state_sender.unbounded_send(new_source_state.clone()); + let _ = receiving_source_state_sender.unbounded_send(new_source_state.clone()); + + if let Some(metrics_msg) = metrics_msg.as_ref() { + metrics_msg.update_source_state::

(new_source_state); + } + }, + &mut source_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving state from {} node", P::SOURCE_NAME), + ).fail_if_connection_error(FailedClient::Source)?; + }, + _ = source_go_offline_future => { + source_client_is_online = true; + }, + _ = source_tick_stream.next() => { + source_state_required = true; + }, + new_target_state = target_state => { + target_state_required = false; + + target_client_is_online = process_future_result( + new_target_state, + &mut target_retry_backoff, + |new_target_state| { + log::debug!( + target: "bridge", + "Received state from {} node: {:?}", + P::TARGET_NAME, + new_target_state, + ); + let _ = delivery_target_state_sender.unbounded_send(new_target_state.clone()); + let _ = receiving_target_state_sender.unbounded_send(new_target_state.clone()); + + if let Some(metrics_msg) = metrics_msg.as_ref() { + metrics_msg.update_target_state::

(new_target_state); + } + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving state from {} node", P::TARGET_NAME), + ).fail_if_connection_error(FailedClient::Target)?; + }, + _ = target_go_offline_future => { + target_client_is_online = true; + }, + _ = target_tick_stream.next() => { + target_state_required = true; + }, + + delivery_error = delivery_race_loop => { + match delivery_error { + Ok(_) => unreachable!("only ends with error; qed"), + Err(err) => return Err(err), + } + }, + receiving_error = receiving_race_loop => { + match receiving_error { + Ok(_) => unreachable!("only ends with error; qed"), + Err(err) => return Err(err), + } + }, + + () = exit_signal => { + return Ok(()); + } + } + + if source_client_is_online && source_state_required { + log::debug!(target: "bridge", "Asking {} node about its state", P::SOURCE_NAME); + source_state.set(source_client.state().fuse()); + source_client_is_online = false; + } + + if target_client_is_online && target_state_required { + log::debug!(target: "bridge", "Asking {} node about its state", P::TARGET_NAME); + target_state.set(target_client.state().fuse()); + target_client_is_online = false; + } + } +} + +#[cfg(test)] +pub(crate) mod tests { + use std::sync::Arc; + + use futures::stream::StreamExt; + use parking_lot::Mutex; + + use relay_utils::{HeaderId, MaybeConnectionError, TrackedTransactionStatus}; + + use super::*; + + pub fn header_id(number: TestSourceHeaderNumber) -> TestSourceHeaderId { + HeaderId(number, number) + } + + pub type TestSourceChainBalance = u64; + pub type TestSourceHeaderId = HeaderId; + pub type TestTargetHeaderId = HeaderId; + + pub type TestMessagesProof = (RangeInclusive, Option); + pub type TestMessagesReceivingProof = MessageNonce; + + pub type TestSourceHeaderNumber = u64; + pub type TestSourceHeaderHash = u64; + + pub type TestTargetHeaderNumber = u64; + pub type TestTargetHeaderHash = u64; + + #[derive(Debug)] + pub struct TestError; + + impl MaybeConnectionError for TestError { + fn is_connection_error(&self) -> bool { + true + } + } + + #[derive(Clone)] + pub struct TestMessageLane; + + impl MessageLane for TestMessageLane { + const SOURCE_NAME: &'static str = "TestSource"; + const TARGET_NAME: &'static str = "TestTarget"; + + type MessagesProof = TestMessagesProof; + type MessagesReceivingProof = TestMessagesReceivingProof; + + type SourceChainBalance = TestSourceChainBalance; + type SourceHeaderNumber = TestSourceHeaderNumber; + type SourceHeaderHash = TestSourceHeaderHash; + + type TargetHeaderNumber = TestTargetHeaderNumber; + type TargetHeaderHash = TestTargetHeaderHash; + } + + #[derive(Clone, Debug)] + pub struct TestTransactionTracker(TrackedTransactionStatus); + + impl Default for TestTransactionTracker { + fn default() -> TestTransactionTracker { + TestTransactionTracker(TrackedTransactionStatus::Finalized(Default::default())) + } + } + + #[async_trait] + impl TransactionTracker for TestTransactionTracker { + type HeaderId = TestTargetHeaderId; + + async fn wait(self) -> TrackedTransactionStatus { + self.0 + } + } + + #[derive(Debug, Clone)] + pub struct TestClientData { + is_source_fails: bool, + is_source_reconnected: bool, + source_state: SourceClientState, + source_latest_generated_nonce: MessageNonce, + source_latest_confirmed_received_nonce: MessageNonce, + source_tracked_transaction_status: TrackedTransactionStatus, + submitted_messages_receiving_proofs: Vec, + is_target_fails: bool, + is_target_reconnected: bool, + target_state: SourceClientState, + target_latest_received_nonce: MessageNonce, + target_latest_confirmed_received_nonce: MessageNonce, + target_tracked_transaction_status: TrackedTransactionStatus, + submitted_messages_proofs: Vec, + target_to_source_header_required: Option, + target_to_source_header_requirements: Vec, + source_to_target_header_required: Option, + source_to_target_header_requirements: Vec, + } + + impl Default for TestClientData { + fn default() -> TestClientData { + TestClientData { + is_source_fails: false, + is_source_reconnected: false, + source_state: Default::default(), + source_latest_generated_nonce: 0, + source_latest_confirmed_received_nonce: 0, + source_tracked_transaction_status: TrackedTransactionStatus::Finalized(HeaderId( + 0, + Default::default(), + )), + submitted_messages_receiving_proofs: Vec::new(), + is_target_fails: false, + is_target_reconnected: false, + target_state: Default::default(), + target_latest_received_nonce: 0, + target_latest_confirmed_received_nonce: 0, + target_tracked_transaction_status: TrackedTransactionStatus::Finalized(HeaderId( + 0, + Default::default(), + )), + submitted_messages_proofs: Vec::new(), + target_to_source_header_required: None, + target_to_source_header_requirements: Vec::new(), + source_to_target_header_required: None, + source_to_target_header_requirements: Vec::new(), + } + } + } + + #[derive(Clone)] + pub struct TestSourceClient { + data: Arc>, + tick: Arc, + post_tick: Arc, + } + + impl Default for TestSourceClient { + fn default() -> Self { + TestSourceClient { + data: Arc::new(Mutex::new(TestClientData::default())), + tick: Arc::new(|_| {}), + post_tick: Arc::new(|_| {}), + } + } + } + + #[async_trait] + impl RelayClient for TestSourceClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + { + let mut data = self.data.lock(); + (self.tick)(&mut data); + data.is_source_reconnected = true; + (self.post_tick)(&mut data); + } + Ok(()) + } + } + + #[async_trait] + impl SourceClient for TestSourceClient { + type TransactionTracker = TestTransactionTracker; + + async fn state(&self) -> Result, TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_source_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok(data.source_state.clone()) + } + + async fn latest_generated_nonce( + &self, + id: SourceHeaderIdOf, + ) -> Result<(SourceHeaderIdOf, MessageNonce), TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_source_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok((id, data.source_latest_generated_nonce)) + } + + async fn latest_confirmed_received_nonce( + &self, + id: SourceHeaderIdOf, + ) -> Result<(SourceHeaderIdOf, MessageNonce), TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + (self.post_tick)(&mut data); + Ok((id, data.source_latest_confirmed_received_nonce)) + } + + async fn generated_message_details( + &self, + _id: SourceHeaderIdOf, + nonces: RangeInclusive, + ) -> Result, TestError> { + Ok(nonces + .map(|nonce| { + ( + nonce, + MessageDetails { + dispatch_weight: Weight::from_ref_time(1), + size: 1, + reward: 1, + }, + ) + }) + .collect()) + } + + async fn prove_messages( + &self, + id: SourceHeaderIdOf, + nonces: RangeInclusive, + proof_parameters: MessageProofParameters, + ) -> Result< + (SourceHeaderIdOf, RangeInclusive, TestMessagesProof), + TestError, + > { + let mut data = self.data.lock(); + (self.tick)(&mut data); + (self.post_tick)(&mut data); + Ok(( + id, + nonces.clone(), + ( + nonces, + if proof_parameters.outbound_state_proof_required { + Some(data.source_latest_confirmed_received_nonce) + } else { + None + }, + ), + )) + } + + async fn submit_messages_receiving_proof( + &self, + _generated_at_block: TargetHeaderIdOf, + proof: TestMessagesReceivingProof, + ) -> Result { + let mut data = self.data.lock(); + (self.tick)(&mut data); + data.source_state.best_self = + HeaderId(data.source_state.best_self.0 + 1, data.source_state.best_self.1 + 1); + data.source_state.best_finalized_self = data.source_state.best_self; + data.submitted_messages_receiving_proofs.push(proof); + data.source_latest_confirmed_received_nonce = proof; + (self.post_tick)(&mut data); + Ok(TestTransactionTracker(data.source_tracked_transaction_status)) + } + + async fn require_target_header_on_source(&self, id: TargetHeaderIdOf) { + let mut data = self.data.lock(); + data.target_to_source_header_required = Some(id); + data.target_to_source_header_requirements.push(id); + (self.tick)(&mut data); + (self.post_tick)(&mut data); + } + } + + #[derive(Clone)] + pub struct TestTargetClient { + data: Arc>, + tick: Arc, + post_tick: Arc, + } + + impl Default for TestTargetClient { + fn default() -> Self { + TestTargetClient { + data: Arc::new(Mutex::new(TestClientData::default())), + tick: Arc::new(|_| {}), + post_tick: Arc::new(|_| {}), + } + } + } + + #[async_trait] + impl RelayClient for TestTargetClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + { + let mut data = self.data.lock(); + (self.tick)(&mut data); + data.is_target_reconnected = true; + (self.post_tick)(&mut data); + } + Ok(()) + } + } + + #[async_trait] + impl TargetClient for TestTargetClient { + type TransactionTracker = TestTransactionTracker; + + async fn state(&self) -> Result, TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_target_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok(data.target_state.clone()) + } + + async fn latest_received_nonce( + &self, + id: TargetHeaderIdOf, + ) -> Result<(TargetHeaderIdOf, MessageNonce), TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_target_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok((id, data.target_latest_received_nonce)) + } + + async fn unrewarded_relayers_state( + &self, + id: TargetHeaderIdOf, + ) -> Result<(TargetHeaderIdOf, UnrewardedRelayersState), TestError> { + Ok(( + id, + UnrewardedRelayersState { + unrewarded_relayer_entries: 0, + messages_in_oldest_entry: 0, + total_messages: 0, + last_delivered_nonce: 0, + }, + )) + } + + async fn latest_confirmed_received_nonce( + &self, + id: TargetHeaderIdOf, + ) -> Result<(TargetHeaderIdOf, MessageNonce), TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_target_fails { + return Err(TestError) + } + (self.post_tick)(&mut data); + Ok((id, data.target_latest_confirmed_received_nonce)) + } + + async fn prove_messages_receiving( + &self, + id: TargetHeaderIdOf, + ) -> Result<(TargetHeaderIdOf, TestMessagesReceivingProof), TestError> { + Ok((id, self.data.lock().target_latest_received_nonce)) + } + + async fn submit_messages_proof( + &self, + _generated_at_header: SourceHeaderIdOf, + nonces: RangeInclusive, + proof: TestMessagesProof, + ) -> Result, TestError> { + let mut data = self.data.lock(); + (self.tick)(&mut data); + if data.is_target_fails { + return Err(TestError) + } + data.target_state.best_self = + HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.1 + 1); + data.target_state.best_finalized_self = data.target_state.best_self; + data.target_latest_received_nonce = *proof.0.end(); + if let Some(target_latest_confirmed_received_nonce) = proof.1 { + data.target_latest_confirmed_received_nonce = + target_latest_confirmed_received_nonce; + } + data.submitted_messages_proofs.push(proof); + (self.post_tick)(&mut data); + Ok(NoncesSubmitArtifacts { + nonces, + tx_tracker: TestTransactionTracker(data.target_tracked_transaction_status), + }) + } + + async fn require_source_header_on_target(&self, id: SourceHeaderIdOf) { + let mut data = self.data.lock(); + data.source_to_target_header_required = Some(id); + data.source_to_target_header_requirements.push(id); + (self.tick)(&mut data); + (self.post_tick)(&mut data); + } + } + + fn run_loop_test( + data: TestClientData, + source_tick: Arc, + source_post_tick: Arc, + target_tick: Arc, + target_post_tick: Arc, + exit_signal: impl Future + 'static + Send, + ) -> TestClientData { + async_std::task::block_on(async { + let data = Arc::new(Mutex::new(data)); + + let source_client = TestSourceClient { + data: data.clone(), + tick: source_tick, + post_tick: source_post_tick, + }; + let target_client = TestTargetClient { + data: data.clone(), + tick: target_tick, + post_tick: target_post_tick, + }; + let _ = run( + Params { + lane: [0, 0, 0, 0], + source_tick: Duration::from_millis(100), + target_tick: Duration::from_millis(100), + reconnect_delay: Duration::from_millis(0), + delivery_params: MessageDeliveryParams { + max_unrewarded_relayer_entries_at_target: 4, + max_unconfirmed_nonces_at_target: 4, + max_messages_in_single_batch: 4, + max_messages_weight_in_single_batch: Weight::from_ref_time(4), + max_messages_size_in_single_batch: 4, + }, + }, + source_client, + target_client, + MetricsParams::disabled(), + exit_signal, + ) + .await; + let result = data.lock().clone(); + result + }) + } + + #[test] + fn message_lane_loop_is_able_to_recover_from_connection_errors() { + // with this configuration, source client will return Err, making source client + // reconnect. Then the target client will fail with Err + reconnect. Then we finally + // able to deliver messages. + let (exit_sender, exit_receiver) = unbounded(); + let result = run_loop_test( + TestClientData { + is_source_fails: true, + source_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: HeaderId(0, 0), + actual_best_finalized_peer_at_best_self: HeaderId(0, 0), + }, + source_latest_generated_nonce: 1, + target_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: HeaderId(0, 0), + actual_best_finalized_peer_at_best_self: HeaderId(0, 0), + }, + target_latest_received_nonce: 0, + ..Default::default() + }, + Arc::new(|data: &mut TestClientData| { + if data.is_source_reconnected { + data.is_source_fails = false; + data.is_target_fails = true; + } + }), + Arc::new(|_| {}), + Arc::new(move |data: &mut TestClientData| { + if data.is_target_reconnected { + data.is_target_fails = false; + } + if data.target_state.best_finalized_peer_at_best_self.0 < 10 { + data.target_state.best_finalized_peer_at_best_self = HeaderId( + data.target_state.best_finalized_peer_at_best_self.0 + 1, + data.target_state.best_finalized_peer_at_best_self.0 + 1, + ); + } + if !data.submitted_messages_proofs.is_empty() { + exit_sender.unbounded_send(()).unwrap(); + } + }), + Arc::new(|_| {}), + exit_receiver.into_future().map(|(_, _)| ()), + ); + + assert_eq!(result.submitted_messages_proofs, vec![(1..=1, None)],); + } + + #[test] + fn message_lane_loop_is_able_to_recover_from_race_stall() { + // with this configuration, both source and target clients will lose their transactions => + // reconnect will happen + let (source_exit_sender, exit_receiver) = unbounded(); + let target_exit_sender = source_exit_sender.clone(); + let result = run_loop_test( + TestClientData { + source_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: HeaderId(0, 0), + actual_best_finalized_peer_at_best_self: HeaderId(0, 0), + }, + source_latest_generated_nonce: 1, + source_tracked_transaction_status: TrackedTransactionStatus::Lost, + target_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: HeaderId(0, 0), + actual_best_finalized_peer_at_best_self: HeaderId(0, 0), + }, + target_latest_received_nonce: 0, + target_tracked_transaction_status: TrackedTransactionStatus::Lost, + ..Default::default() + }, + Arc::new(move |data: &mut TestClientData| { + if data.is_source_reconnected { + data.source_tracked_transaction_status = + TrackedTransactionStatus::Finalized(Default::default()); + } + if data.is_source_reconnected && data.is_target_reconnected { + source_exit_sender.unbounded_send(()).unwrap(); + } + }), + Arc::new(|_| {}), + Arc::new(move |data: &mut TestClientData| { + if data.is_target_reconnected { + data.target_tracked_transaction_status = + TrackedTransactionStatus::Finalized(Default::default()); + } + if data.is_source_reconnected && data.is_target_reconnected { + target_exit_sender.unbounded_send(()).unwrap(); + } + }), + Arc::new(|_| {}), + exit_receiver.into_future().map(|(_, _)| ()), + ); + + assert!(result.is_source_reconnected); + } + + #[test] + fn message_lane_loop_is_able_to_recover_from_unsuccessful_transaction() { + // with this configuration, both source and target clients will mine their transactions, but + // their corresponding nonce won't be udpated => reconnect will happen + let (exit_sender, exit_receiver) = unbounded(); + let result = run_loop_test( + TestClientData { + source_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: HeaderId(0, 0), + actual_best_finalized_peer_at_best_self: HeaderId(0, 0), + }, + source_latest_generated_nonce: 1, + target_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: HeaderId(0, 0), + actual_best_finalized_peer_at_best_self: HeaderId(0, 0), + }, + target_latest_received_nonce: 0, + ..Default::default() + }, + Arc::new(move |data: &mut TestClientData| { + // blocks are produced on every tick + data.source_state.best_self = + HeaderId(data.source_state.best_self.0 + 1, data.source_state.best_self.1 + 1); + data.source_state.best_finalized_self = data.source_state.best_self; + // syncing target headers -> source chain + if let Some(last_requirement) = data.target_to_source_header_requirements.last() { + if *last_requirement != data.source_state.best_finalized_peer_at_best_self { + data.source_state.best_finalized_peer_at_best_self = *last_requirement; + } + } + }), + Arc::new(move |data: &mut TestClientData| { + // if it is the first time we're submitting delivery proof, let's revert changes + // to source status => then the delivery confirmation transaction is "finalized", + // but the state is not altered + if data.submitted_messages_receiving_proofs.len() == 1 { + data.source_latest_confirmed_received_nonce = 0; + } + }), + Arc::new(move |data: &mut TestClientData| { + // blocks are produced on every tick + data.target_state.best_self = + HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.1 + 1); + data.target_state.best_finalized_self = data.target_state.best_self; + // syncing source headers -> target chain + if let Some(last_requirement) = data.source_to_target_header_requirements.last() { + if *last_requirement != data.target_state.best_finalized_peer_at_best_self { + data.target_state.best_finalized_peer_at_best_self = *last_requirement; + } + } + // if source has received all messages receiving confirmations => stop + if data.source_latest_confirmed_received_nonce == 1 { + exit_sender.unbounded_send(()).unwrap(); + } + }), + Arc::new(move |data: &mut TestClientData| { + // if it is the first time we're submitting messages proof, let's revert changes + // to target status => then the messages delivery transaction is "finalized", but + // the state is not altered + if data.submitted_messages_proofs.len() == 1 { + data.target_latest_received_nonce = 0; + data.target_latest_confirmed_received_nonce = 0; + } + }), + exit_receiver.into_future().map(|(_, _)| ()), + ); + + assert!(result.is_source_reconnected); + assert_eq!(result.submitted_messages_proofs.len(), 2); + assert_eq!(result.submitted_messages_receiving_proofs.len(), 2); + } + + #[test] + fn message_lane_loop_works() { + let (exit_sender, exit_receiver) = unbounded(); + let result = run_loop_test( + TestClientData { + source_state: ClientState { + best_self: HeaderId(10, 10), + best_finalized_self: HeaderId(10, 10), + best_finalized_peer_at_best_self: HeaderId(0, 0), + actual_best_finalized_peer_at_best_self: HeaderId(0, 0), + }, + source_latest_generated_nonce: 10, + target_state: ClientState { + best_self: HeaderId(0, 0), + best_finalized_self: HeaderId(0, 0), + best_finalized_peer_at_best_self: HeaderId(0, 0), + actual_best_finalized_peer_at_best_self: HeaderId(0, 0), + }, + target_latest_received_nonce: 0, + ..Default::default() + }, + Arc::new(|data: &mut TestClientData| { + // blocks are produced on every tick + data.source_state.best_self = + HeaderId(data.source_state.best_self.0 + 1, data.source_state.best_self.1 + 1); + data.source_state.best_finalized_self = data.source_state.best_self; + // headers relay must only be started when we need new target headers at source node + if data.target_to_source_header_required.is_some() { + assert!( + data.source_state.best_finalized_peer_at_best_self.0 < + data.target_state.best_self.0 + ); + data.target_to_source_header_required = None; + } + // syncing target headers -> source chain + if let Some(last_requirement) = data.target_to_source_header_requirements.last() { + if *last_requirement != data.source_state.best_finalized_peer_at_best_self { + data.source_state.best_finalized_peer_at_best_self = *last_requirement; + } + } + }), + Arc::new(|_| {}), + Arc::new(move |data: &mut TestClientData| { + // blocks are produced on every tick + data.target_state.best_self = + HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.1 + 1); + data.target_state.best_finalized_self = data.target_state.best_self; + // headers relay must only be started when we need new source headers at target node + if data.source_to_target_header_required.is_some() { + assert!( + data.target_state.best_finalized_peer_at_best_self.0 < + data.source_state.best_self.0 + ); + data.source_to_target_header_required = None; + } + // syncing source headers -> target chain + if let Some(last_requirement) = data.source_to_target_header_requirements.last() { + if *last_requirement != data.target_state.best_finalized_peer_at_best_self { + data.target_state.best_finalized_peer_at_best_self = *last_requirement; + } + } + // if source has received all messages receiving confirmations => stop + if data.source_latest_confirmed_received_nonce == 10 { + exit_sender.unbounded_send(()).unwrap(); + } + }), + Arc::new(|_| {}), + exit_receiver.into_future().map(|(_, _)| ()), + ); + + // there are no strict restrictions on when reward confirmation should come + // (because `max_unconfirmed_nonces_at_target` is `100` in tests and this confirmation + // depends on the state of both clients) + // => we do not check it here + assert_eq!(result.submitted_messages_proofs[0].0, 1..=4); + assert_eq!(result.submitted_messages_proofs[1].0, 5..=8); + assert_eq!(result.submitted_messages_proofs[2].0, 9..=10); + assert!(!result.submitted_messages_receiving_proofs.is_empty()); + + // check that we have at least once required new source->target or target->source headers + assert!(!result.target_to_source_header_requirements.is_empty()); + assert!(!result.source_to_target_header_requirements.is_empty()); + } +} diff --git a/relays/messages/src/message_race_delivery.rs b/relays/messages/src/message_race_delivery.rs new file mode 100644 index 00000000000..b49a05dac5c --- /dev/null +++ b/relays/messages/src/message_race_delivery.rs @@ -0,0 +1,984 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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. + +//! Message delivery race delivers proof-of-messages from "lane.source" to "lane.target". + +use std::{collections::VecDeque, marker::PhantomData, ops::RangeInclusive}; + +use async_trait::async_trait; +use futures::stream::FusedStream; + +use bp_messages::{MessageNonce, UnrewardedRelayersState, Weight}; +use relay_utils::FailedClient; + +use crate::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::{ + MessageDeliveryParams, MessageDetailsMap, MessageProofParameters, NoncesSubmitArtifacts, + SourceClient as MessageLaneSourceClient, SourceClientState, + TargetClient as MessageLaneTargetClient, TargetClientState, + }, + message_race_limits::{MessageRaceLimits, RelayMessagesBatchReference}, + message_race_loop::{ + MessageRace, NoncesRange, RaceState, RaceStrategy, SourceClient, SourceClientNonces, + TargetClient, TargetClientNonces, + }, + message_race_strategy::BasicStrategy, + metrics::MessageLaneLoopMetrics, +}; + +/// Run message delivery race. +pub async fn run( + source_client: impl MessageLaneSourceClient

, + source_state_updates: impl FusedStream>, + target_client: impl MessageLaneTargetClient

, + target_state_updates: impl FusedStream>, + metrics_msg: Option, + params: MessageDeliveryParams, +) -> Result<(), FailedClient> { + crate::message_race_loop::run( + MessageDeliveryRaceSource { + client: source_client.clone(), + metrics_msg: metrics_msg.clone(), + _phantom: Default::default(), + }, + source_state_updates, + MessageDeliveryRaceTarget { + client: target_client.clone(), + metrics_msg: metrics_msg.clone(), + _phantom: Default::default(), + }, + target_state_updates, + MessageDeliveryStrategy:: { + lane_source_client: source_client, + lane_target_client: target_client, + max_unrewarded_relayer_entries_at_target: params + .max_unrewarded_relayer_entries_at_target, + max_unconfirmed_nonces_at_target: params.max_unconfirmed_nonces_at_target, + max_messages_in_single_batch: params.max_messages_in_single_batch, + max_messages_weight_in_single_batch: params.max_messages_weight_in_single_batch, + max_messages_size_in_single_batch: params.max_messages_size_in_single_batch, + latest_confirmed_nonces_at_source: VecDeque::new(), + target_nonces: None, + strategy: BasicStrategy::new(), + metrics_msg, + }, + ) + .await +} + +/// Message delivery race. +struct MessageDeliveryRace

(std::marker::PhantomData

); + +impl MessageRace for MessageDeliveryRace

{ + type SourceHeaderId = SourceHeaderIdOf

; + type TargetHeaderId = TargetHeaderIdOf

; + + type MessageNonce = MessageNonce; + type Proof = P::MessagesProof; + + fn source_name() -> String { + format!("{}::MessagesDelivery", P::SOURCE_NAME) + } + + fn target_name() -> String { + format!("{}::MessagesDelivery", P::TARGET_NAME) + } +} + +/// Message delivery race source, which is a source of the lane. +struct MessageDeliveryRaceSource { + client: C, + metrics_msg: Option, + _phantom: PhantomData

, +} + +#[async_trait] +impl SourceClient> for MessageDeliveryRaceSource +where + P: MessageLane, + C: MessageLaneSourceClient

, +{ + type Error = C::Error; + type NoncesRange = MessageDetailsMap; + type ProofParameters = MessageProofParameters; + + async fn nonces( + &self, + at_block: SourceHeaderIdOf

, + prev_latest_nonce: MessageNonce, + ) -> Result<(SourceHeaderIdOf

, SourceClientNonces), Self::Error> { + let (at_block, latest_generated_nonce) = + self.client.latest_generated_nonce(at_block).await?; + let (at_block, latest_confirmed_nonce) = + self.client.latest_confirmed_received_nonce(at_block).await?; + + if let Some(metrics_msg) = self.metrics_msg.as_ref() { + metrics_msg.update_source_latest_generated_nonce::

(latest_generated_nonce); + metrics_msg.update_source_latest_confirmed_nonce::

(latest_confirmed_nonce); + } + + let new_nonces = if latest_generated_nonce > prev_latest_nonce { + self.client + .generated_message_details( + at_block.clone(), + prev_latest_nonce + 1..=latest_generated_nonce, + ) + .await? + } else { + MessageDetailsMap::new() + }; + + Ok(( + at_block, + SourceClientNonces { new_nonces, confirmed_nonce: Some(latest_confirmed_nonce) }, + )) + } + + async fn generate_proof( + &self, + at_block: SourceHeaderIdOf

, + nonces: RangeInclusive, + proof_parameters: Self::ProofParameters, + ) -> Result<(SourceHeaderIdOf

, RangeInclusive, P::MessagesProof), Self::Error> + { + self.client.prove_messages(at_block, nonces, proof_parameters).await + } +} + +/// Message delivery race target, which is a target of the lane. +struct MessageDeliveryRaceTarget { + client: C, + metrics_msg: Option, + _phantom: PhantomData

, +} + +#[async_trait] +impl TargetClient> for MessageDeliveryRaceTarget +where + P: MessageLane, + C: MessageLaneTargetClient

, +{ + type Error = C::Error; + type TargetNoncesData = DeliveryRaceTargetNoncesData; + type TransactionTracker = C::TransactionTracker; + + async fn require_source_header(&self, id: SourceHeaderIdOf

) { + self.client.require_source_header_on_target(id).await + } + + async fn nonces( + &self, + at_block: TargetHeaderIdOf

, + update_metrics: bool, + ) -> Result<(TargetHeaderIdOf

, TargetClientNonces), Self::Error> + { + let (at_block, latest_received_nonce) = self.client.latest_received_nonce(at_block).await?; + let (at_block, latest_confirmed_nonce) = + self.client.latest_confirmed_received_nonce(at_block).await?; + let (at_block, unrewarded_relayers) = + self.client.unrewarded_relayers_state(at_block).await?; + + if update_metrics { + if let Some(metrics_msg) = self.metrics_msg.as_ref() { + metrics_msg.update_target_latest_received_nonce::

(latest_received_nonce); + metrics_msg.update_target_latest_confirmed_nonce::

(latest_confirmed_nonce); + } + } + + Ok(( + at_block, + TargetClientNonces { + latest_nonce: latest_received_nonce, + nonces_data: DeliveryRaceTargetNoncesData { + confirmed_nonce: latest_confirmed_nonce, + unrewarded_relayers, + }, + }, + )) + } + + async fn submit_proof( + &self, + generated_at_block: SourceHeaderIdOf

, + nonces: RangeInclusive, + proof: P::MessagesProof, + ) -> Result, Self::Error> { + self.client.submit_messages_proof(generated_at_block, nonces, proof).await + } +} + +/// Additional nonces data from the target client used by message delivery race. +#[derive(Debug, Clone)] +struct DeliveryRaceTargetNoncesData { + /// The latest nonce that we know: (1) has been delivered to us (2) has been confirmed + /// back to the source node (by confirmations race) and (3) relayer has received + /// reward for (and this has been confirmed by the message delivery race). + confirmed_nonce: MessageNonce, + /// State of the unrewarded relayers set at the target node. + unrewarded_relayers: UnrewardedRelayersState, +} + +/// Messages delivery strategy. +struct MessageDeliveryStrategy { + /// The client that is connected to the message lane source node. + lane_source_client: SC, + /// The client that is connected to the message lane target node. + lane_target_client: TC, + /// Maximal unrewarded relayer entries at target client. + max_unrewarded_relayer_entries_at_target: MessageNonce, + /// Maximal unconfirmed nonces at target client. + max_unconfirmed_nonces_at_target: MessageNonce, + /// Maximal number of messages in the single delivery transaction. + max_messages_in_single_batch: MessageNonce, + /// Maximal cumulative messages weight in the single delivery transaction. + max_messages_weight_in_single_batch: Weight, + /// Maximal messages size in the single delivery transaction. + max_messages_size_in_single_batch: u32, + /// Latest confirmed nonces at the source client + the header id where we have first met this + /// nonce. + latest_confirmed_nonces_at_source: VecDeque<(SourceHeaderIdOf

, MessageNonce)>, + /// Target nonces from the source client. + target_nonces: Option>, + /// Basic delivery strategy. + strategy: MessageDeliveryStrategyBase

, + /// Message lane metrics. + metrics_msg: Option, +} + +type MessageDeliveryStrategyBase

= BasicStrategy< +

::SourceHeaderNumber, +

::SourceHeaderHash, +

::TargetHeaderNumber, +

::TargetHeaderHash, + MessageDetailsMap<

::SourceChainBalance>, +

::MessagesProof, +>; + +impl std::fmt::Debug for MessageDeliveryStrategy { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct("MessageDeliveryStrategy") + .field( + "max_unrewarded_relayer_entries_at_target", + &self.max_unrewarded_relayer_entries_at_target, + ) + .field("max_unconfirmed_nonces_at_target", &self.max_unconfirmed_nonces_at_target) + .field("max_messages_in_single_batch", &self.max_messages_in_single_batch) + .field("max_messages_weight_in_single_batch", &self.max_messages_weight_in_single_batch) + .field("max_messages_size_in_single_batch", &self.max_messages_size_in_single_batch) + .field("latest_confirmed_nonces_at_source", &self.latest_confirmed_nonces_at_source) + .field("target_nonces", &self.target_nonces) + .field("strategy", &self.strategy) + .finish() + } +} + +impl MessageDeliveryStrategy { + /// Returns total weight of all undelivered messages. + fn total_queued_dispatch_weight(&self) -> Weight { + self.strategy + .source_queue() + .iter() + .flat_map(|(_, range)| range.values().map(|details| details.dispatch_weight)) + .fold(Weight::zero(), |total, weight| total.saturating_add(weight)) + } +} + +#[async_trait] +impl RaceStrategy, TargetHeaderIdOf

, P::MessagesProof> + for MessageDeliveryStrategy +where + P: MessageLane, + SC: MessageLaneSourceClient

, + TC: MessageLaneTargetClient

, +{ + type SourceNoncesRange = MessageDetailsMap; + type ProofParameters = MessageProofParameters; + type TargetNoncesData = DeliveryRaceTargetNoncesData; + + fn is_empty(&self) -> bool { + self.strategy.is_empty() + } + + fn required_source_header_at_target( + &self, + current_best: &SourceHeaderIdOf

, + ) -> Option> { + let header_required_for_messages_delivery = + self.strategy.required_source_header_at_target(current_best); + let header_required_for_reward_confirmations_delivery = + self.latest_confirmed_nonces_at_source.back().map(|(id, _)| id.clone()); + match ( + header_required_for_messages_delivery, + header_required_for_reward_confirmations_delivery, + ) { + (Some(id1), Some(id2)) => Some(if id1.0 > id2.0 { id1 } else { id2 }), + (a, b) => a.or(b), + } + } + + fn best_at_source(&self) -> Option { + self.strategy.best_at_source() + } + + fn best_at_target(&self) -> Option { + self.strategy.best_at_target() + } + + fn source_nonces_updated( + &mut self, + at_block: SourceHeaderIdOf

, + nonces: SourceClientNonces, + ) { + if let Some(confirmed_nonce) = nonces.confirmed_nonce { + let is_confirmed_nonce_updated = self + .latest_confirmed_nonces_at_source + .back() + .map(|(_, prev_nonce)| *prev_nonce != confirmed_nonce) + .unwrap_or(true); + if is_confirmed_nonce_updated { + self.latest_confirmed_nonces_at_source + .push_back((at_block.clone(), confirmed_nonce)); + } + } + self.strategy.source_nonces_updated(at_block, nonces) + } + + fn best_target_nonces_updated( + &mut self, + nonces: TargetClientNonces, + race_state: &mut RaceState, TargetHeaderIdOf

, P::MessagesProof>, + ) { + // best target nonces must always be ge than finalized target nonces + let mut target_nonces = self.target_nonces.take().unwrap_or_else(|| nonces.clone()); + target_nonces.nonces_data = nonces.nonces_data.clone(); + target_nonces.latest_nonce = std::cmp::max(target_nonces.latest_nonce, nonces.latest_nonce); + self.target_nonces = Some(target_nonces); + + self.strategy.best_target_nonces_updated( + TargetClientNonces { latest_nonce: nonces.latest_nonce, nonces_data: () }, + race_state, + ) + } + + fn finalized_target_nonces_updated( + &mut self, + nonces: TargetClientNonces, + race_state: &mut RaceState, TargetHeaderIdOf

, P::MessagesProof>, + ) { + if let Some(ref best_finalized_source_header_id_at_best_target) = + race_state.best_finalized_source_header_id_at_best_target + { + let oldest_header_number_to_keep = best_finalized_source_header_id_at_best_target.0; + while self + .latest_confirmed_nonces_at_source + .front() + .map(|(id, _)| id.0 < oldest_header_number_to_keep) + .unwrap_or(false) + { + self.latest_confirmed_nonces_at_source.pop_front(); + } + } + + if let Some(ref mut target_nonces) = self.target_nonces { + target_nonces.latest_nonce = + std::cmp::max(target_nonces.latest_nonce, nonces.latest_nonce); + } + + self.strategy.finalized_target_nonces_updated( + TargetClientNonces { latest_nonce: nonces.latest_nonce, nonces_data: () }, + race_state, + ) + } + + async fn select_nonces_to_deliver( + &mut self, + race_state: RaceState, TargetHeaderIdOf

, P::MessagesProof>, + ) -> Option<(RangeInclusive, Self::ProofParameters)> { + let best_finalized_source_header_id_at_best_target = + race_state.best_finalized_source_header_id_at_best_target.clone()?; + let latest_confirmed_nonce_at_source = self + .latest_confirmed_nonces_at_source + .iter() + .take_while(|(id, _)| id.0 <= best_finalized_source_header_id_at_best_target.0) + .last() + .map(|(_, nonce)| *nonce)?; + let target_nonces = self.target_nonces.as_ref()?; + + // There's additional condition in the message delivery race: target would reject messages + // if there are too much unconfirmed messages at the inbound lane. + + // The receiving race is responsible to deliver confirmations back to the source chain. So + // if there's a lot of unconfirmed messages, let's wait until it'll be able to do its job. + let latest_received_nonce_at_target = target_nonces.latest_nonce; + let confirmations_missing = + latest_received_nonce_at_target.checked_sub(latest_confirmed_nonce_at_source); + match confirmations_missing { + Some(confirmations_missing) + if confirmations_missing >= self.max_unconfirmed_nonces_at_target => + { + log::debug!( + target: "bridge", + "Cannot deliver any more messages from {} to {}. Too many unconfirmed nonces \ + at target: target.latest_received={:?}, source.latest_confirmed={:?}, max={:?}", + MessageDeliveryRace::

::source_name(), + MessageDeliveryRace::

::target_name(), + latest_received_nonce_at_target, + latest_confirmed_nonce_at_source, + self.max_unconfirmed_nonces_at_target, + ); + + return None + }, + _ => (), + } + + // Ok - we may have new nonces to deliver. But target may still reject new messages, because + // we haven't notified it that (some) messages have been confirmed. So we may want to + // include updated `source.latest_confirmed` in the proof. + // + // Important note: we're including outbound state lane proof whenever there are unconfirmed + // nonces on the target chain. Other strategy is to include it only if it's absolutely + // necessary. + let latest_confirmed_nonce_at_target = target_nonces.nonces_data.confirmed_nonce; + let outbound_state_proof_required = + latest_confirmed_nonce_at_target < latest_confirmed_nonce_at_source; + + // The target node would also reject messages if there are too many entries in the + // "unrewarded relayers" set. If we are unable to prove new rewards to the target node, then + // we should wait for confirmations race. + let unrewarded_relayer_entries_limit_reached = + target_nonces.nonces_data.unrewarded_relayers.unrewarded_relayer_entries >= + self.max_unrewarded_relayer_entries_at_target; + if unrewarded_relayer_entries_limit_reached { + // so there are already too many unrewarded relayer entries in the set + // + // => check if we can prove enough rewards. If not, we should wait for more rewards to + // be paid + let number_of_rewards_being_proved = + latest_confirmed_nonce_at_source.saturating_sub(latest_confirmed_nonce_at_target); + let enough_rewards_being_proved = number_of_rewards_being_proved >= + target_nonces.nonces_data.unrewarded_relayers.messages_in_oldest_entry; + if !enough_rewards_being_proved { + return None + } + } + + // If we're here, then the confirmations race did its job && sending side now knows that + // messages have been delivered. Now let's select nonces that we want to deliver. + // + // We may deliver at most: + // + // max_unconfirmed_nonces_at_target - (latest_received_nonce_at_target - + // latest_confirmed_nonce_at_target) + // + // messages in the batch. But since we're including outbound state proof in the batch, then + // it may be increased to: + // + // max_unconfirmed_nonces_at_target - (latest_received_nonce_at_target - + // latest_confirmed_nonce_at_source) + let future_confirmed_nonce_at_target = if outbound_state_proof_required { + latest_confirmed_nonce_at_source + } else { + latest_confirmed_nonce_at_target + }; + let max_nonces = latest_received_nonce_at_target + .checked_sub(future_confirmed_nonce_at_target) + .and_then(|diff| self.max_unconfirmed_nonces_at_target.checked_sub(diff)) + .unwrap_or_default(); + let max_nonces = std::cmp::min(max_nonces, self.max_messages_in_single_batch); + let max_messages_weight_in_single_batch = self.max_messages_weight_in_single_batch; + let max_messages_size_in_single_batch = self.max_messages_size_in_single_batch; + let lane_source_client = self.lane_source_client.clone(); + let lane_target_client = self.lane_target_client.clone(); + + let maximal_source_queue_index = + self.strategy.maximal_available_source_queue_index(race_state)?; + let previous_total_dispatch_weight = self.total_queued_dispatch_weight(); + let source_queue = self.strategy.source_queue(); + + let reference = RelayMessagesBatchReference { + max_messages_in_this_batch: max_nonces, + max_messages_weight_in_single_batch, + max_messages_size_in_single_batch, + lane_source_client: lane_source_client.clone(), + lane_target_client: lane_target_client.clone(), + nonces_queue: source_queue.clone(), + nonces_queue_range: 0..maximal_source_queue_index + 1, + metrics: self.metrics_msg.clone(), + }; + + let range_end = MessageRaceLimits::decide(reference).await?; + + let range_begin = source_queue[0].1.begin(); + let selected_nonces = range_begin..=range_end; + self.strategy.remove_le_nonces_from_source_queue(range_end); + + let new_total_dispatch_weight = self.total_queued_dispatch_weight(); + let dispatch_weight = previous_total_dispatch_weight - new_total_dispatch_weight; + + Some(( + selected_nonces, + MessageProofParameters { outbound_state_proof_required, dispatch_weight }, + )) + } +} + +impl NoncesRange for MessageDetailsMap { + fn begin(&self) -> MessageNonce { + self.keys().next().cloned().unwrap_or_default() + } + + fn end(&self) -> MessageNonce { + self.keys().next_back().cloned().unwrap_or_default() + } + + fn greater_than(mut self, nonce: MessageNonce) -> Option { + let gte = self.split_off(&(nonce + 1)); + if gte.is_empty() { + None + } else { + Some(gte) + } + } +} + +#[cfg(test)] +mod tests { + use crate::message_lane_loop::{ + tests::{ + header_id, TestMessageLane, TestMessagesProof, TestSourceChainBalance, + TestSourceClient, TestSourceHeaderId, TestTargetClient, TestTargetHeaderId, + }, + MessageDetails, + }; + + use super::*; + + const DEFAULT_DISPATCH_WEIGHT: Weight = Weight::from_ref_time(1); + const DEFAULT_SIZE: u32 = 1; + + type TestRaceState = RaceState; + type TestStrategy = + MessageDeliveryStrategy; + + fn source_nonces( + new_nonces: RangeInclusive, + confirmed_nonce: MessageNonce, + reward: TestSourceChainBalance, + ) -> SourceClientNonces> { + SourceClientNonces { + new_nonces: new_nonces + .into_iter() + .map(|nonce| { + ( + nonce, + MessageDetails { + dispatch_weight: DEFAULT_DISPATCH_WEIGHT, + size: DEFAULT_SIZE, + reward, + }, + ) + }) + .into_iter() + .collect(), + confirmed_nonce: Some(confirmed_nonce), + } + } + + fn prepare_strategy() -> (TestRaceState, TestStrategy) { + let mut race_state = RaceState { + best_finalized_source_header_id_at_source: Some(header_id(1)), + best_finalized_source_header_id_at_best_target: Some(header_id(1)), + best_target_header_id: Some(header_id(1)), + best_finalized_target_header_id: Some(header_id(1)), + nonces_to_submit: None, + nonces_submitted: None, + }; + + let mut race_strategy = TestStrategy { + max_unrewarded_relayer_entries_at_target: 4, + max_unconfirmed_nonces_at_target: 4, + max_messages_in_single_batch: 4, + max_messages_weight_in_single_batch: Weight::from_ref_time(4), + max_messages_size_in_single_batch: 4, + latest_confirmed_nonces_at_source: vec![(header_id(1), 19)].into_iter().collect(), + lane_source_client: TestSourceClient::default(), + lane_target_client: TestTargetClient::default(), + metrics_msg: None, + target_nonces: Some(TargetClientNonces { + latest_nonce: 19, + nonces_data: DeliveryRaceTargetNoncesData { + confirmed_nonce: 19, + unrewarded_relayers: UnrewardedRelayersState { + unrewarded_relayer_entries: 0, + messages_in_oldest_entry: 0, + total_messages: 0, + last_delivered_nonce: 0, + }, + }, + }), + strategy: BasicStrategy::new(), + }; + + race_strategy + .strategy + .source_nonces_updated(header_id(1), source_nonces(20..=23, 19, 0)); + + let target_nonces = TargetClientNonces { latest_nonce: 19, nonces_data: () }; + race_strategy + .strategy + .best_target_nonces_updated(target_nonces.clone(), &mut race_state); + race_strategy + .strategy + .finalized_target_nonces_updated(target_nonces, &mut race_state); + + (race_state, race_strategy) + } + + fn proof_parameters(state_required: bool, weight: u32) -> MessageProofParameters { + MessageProofParameters { + outbound_state_proof_required: state_required, + dispatch_weight: Weight::from_ref_time(weight as u64), + } + } + + #[test] + fn weights_map_works_as_nonces_range() { + fn build_map( + range: RangeInclusive, + ) -> MessageDetailsMap { + range + .map(|idx| { + ( + idx, + MessageDetails { + dispatch_weight: Weight::from_ref_time(idx), + size: idx as _, + reward: idx as _, + }, + ) + }) + .collect() + } + + let map = build_map(20..=30); + + assert_eq!(map.begin(), 20); + assert_eq!(map.end(), 30); + assert_eq!(map.clone().greater_than(10), Some(build_map(20..=30))); + assert_eq!(map.clone().greater_than(19), Some(build_map(20..=30))); + assert_eq!(map.clone().greater_than(20), Some(build_map(21..=30))); + assert_eq!(map.clone().greater_than(25), Some(build_map(26..=30))); + assert_eq!(map.clone().greater_than(29), Some(build_map(30..=30))); + assert_eq!(map.greater_than(30), None); + } + + #[async_std::test] + async fn message_delivery_strategy_selects_messages_to_deliver() { + let (state, mut strategy) = prepare_strategy(); + + // both sides are ready to relay new messages + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=23), proof_parameters(false, 4))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_selects_nothing_if_too_many_confirmations_missing() { + let (state, mut strategy) = prepare_strategy(); + + // if there are already `max_unconfirmed_nonces_at_target` messages on target, + // we need to wait until confirmations will be delivered by receiving race + strategy.latest_confirmed_nonces_at_source = vec![( + header_id(1), + strategy.target_nonces.as_ref().unwrap().latest_nonce - + strategy.max_unconfirmed_nonces_at_target, + )] + .into_iter() + .collect(); + assert_eq!(strategy.select_nonces_to_deliver(state).await, None); + } + + #[async_std::test] + async fn message_delivery_strategy_includes_outbound_state_proof_when_new_nonces_are_available() + { + let (state, mut strategy) = prepare_strategy(); + + // if there are new confirmed nonces on source, we want to relay this information + // to target to prune rewards queue + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = + prev_confirmed_nonce_at_source - 1; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=23), proof_parameters(true, 4))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_selects_nothing_if_there_are_too_many_unrewarded_relayers() { + let (state, mut strategy) = prepare_strategy(); + + // if there are already `max_unrewarded_relayer_entries_at_target` entries at target, + // we need to wait until rewards will be paid + { + let mut unrewarded_relayers = + &mut strategy.target_nonces.as_mut().unwrap().nonces_data.unrewarded_relayers; + unrewarded_relayers.unrewarded_relayer_entries = + strategy.max_unrewarded_relayer_entries_at_target; + unrewarded_relayers.messages_in_oldest_entry = 4; + } + assert_eq!(strategy.select_nonces_to_deliver(state).await, None); + } + + #[async_std::test] + async fn message_delivery_strategy_selects_nothing_if_proved_rewards_is_not_enough_to_remove_oldest_unrewarded_entry( + ) { + let (state, mut strategy) = prepare_strategy(); + + // if there are already `max_unrewarded_relayer_entries_at_target` entries at target, + // we need to prove at least `messages_in_oldest_entry` rewards + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + { + let mut nonces_data = &mut strategy.target_nonces.as_mut().unwrap().nonces_data; + nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1; + let mut unrewarded_relayers = &mut nonces_data.unrewarded_relayers; + unrewarded_relayers.unrewarded_relayer_entries = + strategy.max_unrewarded_relayer_entries_at_target; + unrewarded_relayers.messages_in_oldest_entry = 4; + } + assert_eq!(strategy.select_nonces_to_deliver(state).await, None); + } + + #[async_std::test] + async fn message_delivery_strategy_includes_outbound_state_proof_if_proved_rewards_is_enough() { + let (state, mut strategy) = prepare_strategy(); + + // if there are already `max_unrewarded_relayer_entries_at_target` entries at target, + // we need to prove at least `messages_in_oldest_entry` rewards + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + { + let mut nonces_data = &mut strategy.target_nonces.as_mut().unwrap().nonces_data; + nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 3; + let mut unrewarded_relayers = &mut nonces_data.unrewarded_relayers; + unrewarded_relayers.unrewarded_relayer_entries = + strategy.max_unrewarded_relayer_entries_at_target; + unrewarded_relayers.messages_in_oldest_entry = 3; + } + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=23), proof_parameters(true, 4))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_limits_batch_by_messages_weight() { + let (state, mut strategy) = prepare_strategy(); + + // not all queued messages may fit in the batch, because batch has max weight + strategy.max_messages_weight_in_single_batch = Weight::from_ref_time(3); + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_weight( + ) { + let (state, mut strategy) = prepare_strategy(); + + // first message doesn't fit in the batch, because it has weight (10) that overflows max + // weight (4) + strategy.strategy.source_queue_mut()[0].1.get_mut(&20).unwrap().dispatch_weight = + Weight::from_ref_time(10); + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=20), proof_parameters(false, 10))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_limits_batch_by_messages_size() { + let (state, mut strategy) = prepare_strategy(); + + // not all queued messages may fit in the batch, because batch has max weight + strategy.max_messages_size_in_single_batch = 3; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_size( + ) { + let (state, mut strategy) = prepare_strategy(); + + // first message doesn't fit in the batch, because it has weight (10) that overflows max + // weight (4) + strategy.strategy.source_queue_mut()[0].1.get_mut(&20).unwrap().size = 10; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=20), proof_parameters(false, 1))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_limits_batch_by_messages_count_when_there_is_upper_limit() { + let (state, mut strategy) = prepare_strategy(); + + // not all queued messages may fit in the batch, because batch has max number of messages + // limit + strategy.max_messages_in_single_batch = 3; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_limits_batch_by_messages_count_when_there_are_unconfirmed_nonces( + ) { + let (state, mut strategy) = prepare_strategy(); + + // 1 delivery confirmation from target to source is still missing, so we may only + // relay 3 new messages + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + strategy.latest_confirmed_nonces_at_source = + vec![(header_id(1), prev_confirmed_nonce_at_source - 1)].into_iter().collect(); + strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = + prev_confirmed_nonce_at_source - 1; + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + } + + #[async_std::test] + async fn message_delivery_strategy_waits_for_confirmed_nonce_header_to_appear_on_target() { + // 1 delivery confirmation from target to source is still missing, so we may deliver + // reward confirmation with our message delivery transaction. But the problem is that + // the reward has been paid at header 2 && this header is still unknown to target node. + // + // => so we can't deliver more than 3 messages + let (mut state, mut strategy) = prepare_strategy(); + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + strategy.latest_confirmed_nonces_at_source = vec![ + (header_id(1), prev_confirmed_nonce_at_source - 1), + (header_id(2), prev_confirmed_nonce_at_source), + ] + .into_iter() + .collect(); + strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = + prev_confirmed_nonce_at_source - 1; + state.best_finalized_source_header_id_at_best_target = Some(header_id(1)); + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=22), proof_parameters(false, 3))) + ); + + // the same situation, but the header 2 is known to the target node, so we may deliver + // reward confirmation + let (mut state, mut strategy) = prepare_strategy(); + let prev_confirmed_nonce_at_source = + strategy.latest_confirmed_nonces_at_source.back().unwrap().1; + strategy.latest_confirmed_nonces_at_source = vec![ + (header_id(1), prev_confirmed_nonce_at_source - 1), + (header_id(2), prev_confirmed_nonce_at_source), + ] + .into_iter() + .collect(); + strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = + prev_confirmed_nonce_at_source - 1; + state.best_finalized_source_header_id_at_source = Some(header_id(2)); + state.best_finalized_source_header_id_at_best_target = Some(header_id(2)); + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=23), proof_parameters(true, 4))) + ); + } + + #[async_std::test] + async fn source_header_is_required_when_confirmations_are_required() { + // let's prepare situation when: + // - all messages [20; 23] have been generated at source block#1; + let (mut state, mut strategy) = prepare_strategy(); + // + // - messages [20; 21] have been delivered, but messages [11; 20] can't be delivered because + // of unrewarded relayers vector capacity; + strategy.max_unconfirmed_nonces_at_target = 2; + assert_eq!( + strategy.select_nonces_to_deliver(state.clone()).await, + Some(((20..=21), proof_parameters(false, 2))) + ); + strategy.finalized_target_nonces_updated( + TargetClientNonces { + latest_nonce: 21, + nonces_data: DeliveryRaceTargetNoncesData { + confirmed_nonce: 19, + unrewarded_relayers: UnrewardedRelayersState { + unrewarded_relayer_entries: 2, + messages_in_oldest_entry: 2, + total_messages: 2, + last_delivered_nonce: 19, + }, + }, + }, + &mut state, + ); + assert_eq!(strategy.select_nonces_to_deliver(state).await, None); + // + // - messages [1; 10] receiving confirmation has been delivered at source block#2; + strategy.source_nonces_updated( + header_id(2), + SourceClientNonces { new_nonces: MessageDetailsMap::new(), confirmed_nonce: Some(21) }, + ); + // + // - so now we'll need to relay source block#11 to be able to accept messages [11; 20]. + assert_eq!(strategy.required_source_header_at_target(&header_id(1)), Some(header_id(2))); + } + + #[async_std::test] + async fn relayer_uses_flattened_view_of_the_source_queue_to_select_nonces() { + // Real scenario that has happened on test deployments: + // 1) relayer witnessed M1 at block 1 => it has separate entry in the `source_queue` + // 2) relayer witnessed M2 at block 2 => it has separate entry in the `source_queue` + // 3) if block 2 is known to the target node, then both M1 and M2 are selected for single + // delivery, even though weight(M1+M2) > larger than largest allowed weight + // + // This was happening because selector (`select_nonces_for_delivery_transaction`) has been + // called for every `source_queue` entry separately without preserving any context. + let (mut state, mut strategy) = prepare_strategy(); + let nonces = source_nonces(24..=25, 19, 0); + strategy.strategy.source_nonces_updated(header_id(2), nonces); + strategy.max_unrewarded_relayer_entries_at_target = 100; + strategy.max_unconfirmed_nonces_at_target = 100; + strategy.max_messages_in_single_batch = 5; + strategy.max_messages_weight_in_single_batch = Weight::from_ref_time(100); + strategy.max_messages_size_in_single_batch = 100; + state.best_finalized_source_header_id_at_best_target = Some(header_id(2)); + + assert_eq!( + strategy.select_nonces_to_deliver(state).await, + Some(((20..=24), proof_parameters(false, 5))) + ); + } +} diff --git a/relays/messages/src/message_race_limits.rs b/relays/messages/src/message_race_limits.rs new file mode 100644 index 00000000000..a28d9ba63da --- /dev/null +++ b/relays/messages/src/message_race_limits.rs @@ -0,0 +1,200 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! enforcement strategy + +use num_traits::Zero; +use std::ops::Range; + +use bp_messages::{MessageNonce, Weight}; + +use crate::{ + message_lane::MessageLane, + message_lane_loop::{ + MessageDetails, MessageDetailsMap, SourceClient as MessageLaneSourceClient, + TargetClient as MessageLaneTargetClient, + }, + message_race_loop::NoncesRange, + message_race_strategy::SourceRangesQueue, + metrics::MessageLaneLoopMetrics, +}; + +/// Reference data for participating in relay +pub struct RelayReference< + P: MessageLane, + SourceClient: MessageLaneSourceClient

, + TargetClient: MessageLaneTargetClient

, +> { + /// The client that is connected to the message lane source node. + pub lane_source_client: SourceClient, + /// The client that is connected to the message lane target node. + pub lane_target_client: TargetClient, + /// Metrics reference. + pub metrics: Option, + /// Messages size summary + pub selected_size: u32, + + /// Hard check begin nonce + pub hard_selected_begin_nonce: MessageNonce, + + /// Index by all ready nonces + pub index: usize, + /// Current nonce + pub nonce: MessageNonce, + /// Current nonce details + pub details: MessageDetails, +} + +/// Relay reference data +pub struct RelayMessagesBatchReference< + P: MessageLane, + SourceClient: MessageLaneSourceClient

, + TargetClient: MessageLaneTargetClient

, +> { + /// Maximal number of relayed messages in single delivery transaction. + pub max_messages_in_this_batch: MessageNonce, + /// Maximal cumulative dispatch weight of relayed messages in single delivery transaction. + pub max_messages_weight_in_single_batch: Weight, + /// Maximal cumulative size of relayed messages in single delivery transaction. + pub max_messages_size_in_single_batch: u32, + /// The client that is connected to the message lane source node. + pub lane_source_client: SourceClient, + /// The client that is connected to the message lane target node. + pub lane_target_client: TargetClient, + /// Metrics reference. + pub metrics: Option, + /// Source queue. + pub nonces_queue: SourceRangesQueue< + P::SourceHeaderHash, + P::SourceHeaderNumber, + MessageDetailsMap, + >, + /// Source queue range + pub nonces_queue_range: Range, +} + +/// Limits of the message race transactions. +#[derive(Clone)] +pub struct MessageRaceLimits; + +impl MessageRaceLimits { + pub async fn decide< + P: MessageLane, + SourceClient: MessageLaneSourceClient

, + TargetClient: MessageLaneTargetClient

, + >( + reference: RelayMessagesBatchReference, + ) -> Option { + let mut hard_selected_count = 0; + + let mut selected_weight = Weight::zero(); + let mut selected_count: MessageNonce = 0; + + let hard_selected_begin_nonce = + reference.nonces_queue[reference.nonces_queue_range.start].1.begin(); + + // relay reference + let mut relay_reference = RelayReference { + lane_source_client: reference.lane_source_client.clone(), + lane_target_client: reference.lane_target_client.clone(), + metrics: reference.metrics.clone(), + + selected_size: 0, + + hard_selected_begin_nonce, + + index: 0, + nonce: 0, + details: MessageDetails { + dispatch_weight: Weight::zero(), + size: 0, + reward: P::SourceChainBalance::zero(), + }, + }; + + let all_ready_nonces = reference + .nonces_queue + .range(reference.nonces_queue_range.clone()) + .flat_map(|(_, ready_nonces)| ready_nonces.iter()) + .enumerate(); + for (index, (nonce, details)) in all_ready_nonces { + relay_reference.index = index; + relay_reference.nonce = *nonce; + relay_reference.details = *details; + + // Since we (hopefully) have some reserves in `max_messages_weight_in_single_batch` + // and `max_messages_size_in_single_batch`, we may still try to submit transaction + // with single message if message overflows these limits. The worst case would be if + // transaction will be rejected by the target runtime, but at least we have tried. + + // limit messages in the batch by weight + let new_selected_weight = match selected_weight.checked_add(&details.dispatch_weight) { + Some(new_selected_weight) + if new_selected_weight + .all_lte(reference.max_messages_weight_in_single_batch) => + new_selected_weight, + new_selected_weight if selected_count == 0 => { + log::warn!( + target: "bridge", + "Going to submit message delivery transaction with declared dispatch \ + weight {:?} that overflows maximal configured weight {}", + new_selected_weight, + reference.max_messages_weight_in_single_batch, + ); + new_selected_weight.unwrap_or(Weight::MAX) + }, + _ => break, + }; + + // limit messages in the batch by size + let new_selected_size = match relay_reference.selected_size.checked_add(details.size) { + Some(new_selected_size) + if new_selected_size <= reference.max_messages_size_in_single_batch => + new_selected_size, + new_selected_size if selected_count == 0 => { + log::warn!( + target: "bridge", + "Going to submit message delivery transaction with message \ + size {:?} that overflows maximal configured size {}", + new_selected_size, + reference.max_messages_size_in_single_batch, + ); + new_selected_size.unwrap_or(u32::MAX) + }, + _ => break, + }; + + // limit number of messages in the batch + let new_selected_count = selected_count + 1; + if new_selected_count > reference.max_messages_in_this_batch { + break + } + relay_reference.selected_size = new_selected_size; + + hard_selected_count = index + 1; + selected_weight = new_selected_weight; + selected_count = new_selected_count; + } + + if hard_selected_count != 0 { + let selected_max_nonce = + hard_selected_begin_nonce + hard_selected_count as MessageNonce - 1; + Some(selected_max_nonce) + } else { + None + } + } +} diff --git a/relays/messages/src/message_race_loop.rs b/relays/messages/src/message_race_loop.rs new file mode 100644 index 00000000000..4f59b635ae6 --- /dev/null +++ b/relays/messages/src/message_race_loop.rs @@ -0,0 +1,663 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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. + +//! Loop that is serving single race within message lane. This could be +//! message delivery race, receiving confirmations race or processing +//! confirmations race. +//! +//! The idea of the race is simple - we have `nonce`-s on source and target +//! nodes. We're trying to prove that the source node has this nonce (and +//! associated data - like messages, lane state, etc) to the target node by +//! generating and submitting proof. + +use crate::message_lane_loop::{ClientState, NoncesSubmitArtifacts}; + +use async_trait::async_trait; +use bp_messages::MessageNonce; +use futures::{ + future::FutureExt, + stream::{FusedStream, StreamExt}, +}; +use relay_utils::{ + process_future_result, retry_backoff, FailedClient, MaybeConnectionError, + TrackedTransactionStatus, TransactionTracker, +}; +use std::{ + fmt::Debug, + ops::RangeInclusive, + time::{Duration, Instant}, +}; + +/// One of races within lane. +pub trait MessageRace { + /// Header id of the race source. + type SourceHeaderId: Debug + Clone + PartialEq; + /// Header id of the race source. + type TargetHeaderId: Debug + Clone + PartialEq; + + /// Message nonce used in the race. + type MessageNonce: Debug + Clone; + /// Proof that is generated and delivered in this race. + type Proof: Debug + Clone; + + /// Name of the race source. + fn source_name() -> String; + /// Name of the race target. + fn target_name() -> String; +} + +/// State of race source client. +type SourceClientState

= + ClientState<

::SourceHeaderId,

::TargetHeaderId>; + +/// State of race target client. +type TargetClientState

= + ClientState<

::TargetHeaderId,

::SourceHeaderId>; + +/// Inclusive nonces range. +pub trait NoncesRange: Debug + Sized { + /// Get begin of the range. + fn begin(&self) -> MessageNonce; + /// Get end of the range. + fn end(&self) -> MessageNonce; + /// Returns new range with current range nonces that are greater than the passed `nonce`. + /// If there are no such nonces, `None` is returned. + fn greater_than(self, nonce: MessageNonce) -> Option; +} + +/// Nonces on the race source client. +#[derive(Debug, Clone)] +pub struct SourceClientNonces { + /// New nonces range known to the client. `New` here means all nonces generated after + /// `prev_latest_nonce` passed to the `SourceClient::nonces` method. + pub new_nonces: NoncesRange, + /// The latest nonce that is confirmed to the bridged client. This nonce only makes + /// sense in some races. In other races it is `None`. + pub confirmed_nonce: Option, +} + +/// Nonces on the race target client. +#[derive(Debug, Clone)] +pub struct TargetClientNonces { + /// The latest nonce that is known to the target client. + pub latest_nonce: MessageNonce, + /// Additional data from target node that may be used by the race. + pub nonces_data: TargetNoncesData, +} + +/// One of message lane clients, which is source client for the race. +#[async_trait] +pub trait SourceClient { + /// Type of error these clients returns. + type Error: std::fmt::Debug + MaybeConnectionError; + /// Type of nonces range returned by the source client. + type NoncesRange: NoncesRange; + /// Additional proof parameters required to generate proof. + type ProofParameters; + + /// Return nonces that are known to the source client. + async fn nonces( + &self, + at_block: P::SourceHeaderId, + prev_latest_nonce: MessageNonce, + ) -> Result<(P::SourceHeaderId, SourceClientNonces), Self::Error>; + /// Generate proof for delivering to the target client. + async fn generate_proof( + &self, + at_block: P::SourceHeaderId, + nonces: RangeInclusive, + proof_parameters: Self::ProofParameters, + ) -> Result<(P::SourceHeaderId, RangeInclusive, P::Proof), Self::Error>; +} + +/// One of message lane clients, which is target client for the race. +#[async_trait] +pub trait TargetClient { + /// Type of error these clients returns. + type Error: std::fmt::Debug + MaybeConnectionError; + /// Type of the additional data from the target client, used by the race. + type TargetNoncesData: std::fmt::Debug; + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker; + + /// Ask headers relay to relay finalized headers up to (and including) given header + /// from race source to race target. + async fn require_source_header(&self, id: P::SourceHeaderId); + + /// Return nonces that are known to the target client. + async fn nonces( + &self, + at_block: P::TargetHeaderId, + update_metrics: bool, + ) -> Result<(P::TargetHeaderId, TargetClientNonces), Self::Error>; + /// Submit proof to the target client. + async fn submit_proof( + &self, + generated_at_block: P::SourceHeaderId, + nonces: RangeInclusive, + proof: P::Proof, + ) -> Result, Self::Error>; +} + +/// Race strategy. +#[async_trait] +pub trait RaceStrategy: Debug { + /// Type of nonces range expected from the source client. + type SourceNoncesRange: NoncesRange; + /// Additional proof parameters required to generate proof. + type ProofParameters; + /// Additional data expected from the target client. + type TargetNoncesData; + + /// Should return true if nothing has to be synced. + fn is_empty(&self) -> bool; + /// Return id of source header that is required to be on target to continue synchronization. + fn required_source_header_at_target( + &self, + current_best: &SourceHeaderId, + ) -> Option; + /// Return the best nonce at source node. + /// + /// `Some` is returned only if we are sure that the value is greater or equal + /// than the result of `best_at_target`. + fn best_at_source(&self) -> Option; + /// Return the best nonce at target node. + /// + /// May return `None` if value is yet unknown. + fn best_at_target(&self) -> Option; + + /// Called when nonces are updated at source node of the race. + fn source_nonces_updated( + &mut self, + at_block: SourceHeaderId, + nonces: SourceClientNonces, + ); + /// Called when best nonces are updated at target node of the race. + fn best_target_nonces_updated( + &mut self, + nonces: TargetClientNonces, + race_state: &mut RaceState, + ); + /// Called when finalized nonces are updated at target node of the race. + fn finalized_target_nonces_updated( + &mut self, + nonces: TargetClientNonces, + race_state: &mut RaceState, + ); + /// Should return `Some(nonces)` if we need to deliver proof of `nonces` (and associated + /// data) from source to target node. + /// Additionally, parameters required to generate proof are returned. + async fn select_nonces_to_deliver( + &mut self, + race_state: RaceState, + ) -> Option<(RangeInclusive, Self::ProofParameters)>; +} + +/// State of the race. +#[derive(Debug, Clone)] +pub struct RaceState { + /// Best finalized source header id at the source client. + pub best_finalized_source_header_id_at_source: Option, + /// Best finalized source header id at the best block on the target + /// client (at the `best_finalized_source_header_id_at_best_target`). + pub best_finalized_source_header_id_at_best_target: Option, + /// The best header id at the target client. + pub best_target_header_id: Option, + /// Best finalized header id at the target client. + pub best_finalized_target_header_id: Option, + /// Range of nonces that we have selected to submit. + pub nonces_to_submit: Option<(SourceHeaderId, RangeInclusive, Proof)>, + /// Range of nonces that is currently submitted. + pub nonces_submitted: Option>, +} + +/// Run race loop until connection with target or source node is lost. +pub async fn run, TC: TargetClient

>( + race_source: SC, + race_source_updated: impl FusedStream>, + race_target: TC, + race_target_updated: impl FusedStream>, + mut strategy: impl RaceStrategy< + P::SourceHeaderId, + P::TargetHeaderId, + P::Proof, + SourceNoncesRange = SC::NoncesRange, + ProofParameters = SC::ProofParameters, + TargetNoncesData = TC::TargetNoncesData, + >, +) -> Result<(), FailedClient> { + let mut progress_context = Instant::now(); + let mut race_state = RaceState::default(); + + let mut source_retry_backoff = retry_backoff(); + let mut source_client_is_online = true; + let mut source_nonces_required = false; + let source_nonces = futures::future::Fuse::terminated(); + let source_generate_proof = futures::future::Fuse::terminated(); + let source_go_offline_future = futures::future::Fuse::terminated(); + + let mut target_retry_backoff = retry_backoff(); + let mut target_client_is_online = true; + let mut target_best_nonces_required = false; + let mut target_finalized_nonces_required = false; + let target_best_nonces = futures::future::Fuse::terminated(); + let target_finalized_nonces = futures::future::Fuse::terminated(); + let target_submit_proof = futures::future::Fuse::terminated(); + let target_tx_tracker = futures::future::Fuse::terminated(); + let target_go_offline_future = futures::future::Fuse::terminated(); + + futures::pin_mut!( + race_source_updated, + source_nonces, + source_generate_proof, + source_go_offline_future, + race_target_updated, + target_best_nonces, + target_finalized_nonces, + target_submit_proof, + target_tx_tracker, + target_go_offline_future, + ); + + loop { + futures::select! { + // when headers ids are updated + source_state = race_source_updated.next() => { + if let Some(source_state) = source_state { + let is_source_state_updated = race_state.best_finalized_source_header_id_at_source.as_ref() + != Some(&source_state.best_finalized_self); + if is_source_state_updated { + source_nonces_required = true; + race_state.best_finalized_source_header_id_at_source = Some(source_state.best_finalized_self); + } + } + }, + target_state = race_target_updated.next() => { + if let Some(target_state) = target_state { + let is_target_best_state_updated = race_state.best_target_header_id.as_ref() + != Some(&target_state.best_self); + + if is_target_best_state_updated { + target_best_nonces_required = true; + race_state.best_target_header_id = Some(target_state.best_self); + race_state.best_finalized_source_header_id_at_best_target + = Some(target_state.best_finalized_peer_at_best_self); + } + + let is_target_finalized_state_updated = race_state.best_finalized_target_header_id.as_ref() + != Some(&target_state.best_finalized_self); + if is_target_finalized_state_updated { + target_finalized_nonces_required = true; + race_state.best_finalized_target_header_id = Some(target_state.best_finalized_self); + } + } + }, + + // when nonces are updated + nonces = source_nonces => { + source_nonces_required = false; + + source_client_is_online = process_future_result( + nonces, + &mut source_retry_backoff, + |(at_block, nonces)| { + log::debug!( + target: "bridge", + "Received nonces from {}: {:?}", + P::source_name(), + nonces, + ); + + strategy.source_nonces_updated(at_block, nonces); + }, + &mut source_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving nonces from {}", P::source_name()), + ).fail_if_connection_error(FailedClient::Source)?; + + // ask for more headers if we have nonces to deliver and required headers are missing + let required_source_header_id = race_state + .best_finalized_source_header_id_at_best_target + .as_ref() + .and_then(|best|strategy.required_source_header_at_target(best)); + if let Some(required_source_header_id) = required_source_header_id { + race_target.require_source_header(required_source_header_id).await; + } + }, + nonces = target_best_nonces => { + target_best_nonces_required = false; + + target_client_is_online = process_future_result( + nonces, + &mut target_retry_backoff, + |(_, nonces)| { + log::debug!( + target: "bridge", + "Received best nonces from {}: {:?}", + P::target_name(), + nonces, + ); + + strategy.best_target_nonces_updated(nonces, &mut race_state); + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving best nonces from {}", P::target_name()), + ).fail_if_connection_error(FailedClient::Target)?; + }, + nonces = target_finalized_nonces => { + target_finalized_nonces_required = false; + + target_client_is_online = process_future_result( + nonces, + &mut target_retry_backoff, + |(_, nonces)| { + log::debug!( + target: "bridge", + "Received finalized nonces from {}: {:?}", + P::target_name(), + nonces, + ); + + strategy.finalized_target_nonces_updated(nonces, &mut race_state); + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error retrieving finalized nonces from {}", P::target_name()), + ).fail_if_connection_error(FailedClient::Target)?; + }, + + // proof generation and submission + proof = source_generate_proof => { + source_client_is_online = process_future_result( + proof, + &mut source_retry_backoff, + |(at_block, nonces_range, proof)| { + log::debug!( + target: "bridge", + "Received proof for nonces in range {:?} from {}", + nonces_range, + P::source_name(), + ); + + race_state.nonces_to_submit = Some((at_block, nonces_range, proof)); + }, + &mut source_go_offline_future, + async_std::task::sleep, + || format!("Error generating proof at {}", P::source_name()), + ).fail_if_error(FailedClient::Source).map(|_| true)?; + }, + proof_submit_result = target_submit_proof => { + target_client_is_online = process_future_result( + proof_submit_result, + &mut target_retry_backoff, + |artifacts: NoncesSubmitArtifacts| { + log::debug!( + target: "bridge", + "Successfully submitted proof of nonces {:?} to {}", + artifacts.nonces, + P::target_name(), + ); + + race_state.nonces_to_submit = None; + race_state.nonces_submitted = Some(artifacts.nonces); + target_tx_tracker.set(artifacts.tx_tracker.wait().fuse()); + }, + &mut target_go_offline_future, + async_std::task::sleep, + || format!("Error submitting proof {}", P::target_name()), + ).fail_if_error(FailedClient::Target).map(|_| true)?; + }, + target_transaction_status = target_tx_tracker => { + match (target_transaction_status, race_state.nonces_submitted.as_ref()) { + (TrackedTransactionStatus::Finalized(at_block), Some(nonces_submitted)) => { + // our transaction has been mined, but was it successful or not? let's check the best + // nonce at the target node. + race_target.nonces(at_block, false) + .await + .map_err(|e| format!("failed to read nonces from target node: {e:?}")) + .and_then(|(_, nonces_at_target)| { + if nonces_at_target.latest_nonce < *nonces_submitted.end() { + Err(format!( + "best nonce at target after tx is {:?} and we've submitted {:?}", + nonces_at_target.latest_nonce, + nonces_submitted.end(), + )) + } else { + Ok(()) + } + }) + .map_err(|e| { + log::error!( + target: "bridge", + "{} -> {} race has stalled. Transaction failed: {}. Going to restart", + P::source_name(), + P::target_name(), + e, + ); + + FailedClient::Both + })?; + }, + (TrackedTransactionStatus::Lost, _) => { + log::warn!( + target: "bridge", + "{} -> {} race has stalled. State: {:?}. Strategy: {:?}", + P::source_name(), + P::target_name(), + race_state, + strategy, + ); + + return Err(FailedClient::Both); + }, + _ => (), + } + }, + + // when we're ready to retry request + _ = source_go_offline_future => { + source_client_is_online = true; + }, + _ = target_go_offline_future => { + target_client_is_online = true; + }, + } + + progress_context = print_race_progress::(progress_context, &strategy); + + if source_client_is_online { + source_client_is_online = false; + + let nonces_to_deliver = + select_nonces_to_deliver(race_state.clone(), &mut strategy).await; + let best_at_source = strategy.best_at_source(); + + if let Some((at_block, nonces_range, proof_parameters)) = nonces_to_deliver { + log::debug!( + target: "bridge", + "Asking {} to prove nonces in range {:?} at block {:?}", + P::source_name(), + nonces_range, + at_block, + ); + source_generate_proof.set( + race_source.generate_proof(at_block, nonces_range, proof_parameters).fuse(), + ); + } else if source_nonces_required && best_at_source.is_some() { + log::debug!(target: "bridge", "Asking {} about message nonces", P::source_name()); + let at_block = race_state + .best_finalized_source_header_id_at_source + .as_ref() + .expect( + "source_nonces_required is only true when\ + best_finalized_source_header_id_at_source is Some; qed", + ) + .clone(); + source_nonces.set( + race_source + .nonces(at_block, best_at_source.expect("guaranteed by if condition; qed")) + .fuse(), + ); + } else { + source_client_is_online = true; + } + } + + if target_client_is_online { + target_client_is_online = false; + + if let Some((at_block, nonces_range, proof)) = race_state.nonces_to_submit.as_ref() { + log::debug!( + target: "bridge", + "Going to submit proof of messages in range {:?} to {} node", + nonces_range, + P::target_name(), + ); + target_submit_proof.set( + race_target + .submit_proof(at_block.clone(), nonces_range.clone(), proof.clone()) + .fuse(), + ); + } else if target_best_nonces_required { + log::debug!(target: "bridge", "Asking {} about best message nonces", P::target_name()); + let at_block = race_state + .best_target_header_id + .as_ref() + .expect("target_best_nonces_required is only true when best_target_header_id is Some; qed") + .clone(); + target_best_nonces.set(race_target.nonces(at_block, false).fuse()); + } else if target_finalized_nonces_required { + log::debug!(target: "bridge", "Asking {} about finalized message nonces", P::target_name()); + let at_block = race_state + .best_finalized_target_header_id + .as_ref() + .expect( + "target_finalized_nonces_required is only true when\ + best_finalized_target_header_id is Some; qed", + ) + .clone(); + target_finalized_nonces.set(race_target.nonces(at_block, true).fuse()); + } else { + target_client_is_online = true; + } + } + } +} + +impl Default + for RaceState +{ + fn default() -> Self { + RaceState { + best_finalized_source_header_id_at_source: None, + best_finalized_source_header_id_at_best_target: None, + best_target_header_id: None, + best_finalized_target_header_id: None, + nonces_to_submit: None, + nonces_submitted: None, + } + } +} + +/// Print race progress. +fn print_race_progress(prev_time: Instant, strategy: &S) -> Instant +where + P: MessageRace, + S: RaceStrategy, +{ + let now_time = Instant::now(); + + let need_update = now_time.saturating_duration_since(prev_time) > Duration::from_secs(10); + if !need_update { + return prev_time + } + + let now_best_nonce_at_source = strategy.best_at_source(); + let now_best_nonce_at_target = strategy.best_at_target(); + log::info!( + target: "bridge", + "Synced {:?} of {:?} nonces in {} -> {} race", + now_best_nonce_at_target, + now_best_nonce_at_source, + P::source_name(), + P::target_name(), + ); + now_time +} + +async fn select_nonces_to_deliver( + race_state: RaceState, + strategy: &mut Strategy, +) -> Option<(SourceHeaderId, RangeInclusive, Strategy::ProofParameters)> +where + SourceHeaderId: Clone, + Strategy: RaceStrategy, +{ + let best_finalized_source_header_id_at_best_target = + race_state.best_finalized_source_header_id_at_best_target.clone()?; + strategy + .select_nonces_to_deliver(race_state) + .await + .map(|(nonces_range, proof_parameters)| { + (best_finalized_source_header_id_at_best_target, nonces_range, proof_parameters) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::message_race_strategy::BasicStrategy; + use relay_utils::HeaderId; + + #[async_std::test] + async fn proof_is_generated_at_best_block_known_to_target_node() { + const GENERATED_AT: u64 = 6; + const BEST_AT_SOURCE: u64 = 10; + const BEST_AT_TARGET: u64 = 8; + + // target node only knows about source' BEST_AT_TARGET block + // source node has BEST_AT_SOURCE > BEST_AT_TARGET block + let mut race_state = RaceState::<_, _, ()> { + best_finalized_source_header_id_at_source: Some(HeaderId( + BEST_AT_SOURCE, + BEST_AT_SOURCE, + )), + best_finalized_source_header_id_at_best_target: Some(HeaderId( + BEST_AT_TARGET, + BEST_AT_TARGET, + )), + best_target_header_id: Some(HeaderId(0, 0)), + best_finalized_target_header_id: Some(HeaderId(0, 0)), + nonces_to_submit: None, + nonces_submitted: None, + }; + + // we have some nonces to deliver and they're generated at GENERATED_AT < BEST_AT_SOURCE + let mut strategy = BasicStrategy::new(); + strategy.source_nonces_updated( + HeaderId(GENERATED_AT, GENERATED_AT), + SourceClientNonces { new_nonces: 0..=10, confirmed_nonce: None }, + ); + strategy.best_target_nonces_updated( + TargetClientNonces { latest_nonce: 5u64, nonces_data: () }, + &mut race_state, + ); + + // the proof will be generated on source, but using BEST_AT_TARGET block + assert_eq!( + select_nonces_to_deliver(race_state, &mut strategy).await, + Some((HeaderId(BEST_AT_TARGET, BEST_AT_TARGET), 6..=10, (),)) + ); + } +} diff --git a/relays/messages/src/message_race_receiving.rs b/relays/messages/src/message_race_receiving.rs new file mode 100644 index 00000000000..c3d65d0e86a --- /dev/null +++ b/relays/messages/src/message_race_receiving.rs @@ -0,0 +1,228 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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. + +//! Message receiving race delivers proof-of-messages-delivery from "lane.target" to "lane.source". + +use crate::{ + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, + message_lane_loop::{ + NoncesSubmitArtifacts, SourceClient as MessageLaneSourceClient, SourceClientState, + TargetClient as MessageLaneTargetClient, TargetClientState, + }, + message_race_loop::{ + MessageRace, NoncesRange, SourceClient, SourceClientNonces, TargetClient, + TargetClientNonces, + }, + message_race_strategy::BasicStrategy, + metrics::MessageLaneLoopMetrics, +}; + +use async_trait::async_trait; +use bp_messages::MessageNonce; +use futures::stream::FusedStream; +use relay_utils::FailedClient; +use std::{marker::PhantomData, ops::RangeInclusive}; + +/// Message receiving confirmations delivery strategy. +type ReceivingConfirmationsBasicStrategy

= BasicStrategy< +

::TargetHeaderNumber, +

::TargetHeaderHash, +

::SourceHeaderNumber, +

::SourceHeaderHash, + RangeInclusive, +

::MessagesReceivingProof, +>; + +/// Run receiving confirmations race. +pub async fn run( + source_client: impl MessageLaneSourceClient

, + source_state_updates: impl FusedStream>, + target_client: impl MessageLaneTargetClient

, + target_state_updates: impl FusedStream>, + metrics_msg: Option, +) -> Result<(), FailedClient> { + crate::message_race_loop::run( + ReceivingConfirmationsRaceSource { + client: target_client, + metrics_msg: metrics_msg.clone(), + _phantom: Default::default(), + }, + target_state_updates, + ReceivingConfirmationsRaceTarget { + client: source_client, + metrics_msg, + _phantom: Default::default(), + }, + source_state_updates, + ReceivingConfirmationsBasicStrategy::

::new(), + ) + .await +} + +/// Messages receiving confirmations race. +struct ReceivingConfirmationsRace

(std::marker::PhantomData

); + +impl MessageRace for ReceivingConfirmationsRace

{ + type SourceHeaderId = TargetHeaderIdOf

; + type TargetHeaderId = SourceHeaderIdOf

; + + type MessageNonce = MessageNonce; + type Proof = P::MessagesReceivingProof; + + fn source_name() -> String { + format!("{}::ReceivingConfirmationsDelivery", P::TARGET_NAME) + } + + fn target_name() -> String { + format!("{}::ReceivingConfirmationsDelivery", P::SOURCE_NAME) + } +} + +/// Message receiving confirmations race source, which is a target of the lane. +struct ReceivingConfirmationsRaceSource { + client: C, + metrics_msg: Option, + _phantom: PhantomData

, +} + +#[async_trait] +impl SourceClient> for ReceivingConfirmationsRaceSource +where + P: MessageLane, + C: MessageLaneTargetClient

, +{ + type Error = C::Error; + type NoncesRange = RangeInclusive; + type ProofParameters = (); + + async fn nonces( + &self, + at_block: TargetHeaderIdOf

, + prev_latest_nonce: MessageNonce, + ) -> Result<(TargetHeaderIdOf

, SourceClientNonces), Self::Error> { + let (at_block, latest_received_nonce) = self.client.latest_received_nonce(at_block).await?; + if let Some(metrics_msg) = self.metrics_msg.as_ref() { + metrics_msg.update_target_latest_received_nonce::

(latest_received_nonce); + } + Ok(( + at_block, + SourceClientNonces { + new_nonces: prev_latest_nonce + 1..=latest_received_nonce, + confirmed_nonce: None, + }, + )) + } + + #[allow(clippy::unit_arg)] + async fn generate_proof( + &self, + at_block: TargetHeaderIdOf

, + nonces: RangeInclusive, + _proof_parameters: Self::ProofParameters, + ) -> Result< + (TargetHeaderIdOf

, RangeInclusive, P::MessagesReceivingProof), + Self::Error, + > { + self.client + .prove_messages_receiving(at_block) + .await + .map(|(at_block, proof)| (at_block, nonces, proof)) + } +} + +/// Message receiving confirmations race target, which is a source of the lane. +struct ReceivingConfirmationsRaceTarget { + client: C, + metrics_msg: Option, + _phantom: PhantomData

, +} + +#[async_trait] +impl TargetClient> for ReceivingConfirmationsRaceTarget +where + P: MessageLane, + C: MessageLaneSourceClient

, +{ + type Error = C::Error; + type TargetNoncesData = (); + type TransactionTracker = C::TransactionTracker; + + async fn require_source_header(&self, id: TargetHeaderIdOf

) { + self.client.require_target_header_on_source(id).await + } + + async fn nonces( + &self, + at_block: SourceHeaderIdOf

, + update_metrics: bool, + ) -> Result<(SourceHeaderIdOf

, TargetClientNonces<()>), Self::Error> { + let (at_block, latest_confirmed_nonce) = + self.client.latest_confirmed_received_nonce(at_block).await?; + if update_metrics { + if let Some(metrics_msg) = self.metrics_msg.as_ref() { + metrics_msg.update_source_latest_confirmed_nonce::

(latest_confirmed_nonce); + } + } + Ok((at_block, TargetClientNonces { latest_nonce: latest_confirmed_nonce, nonces_data: () })) + } + + async fn submit_proof( + &self, + generated_at_block: TargetHeaderIdOf

, + nonces: RangeInclusive, + proof: P::MessagesReceivingProof, + ) -> Result, Self::Error> { + let tx_tracker = + self.client.submit_messages_receiving_proof(generated_at_block, proof).await?; + Ok(NoncesSubmitArtifacts { nonces, tx_tracker }) + } +} + +impl NoncesRange for RangeInclusive { + fn begin(&self) -> MessageNonce { + *RangeInclusive::::start(self) + } + + fn end(&self) -> MessageNonce { + *RangeInclusive::::end(self) + } + + fn greater_than(self, nonce: MessageNonce) -> Option { + let next_nonce = nonce + 1; + let end = *self.end(); + if next_nonce > end { + None + } else { + Some(std::cmp::max(self.begin(), next_nonce)..=end) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn range_inclusive_works_as_nonces_range() { + let range = 20..=30; + + assert_eq!(NoncesRange::begin(&range), 20); + assert_eq!(NoncesRange::end(&range), 30); + assert_eq!(range.clone().greater_than(10), Some(20..=30)); + assert_eq!(range.clone().greater_than(19), Some(20..=30)); + assert_eq!(range.clone().greater_than(20), Some(21..=30)); + assert_eq!(range.clone().greater_than(25), Some(26..=30)); + assert_eq!(range.clone().greater_than(29), Some(30..=30)); + assert_eq!(range.greater_than(30), None); + } +} diff --git a/relays/messages/src/message_race_strategy.rs b/relays/messages/src/message_race_strategy.rs new file mode 100644 index 00000000000..9b9091b979f --- /dev/null +++ b/relays/messages/src/message_race_strategy.rs @@ -0,0 +1,517 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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. + +//! Basic delivery strategy. The strategy selects nonces if: +//! +//! 1) there are more nonces on the source side than on the target side; +//! 2) new nonces may be proved to target node (i.e. they have appeared at the +//! block, which is known to the target node). + +use crate::message_race_loop::{ + NoncesRange, RaceState, RaceStrategy, SourceClientNonces, TargetClientNonces, +}; + +use async_trait::async_trait; +use bp_messages::MessageNonce; +use relay_utils::HeaderId; +use std::{collections::VecDeque, fmt::Debug, marker::PhantomData, ops::RangeInclusive}; + +/// Queue of nonces known to the source node. +pub type SourceRangesQueue = + VecDeque<(HeaderId, SourceNoncesRange)>; + +/// Nonces delivery strategy. +#[derive(Debug)] +pub struct BasicStrategy< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, +> { + /// All queued nonces. + source_queue: SourceRangesQueue, + /// The best nonce known to target node (at its best block). `None` if it has not been received + /// yet. + best_target_nonce: Option, + /// Unused generic types dump. + _phantom: PhantomData<(TargetHeaderNumber, TargetHeaderHash, Proof)>, +} + +impl< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, + > + BasicStrategy< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, + > where + SourceHeaderHash: Clone, + SourceHeaderNumber: Clone + Ord, + SourceNoncesRange: NoncesRange, +{ + /// Create new delivery strategy. + pub fn new() -> Self { + BasicStrategy { + source_queue: VecDeque::new(), + best_target_nonce: None, + _phantom: Default::default(), + } + } + + /// Reference to source queue. + pub(crate) fn source_queue( + &self, + ) -> &VecDeque<(HeaderId, SourceNoncesRange)> { + &self.source_queue + } + + /// Mutable reference to source queue to use in tests. + #[cfg(test)] + pub(crate) fn source_queue_mut( + &mut self, + ) -> &mut VecDeque<(HeaderId, SourceNoncesRange)> { + &mut self.source_queue + } + + /// Returns index of the latest source queue entry, that may be delivered to the target node. + /// + /// Returns `None` if no entries may be delivered. All entries before and including the + /// `Some(_)` index are guaranteed to be witnessed at source blocks that are known to be + /// finalized at the target node. + pub fn maximal_available_source_queue_index( + &self, + race_state: RaceState< + HeaderId, + HeaderId, + Proof, + >, + ) -> Option { + // if we do not know best nonce at target node, we can't select anything + let _ = self.best_target_nonce?; + + // if we have already selected nonces that we want to submit, do nothing + if race_state.nonces_to_submit.is_some() { + return None + } + + // if we already submitted some nonces, do nothing + if race_state.nonces_submitted.is_some() { + return None + } + + // 1) we want to deliver all nonces, starting from `target_nonce + 1` + // 2) we can't deliver new nonce until header, that has emitted this nonce, is finalized + // by target client + // 3) selector is used for more complicated logic + // + // => let's first select range of entries inside deque that are already finalized at + // the target client and pass this range to the selector + let best_header_at_target = race_state.best_finalized_source_header_id_at_best_target?; + self.source_queue + .iter() + .enumerate() + .take_while(|(_, (queued_at, _))| queued_at.0 <= best_header_at_target.0) + .map(|(index, _)| index) + .last() + } + + /// Remove all nonces that are less than or equal to given nonce from the source queue. + pub fn remove_le_nonces_from_source_queue(&mut self, nonce: MessageNonce) { + while let Some((queued_at, queued_range)) = self.source_queue.pop_front() { + if let Some(range_to_requeue) = queued_range.greater_than(nonce) { + self.source_queue.push_front((queued_at, range_to_requeue)); + break + } + } + } +} + +#[async_trait] +impl< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, + > + RaceStrategy< + HeaderId, + HeaderId, + Proof, + > + for BasicStrategy< + SourceHeaderNumber, + SourceHeaderHash, + TargetHeaderNumber, + TargetHeaderHash, + SourceNoncesRange, + Proof, + > where + SourceHeaderHash: Clone + Debug + Send, + SourceHeaderNumber: Clone + Ord + Debug + Send, + SourceNoncesRange: NoncesRange + Debug + Send, + TargetHeaderHash: Debug + Send, + TargetHeaderNumber: Debug + Send, + Proof: Debug + Send, +{ + type SourceNoncesRange = SourceNoncesRange; + type ProofParameters = (); + type TargetNoncesData = (); + + fn is_empty(&self) -> bool { + self.source_queue.is_empty() + } + + fn required_source_header_at_target( + &self, + current_best: &HeaderId, + ) -> Option> { + self.source_queue + .back() + .and_then(|(h, _)| if h.0 > current_best.0 { Some(h.clone()) } else { None }) + } + + fn best_at_source(&self) -> Option { + let best_in_queue = self.source_queue.back().map(|(_, range)| range.end()); + match (best_in_queue, self.best_target_nonce) { + (Some(best_in_queue), Some(best_target_nonce)) if best_in_queue > best_target_nonce => + Some(best_in_queue), + (_, Some(best_target_nonce)) => Some(best_target_nonce), + (_, None) => None, + } + } + + fn best_at_target(&self) -> Option { + self.best_target_nonce + } + + fn source_nonces_updated( + &mut self, + at_block: HeaderId, + nonces: SourceClientNonces, + ) { + let best_in_queue = self + .source_queue + .back() + .map(|(_, range)| range.end()) + .or(self.best_target_nonce) + .unwrap_or_default(); + self.source_queue.extend( + nonces + .new_nonces + .greater_than(best_in_queue) + .into_iter() + .map(move |range| (at_block.clone(), range)), + ) + } + + fn best_target_nonces_updated( + &mut self, + nonces: TargetClientNonces<()>, + race_state: &mut RaceState< + HeaderId, + HeaderId, + Proof, + >, + ) { + let nonce = nonces.latest_nonce; + + if let Some(best_target_nonce) = self.best_target_nonce { + if nonce < best_target_nonce { + return + } + } + + while let Some(true) = self.source_queue.front().map(|(_, range)| range.begin() <= nonce) { + let maybe_subrange = self.source_queue.pop_front().and_then(|(at_block, range)| { + range.greater_than(nonce).map(|subrange| (at_block, subrange)) + }); + if let Some((at_block, subrange)) = maybe_subrange { + self.source_queue.push_front((at_block, subrange)); + break + } + } + + let need_to_select_new_nonces = race_state + .nonces_to_submit + .as_ref() + .map(|(_, nonces, _)| *nonces.end() <= nonce) + .unwrap_or(false); + if need_to_select_new_nonces { + race_state.nonces_to_submit = None; + } + + let need_new_nonces_to_submit = race_state + .nonces_submitted + .as_ref() + .map(|nonces| *nonces.end() <= nonce) + .unwrap_or(false); + if need_new_nonces_to_submit { + race_state.nonces_submitted = None; + } + + self.best_target_nonce = + Some(std::cmp::max(self.best_target_nonce.unwrap_or(nonces.latest_nonce), nonce)); + } + + fn finalized_target_nonces_updated( + &mut self, + nonces: TargetClientNonces<()>, + _race_state: &mut RaceState< + HeaderId, + HeaderId, + Proof, + >, + ) { + self.best_target_nonce = Some(std::cmp::max( + self.best_target_nonce.unwrap_or(nonces.latest_nonce), + nonces.latest_nonce, + )); + } + + async fn select_nonces_to_deliver( + &mut self, + race_state: RaceState< + HeaderId, + HeaderId, + Proof, + >, + ) -> Option<(RangeInclusive, Self::ProofParameters)> { + let maximal_source_queue_index = self.maximal_available_source_queue_index(race_state)?; + let range_begin = self.source_queue[0].1.begin(); + let range_end = self.source_queue[maximal_source_queue_index].1.end(); + self.remove_le_nonces_from_source_queue(range_end); + Some((range_begin..=range_end, ())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + message_lane::MessageLane, + message_lane_loop::tests::{ + header_id, TestMessageLane, TestMessagesProof, TestSourceHeaderHash, + TestSourceHeaderNumber, + }, + }; + + type SourceNoncesRange = RangeInclusive; + + type BasicStrategy

= super::BasicStrategy< +

::SourceHeaderNumber, +

::SourceHeaderHash, +

::TargetHeaderNumber, +

::TargetHeaderHash, + SourceNoncesRange, +

::MessagesProof, + >; + + fn source_nonces(new_nonces: SourceNoncesRange) -> SourceClientNonces { + SourceClientNonces { new_nonces, confirmed_nonce: None } + } + + fn target_nonces(latest_nonce: MessageNonce) -> TargetClientNonces<()> { + TargetClientNonces { latest_nonce, nonces_data: () } + } + + #[test] + fn strategy_is_empty_works() { + let mut strategy = BasicStrategy::::new(); + assert!(strategy.is_empty()); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=1)); + assert!(!strategy.is_empty()); + } + + #[test] + fn best_at_source_is_never_lower_than_target_nonce() { + let mut strategy = BasicStrategy::::new(); + assert_eq!(strategy.best_at_source(), None); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); + assert_eq!(strategy.best_at_source(), None); + strategy.best_target_nonces_updated(target_nonces(10), &mut Default::default()); + assert_eq!(strategy.source_queue, vec![]); + assert_eq!(strategy.best_at_source(), Some(10)); + } + + #[test] + fn source_nonce_is_never_lower_than_known_target_nonce() { + let mut strategy = BasicStrategy::::new(); + strategy.best_target_nonces_updated(target_nonces(10), &mut Default::default()); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); + assert_eq!(strategy.source_queue, vec![]); + } + + #[test] + fn source_nonce_is_never_lower_than_latest_known_source_nonce() { + let mut strategy = BasicStrategy::::new(); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); + strategy.source_nonces_updated(header_id(2), source_nonces(1..=3)); + strategy.source_nonces_updated(header_id(2), source_nonces(1..=5)); + assert_eq!(strategy.source_queue, vec![(header_id(1), 1..=5)]); + } + + #[test] + fn target_nonce_is_never_lower_than_latest_known_target_nonce() { + let mut strategy = BasicStrategy::::new(); + assert_eq!(strategy.best_target_nonce, None); + strategy.best_target_nonces_updated(target_nonces(10), &mut Default::default()); + assert_eq!(strategy.best_target_nonce, Some(10)); + strategy.best_target_nonces_updated(target_nonces(5), &mut Default::default()); + assert_eq!(strategy.best_target_nonce, Some(10)); + } + + #[test] + fn updated_target_nonce_removes_queued_entries() { + let mut strategy = BasicStrategy::::new(); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); + strategy.source_nonces_updated(header_id(2), source_nonces(6..=10)); + strategy.source_nonces_updated(header_id(3), source_nonces(11..=15)); + strategy.source_nonces_updated(header_id(4), source_nonces(16..=20)); + strategy.best_target_nonces_updated(target_nonces(15), &mut Default::default()); + assert_eq!(strategy.source_queue, vec![(header_id(4), 16..=20)]); + strategy.best_target_nonces_updated(target_nonces(17), &mut Default::default()); + assert_eq!(strategy.source_queue, vec![(header_id(4), 18..=20)]); + } + + #[test] + fn selected_nonces_are_dropped_on_target_nonce_update() { + let mut state = RaceState::default(); + let mut strategy = BasicStrategy::::new(); + state.nonces_to_submit = Some((header_id(1), 5..=10, (5..=10, None))); + strategy.best_target_nonces_updated(target_nonces(7), &mut state); + assert!(state.nonces_to_submit.is_some()); + strategy.best_target_nonces_updated(target_nonces(10), &mut state); + assert!(state.nonces_to_submit.is_none()); + } + + #[test] + fn submitted_nonces_are_dropped_on_target_nonce_update() { + let mut state = RaceState::default(); + let mut strategy = BasicStrategy::::new(); + state.nonces_submitted = Some(5..=10); + strategy.best_target_nonces_updated(target_nonces(7), &mut state); + assert!(state.nonces_submitted.is_some()); + strategy.best_target_nonces_updated(target_nonces(10), &mut state); + assert!(state.nonces_submitted.is_none()); + } + + #[async_std::test] + async fn nothing_is_selected_if_something_is_already_selected() { + let mut state = RaceState::default(); + let mut strategy = BasicStrategy::::new(); + state.nonces_to_submit = Some((header_id(1), 1..=10, (1..=10, None))); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=10)); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + } + + #[async_std::test] + async fn nothing_is_selected_if_something_is_already_submitted() { + let mut state = RaceState::default(); + let mut strategy = BasicStrategy::::new(); + state.nonces_submitted = Some(1..=10); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=10)); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + } + + #[async_std::test] + async fn select_nonces_to_deliver_works() { + let mut state = RaceState::<_, _, TestMessagesProof>::default(); + let mut strategy = BasicStrategy::::new(); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=1)); + strategy.source_nonces_updated(header_id(2), source_nonces(2..=2)); + strategy.source_nonces_updated(header_id(3), source_nonces(3..=6)); + strategy.source_nonces_updated(header_id(5), source_nonces(7..=8)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(4)); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, Some((1..=6, ()))); + strategy.best_target_nonces_updated(target_nonces(6), &mut state); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(5)); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, Some((7..=8, ()))); + strategy.best_target_nonces_updated(target_nonces(8), &mut state); + assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); + } + + #[test] + fn maximal_available_source_queue_index_works() { + let mut state = RaceState::<_, _, TestMessagesProof>::default(); + let mut strategy = BasicStrategy::::new(); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=3)); + strategy.source_nonces_updated(header_id(2), source_nonces(4..=6)); + strategy.source_nonces_updated(header_id(3), source_nonces(7..=9)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(0)); + assert_eq!(strategy.maximal_available_source_queue_index(state.clone()), None); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(1)); + assert_eq!(strategy.maximal_available_source_queue_index(state.clone()), Some(0)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(2)); + assert_eq!(strategy.maximal_available_source_queue_index(state.clone()), Some(1)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(3)); + assert_eq!(strategy.maximal_available_source_queue_index(state.clone()), Some(2)); + + state.best_finalized_source_header_id_at_best_target = Some(header_id(4)); + assert_eq!(strategy.maximal_available_source_queue_index(state), Some(2)); + } + + #[test] + fn remove_le_nonces_from_source_queue_works() { + let mut state = RaceState::<_, _, TestMessagesProof>::default(); + let mut strategy = BasicStrategy::::new(); + strategy.best_target_nonces_updated(target_nonces(0), &mut state); + strategy.source_nonces_updated(header_id(1), source_nonces(1..=3)); + strategy.source_nonces_updated(header_id(2), source_nonces(4..=6)); + strategy.source_nonces_updated(header_id(3), source_nonces(7..=9)); + + fn source_queue_nonces( + source_queue: &SourceRangesQueue< + TestSourceHeaderHash, + TestSourceHeaderNumber, + SourceNoncesRange, + >, + ) -> Vec { + source_queue.iter().flat_map(|(_, range)| range.clone()).collect() + } + + strategy.remove_le_nonces_from_source_queue(1); + assert_eq!(source_queue_nonces(&strategy.source_queue), vec![2, 3, 4, 5, 6, 7, 8, 9],); + + strategy.remove_le_nonces_from_source_queue(5); + assert_eq!(source_queue_nonces(&strategy.source_queue), vec![6, 7, 8, 9],); + + strategy.remove_le_nonces_from_source_queue(9); + assert_eq!(source_queue_nonces(&strategy.source_queue), Vec::::new(),); + + strategy.remove_le_nonces_from_source_queue(100); + assert_eq!(source_queue_nonces(&strategy.source_queue), Vec::::new(),); + } +} diff --git a/relays/messages/src/metrics.rs b/relays/messages/src/metrics.rs new file mode 100644 index 00000000000..4decb7e092e --- /dev/null +++ b/relays/messages/src/metrics.rs @@ -0,0 +1,139 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Metrics for message lane relay loop. + +use crate::{ + message_lane::MessageLane, + message_lane_loop::{SourceClientState, TargetClientState}, +}; + +use bp_messages::MessageNonce; +use finality_relay::SyncLoopMetrics; +use relay_utils::metrics::{ + metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64, +}; + +/// Message lane relay metrics. +/// +/// Cloning only clones references. +#[derive(Clone)] +pub struct MessageLaneLoopMetrics { + /// Best finalized block numbers - "source", "source_at_target", "target_at_source". + source_to_target_finality_metrics: SyncLoopMetrics, + /// Best finalized block numbers - "source", "target", "source_at_target", "target_at_source". + target_to_source_finality_metrics: SyncLoopMetrics, + /// Lane state nonces: "source_latest_generated", "source_latest_confirmed", + /// "target_latest_received", "target_latest_confirmed". + lane_state_nonces: GaugeVec, +} + +impl MessageLaneLoopMetrics { + /// Create and register messages loop metrics. + pub fn new(prefix: Option<&str>) -> Result { + Ok(MessageLaneLoopMetrics { + source_to_target_finality_metrics: SyncLoopMetrics::new( + prefix, + "source", + "source_at_target", + )?, + target_to_source_finality_metrics: SyncLoopMetrics::new( + prefix, + "target", + "target_at_source", + )?, + lane_state_nonces: GaugeVec::new( + Opts::new(metric_name(prefix, "lane_state_nonces"), "Nonces of the lane state"), + &["type"], + )?, + }) + } + + /// Update source client state metrics. + pub fn update_source_state(&self, source_client_state: SourceClientState

) { + self.source_to_target_finality_metrics + .update_best_block_at_source(source_client_state.best_self.0.into()); + self.target_to_source_finality_metrics.update_best_block_at_target( + source_client_state.best_finalized_peer_at_best_self.0.into(), + ); + self.target_to_source_finality_metrics.update_using_same_fork( + source_client_state.best_finalized_peer_at_best_self.1 == + source_client_state.actual_best_finalized_peer_at_best_self.1, + ); + } + + /// Update target client state metrics. + pub fn update_target_state(&self, target_client_state: TargetClientState

) { + self.target_to_source_finality_metrics + .update_best_block_at_source(target_client_state.best_self.0.into()); + self.source_to_target_finality_metrics.update_best_block_at_target( + target_client_state.best_finalized_peer_at_best_self.0.into(), + ); + self.source_to_target_finality_metrics.update_using_same_fork( + target_client_state.best_finalized_peer_at_best_self.1 == + target_client_state.actual_best_finalized_peer_at_best_self.1, + ); + } + + /// Update latest generated nonce at source. + pub fn update_source_latest_generated_nonce( + &self, + source_latest_generated_nonce: MessageNonce, + ) { + self.lane_state_nonces + .with_label_values(&["source_latest_generated"]) + .set(source_latest_generated_nonce); + } + + /// Update the latest confirmed nonce at source. + pub fn update_source_latest_confirmed_nonce( + &self, + source_latest_confirmed_nonce: MessageNonce, + ) { + self.lane_state_nonces + .with_label_values(&["source_latest_confirmed"]) + .set(source_latest_confirmed_nonce); + } + + /// Update the latest received nonce at target. + pub fn update_target_latest_received_nonce( + &self, + target_latest_generated_nonce: MessageNonce, + ) { + self.lane_state_nonces + .with_label_values(&["target_latest_received"]) + .set(target_latest_generated_nonce); + } + + /// Update the latest confirmed nonce at target. + pub fn update_target_latest_confirmed_nonce( + &self, + target_latest_confirmed_nonce: MessageNonce, + ) { + self.lane_state_nonces + .with_label_values(&["target_latest_confirmed"]) + .set(target_latest_confirmed_nonce); + } +} + +impl Metric for MessageLaneLoopMetrics { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + self.source_to_target_finality_metrics.register(registry)?; + self.target_to_source_finality_metrics.register(registry)?; + register(self.lane_state_nonces.clone(), registry)?; + Ok(()) + } +} diff --git a/relays/parachains/Cargo.toml b/relays/parachains/Cargo.toml new file mode 100644 index 00000000000..2af091c7ebb --- /dev/null +++ b/relays/parachains/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "parachains-relay" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +async-std = "1.6.5" +async-trait = "0.1.40" +futures = "0.3.5" +log = "0.4.17" +relay-utils = { path = "../utils" } + +# Bridge dependencies + +bp-parachains = { path = "../../primitives/parachains" } +bp-polkadot-core = { path = "../../primitives/polkadot-core" } +relay-substrate-client = { path = "../client-substrate" } + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5" } +relay-substrate-client = { path = "../client-substrate", features = ["test-helpers"] } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/parachains/src/lib.rs b/relays/parachains/src/lib.rs new file mode 100644 index 00000000000..94b3ce3ec76 --- /dev/null +++ b/relays/parachains/src/lib.rs @@ -0,0 +1,30 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use std::fmt::Debug; + +use relay_substrate_client::Chain; + +pub mod parachains_loop; +pub mod parachains_loop_metrics; + +/// Finality proofs synchronization pipeline. +pub trait ParachainsPipeline: 'static + Clone + Debug + Send + Sync { + /// Relay chain which is storing parachain heads in its `paras` module. + type SourceChain: Chain; + /// Target chain (either relay or para) which wants to know about new parachain heads. + type TargetChain: Chain; +} diff --git a/relays/parachains/src/parachains_loop.rs b/relays/parachains/src/parachains_loop.rs new file mode 100644 index 00000000000..3ef9a7f7a73 --- /dev/null +++ b/relays/parachains/src/parachains_loop.rs @@ -0,0 +1,1255 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{parachains_loop_metrics::ParachainsLoopMetrics, ParachainsPipeline}; + +use async_trait::async_trait; +use bp_parachains::BestParaHeadHash; +use bp_polkadot_core::{ + parachains::{ParaHash, ParaHeadsProof, ParaId}, + BlockNumber as RelayBlockNumber, +}; +use futures::{ + future::{FutureExt, Shared}, + poll, select_biased, +}; +use relay_substrate_client::{BlockNumberOf, Chain, HeaderIdOf}; +use relay_utils::{ + metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, + TrackedTransactionStatus, TransactionTracker, +}; +use std::{ + collections::{BTreeMap, BTreeSet}, + future::Future, + pin::Pin, + task::Poll, + time::Duration, +}; + +/// Parachain heads synchronization params. +#[derive(Clone, Debug)] +pub struct ParachainSyncParams { + /// Parachains that we're relaying here. + pub parachains: Vec, + /// Parachain heads update strategy. + pub strategy: ParachainSyncStrategy, + /// Stall timeout. If we have submitted transaction and we see no state updates for this + /// period, we consider our transaction lost. + pub stall_timeout: Duration, +} + +/// Parachain heads update strategy. +#[derive(Clone, Copy, Debug)] +pub enum ParachainSyncStrategy { + /// Update whenever any parachain head is updated. + Any, + /// Wait till all parachain heads are updated. + All, +} + +/// Parachain header availability at a certain chain. +#[derive(Clone, Copy, Debug)] +pub enum AvailableHeader { + /// The client refuses to report parachain head at this moment. + /// + /// It is a "mild" error, which may appear when e.g. on-demand parachains relay is used. + /// This variant must be treated as "we don't want to update parachain head value at the + /// target chain at this moment". + Unavailable, + /// There's no parachain header at the relay chain. + /// + /// Normally it means that the parachain is not registered there. + Missing, + /// Parachain head with given hash is available at the source chain. + Available(T), +} + +impl AvailableHeader { + /// Transform contained value. + pub fn map(self, f: F) -> AvailableHeader + where + F: FnOnce(T) -> U, + { + match self { + AvailableHeader::Unavailable => AvailableHeader::Unavailable, + AvailableHeader::Missing => AvailableHeader::Missing, + AvailableHeader::Available(val) => AvailableHeader::Available(f(val)), + } + } +} + +/// Source client used in parachain heads synchronization loop. +#[async_trait] +pub trait SourceClient: RelayClient { + /// Returns `Ok(true)` if client is in synced state. + async fn ensure_synced(&self) -> Result; + + /// Get parachain head hash at given block. + /// + /// The implementation may call `ParachainsLoopMetrics::update_best_parachain_block_at_source` + /// on provided `metrics` object to update corresponding metric value. + async fn parachain_head( + &self, + at_block: HeaderIdOf, + metrics: Option<&ParachainsLoopMetrics>, + para_id: ParaId, + ) -> Result, Self::Error>; + + /// Get parachain heads proof. + /// + /// The number and order of entries in the resulting parachain head hashes vector must match the + /// number and order of parachains in the `parachains` vector. The incorrect implementation will + /// result in panic. + async fn prove_parachain_heads( + &self, + at_block: HeaderIdOf, + parachains: &[ParaId], + ) -> Result<(ParaHeadsProof, Vec), Self::Error>; +} + +/// Target client used in parachain heads synchronization loop. +#[async_trait] +pub trait TargetClient: RelayClient { + /// Transaction tracker to track submitted transactions. + type TransactionTracker: TransactionTracker>; + + /// Get best block id. + async fn best_block(&self) -> Result, Self::Error>; + + /// Get best finalized source block id. + async fn best_finalized_source_block( + &self, + at_block: &HeaderIdOf, + ) -> Result, Self::Error>; + + /// Get parachain head hash at given block. + /// + /// The implementation may call `ParachainsLoopMetrics::update_best_parachain_block_at_target` + /// on provided `metrics` object to update corresponding metric value. + async fn parachain_head( + &self, + at_block: HeaderIdOf, + metrics: Option<&ParachainsLoopMetrics>, + para_id: ParaId, + ) -> Result, Self::Error>; + + /// Submit parachain heads proof. + async fn submit_parachain_heads_proof( + &self, + at_source_block: HeaderIdOf, + updated_parachains: Vec<(ParaId, ParaHash)>, + proof: ParaHeadsProof, + ) -> Result; +} + +/// Return prefix that will be used by default to expose Prometheus metrics of the parachains +/// sync loop. +pub fn metrics_prefix() -> String { + format!("{}_to_{}_Parachains", P::SourceChain::NAME, P::TargetChain::NAME) +} + +/// Run parachain heads synchronization. +pub async fn run( + source_client: impl SourceClient

, + target_client: impl TargetClient

, + sync_params: ParachainSyncParams, + metrics_params: MetricsParams, + exit_signal: impl Future + 'static + Send, +) -> Result<(), relay_utils::Error> +where + P::SourceChain: Chain, +{ + let exit_signal = exit_signal.shared(); + relay_utils::relay_loop(source_client, target_client) + .with_metrics(metrics_params) + .loop_metric(ParachainsLoopMetrics::new(Some(&metrics_prefix::

()))?)? + .expose() + .await? + .run(metrics_prefix::

(), move |source_client, target_client, metrics| { + run_until_connection_lost( + source_client, + target_client, + sync_params.clone(), + metrics, + exit_signal.clone(), + ) + }) + .await +} + +/// Run parachain heads synchronization. +async fn run_until_connection_lost( + source_client: impl SourceClient

, + target_client: impl TargetClient

, + sync_params: ParachainSyncParams, + metrics: Option, + exit_signal: impl Future + Send, +) -> Result<(), FailedClient> +where + P::SourceChain: Chain, +{ + let exit_signal = exit_signal.fuse(); + let min_block_interval = std::cmp::min( + P::SourceChain::AVERAGE_BLOCK_INTERVAL, + P::TargetChain::AVERAGE_BLOCK_INTERVAL, + ); + + let mut submitted_heads_tracker: Option> = None; + + futures::pin_mut!(exit_signal); + + // Note that the internal loop breaks with `FailedClient` error even if error is non-connection. + // It is Ok for now, but it may need to be fixed in the future to use exponential backoff for + // regular errors. + + loop { + // Either wait for new block, or exit signal. + // Please note that we are prioritizing the exit signal since if both events happen at once + // it doesn't make sense to perform one more loop iteration. + select_biased! { + _ = exit_signal => return Ok(()), + _ = async_std::task::sleep(min_block_interval).fuse() => {}, + } + + // if source client is not yet synced, we'll need to sleep. Otherwise we risk submitting too + // much redundant transactions + match source_client.ensure_synced().await { + Ok(true) => (), + Ok(false) => { + log::warn!( + target: "bridge", + "{} client is syncing. Won't do anything until it is synced", + P::SourceChain::NAME, + ); + continue + }, + Err(e) => { + log::warn!( + target: "bridge", + "{} client has failed to return its sync status: {:?}", + P::SourceChain::NAME, + e, + ); + return Err(FailedClient::Target) + }, + } + + // if we have active transaction, we'll need to wait until it is mined or dropped + let best_target_block = target_client.best_block().await.map_err(|e| { + log::warn!(target: "bridge", "Failed to read best {} block: {:?}", P::SourceChain::NAME, e); + FailedClient::Target + })?; + let heads_at_target = read_heads_at_target( + &target_client, + metrics.as_ref(), + &best_target_block, + &sync_params.parachains, + ) + .await?; + + // check if our transaction has been mined + if let Some(tracker) = submitted_heads_tracker.take() { + match tracker.update(&best_target_block, &heads_at_target).await { + SubmittedHeadsStatus::Waiting(tracker) => { + // no news about our transaction and we shall keep waiting + submitted_heads_tracker = Some(tracker); + continue + }, + SubmittedHeadsStatus::Final(TrackedTransactionStatus::Finalized(_)) => { + // all heads have been updated, we don't need this tracker anymore + }, + SubmittedHeadsStatus::Final(TrackedTransactionStatus::Lost) => { + log::warn!( + target: "bridge", + "Parachains synchronization from {} to {} has stalled. Going to restart", + P::SourceChain::NAME, + P::TargetChain::NAME, + ); + + return Err(FailedClient::Both) + }, + } + } + + // we have no active transaction and may need to update heads, but do we have something for + // update? + let best_finalized_relay_block = target_client + .best_finalized_source_block(&best_target_block) + .await + .map_err(|e| { + log::warn!( + target: "bridge", + "Failed to read best finalized {} block from {}: {:?}", + P::SourceChain::NAME, + P::TargetChain::NAME, + e, + ); + FailedClient::Target + })?; + let heads_at_source = read_heads_at_source( + &source_client, + metrics.as_ref(), + &best_finalized_relay_block, + &sync_params.parachains, + ) + .await?; + let updated_ids = select_parachains_to_update::

( + heads_at_source, + heads_at_target, + best_finalized_relay_block, + ); + let is_update_required = is_update_required(&sync_params, &updated_ids); + + log::info!( + target: "bridge", + "Total {} parachains: {}. Up-to-date at {}: {}. Needs update at {}: {}.", + P::SourceChain::NAME, + sync_params.parachains.len(), + P::TargetChain::NAME, + sync_params.parachains.len() - updated_ids.len(), + P::TargetChain::NAME, + updated_ids.len(), + ); + + if is_update_required { + let (heads_proofs, head_hashes) = source_client + .prove_parachain_heads(best_finalized_relay_block, &updated_ids) + .await + .map_err(|e| { + log::warn!( + target: "bridge", + "Failed to prove {} parachain heads: {:?}", + P::SourceChain::NAME, + e, + ); + FailedClient::Source + })?; + log::info!( + target: "bridge", + "Submitting {} parachain heads update transaction to {}", + P::SourceChain::NAME, + P::TargetChain::NAME, + ); + + assert_eq!( + head_hashes.len(), + updated_ids.len(), + "Incorrect parachains SourceClient implementation" + ); + + let transaction_tracker = target_client + .submit_parachain_heads_proof( + best_finalized_relay_block, + updated_ids.iter().cloned().zip(head_hashes).collect(), + heads_proofs, + ) + .await + .map_err(|e| { + log::warn!( + target: "bridge", + "Failed to submit {} parachain heads proof to {}: {:?}", + P::SourceChain::NAME, + P::TargetChain::NAME, + e, + ); + FailedClient::Target + })?; + submitted_heads_tracker = Some(SubmittedHeadsTracker::

::new( + updated_ids, + best_finalized_relay_block.0, + transaction_tracker, + )); + } + } +} + +/// Given heads at source and target clients, returns set of heads that are out of sync. +fn select_parachains_to_update( + heads_at_source: BTreeMap>, + heads_at_target: BTreeMap>, + best_finalized_relay_block: HeaderIdOf, +) -> Vec +where + P::SourceChain: Chain, +{ + log::trace!( + target: "bridge", + "Selecting {} parachains to update at {} (relay block: {:?}):\n\t\ + At {}: {:?}\n\t\ + At {}: {:?}", + P::SourceChain::NAME, + P::TargetChain::NAME, + best_finalized_relay_block, + P::SourceChain::NAME, + heads_at_source, + P::TargetChain::NAME, + heads_at_target, + ); + + heads_at_source + .into_iter() + .zip(heads_at_target.into_iter()) + .filter(|((para, head_at_source), (_, head_at_target))| { + let needs_update = match (head_at_source, head_at_target) { + (AvailableHeader::Unavailable, _) => { + // source client has politely asked us not to update current parachain head + // at the target chain + false + }, + (AvailableHeader::Available(head_at_source), Some(head_at_target)) + if head_at_target.at_relay_block_number < best_finalized_relay_block.0 && + head_at_target.head_hash != *head_at_source => + { + // source client knows head that is better than the head known to the target + // client + true + }, + (AvailableHeader::Available(_), Some(_)) => { + // this is normal case when relay has recently updated heads, when parachain is + // not progressing, or when our source client is still syncing + false + }, + (AvailableHeader::Available(_), None) => { + // parachain is not yet known to the target client. This is true when parachain + // or bridge has been just onboarded/started + true + }, + (AvailableHeader::Missing, Some(_)) => { + // parachain/parathread has been offboarded removed from the system. It needs to + // be propageted to the target client + true + }, + (AvailableHeader::Missing, None) => { + // all's good - parachain is unknown to both clients + false + }, + }; + if needs_update { + log::trace!( + target: "bridge", + "{} parachain {:?} needs update at {}: {:?} vs {:?}", + P::SourceChain::NAME, + para, + P::TargetChain::NAME, + head_at_source, + head_at_target, + ); + } + + needs_update + }) + .map(|((para, _), _)| para) + .collect() +} + +/// Returns true if we need to submit update transactions to the target node. +fn is_update_required(sync_params: &ParachainSyncParams, updated_ids: &[ParaId]) -> bool { + match sync_params.strategy { + ParachainSyncStrategy::All => updated_ids.len() == sync_params.parachains.len(), + ParachainSyncStrategy::Any => !updated_ids.is_empty(), + } +} + +/// Reads given parachains heads from the source client. +/// +/// Guarantees that the returning map will have an entry for every parachain from `parachains`. +async fn read_heads_at_source( + source_client: &impl SourceClient

, + metrics: Option<&ParachainsLoopMetrics>, + at_relay_block: &HeaderIdOf, + parachains: &[ParaId], +) -> Result>, FailedClient> { + let mut para_head_hashes = BTreeMap::new(); + for para in parachains { + let para_head = source_client.parachain_head(*at_relay_block, metrics, *para).await; + match para_head { + Ok(para_head) => { + para_head_hashes.insert(*para, para_head); + }, + Err(e) => { + log::warn!( + target: "bridge", + "Failed to read head of {} parachain {:?}: {:?}", + P::SourceChain::NAME, + para, + e, + ); + return Err(FailedClient::Source) + }, + } + } + Ok(para_head_hashes) +} + +/// Reads given parachains heads from the source client. +/// +/// Guarantees that the returning map will have an entry for every parachain from `parachains`. +async fn read_heads_at_target( + target_client: &impl TargetClient

, + metrics: Option<&ParachainsLoopMetrics>, + at_block: &HeaderIdOf, + parachains: &[ParaId], +) -> Result>, FailedClient> { + let mut para_best_head_hashes = BTreeMap::new(); + for para in parachains { + let para_best_head = target_client.parachain_head(*at_block, metrics, *para).await; + match para_best_head { + Ok(para_best_head) => { + para_best_head_hashes.insert(*para, para_best_head); + }, + Err(e) => { + log::warn!( + target: "bridge", + "Failed to read head of {} parachain {:?} at {}: {:?}", + P::SourceChain::NAME, + para, + P::TargetChain::NAME, + e, + ); + return Err(FailedClient::Target) + }, + } + } + Ok(para_best_head_hashes) +} + +/// Submitted heads status. +enum SubmittedHeadsStatus { + /// Heads are not yet updated. + Waiting(SubmittedHeadsTracker

), + /// Heads transaction has either been finalized or lost (i.e. received its "final" status). + Final(TrackedTransactionStatus>), +} + +/// Type of the transaction tracker that the `SubmittedHeadsTracker` is using. +/// +/// It needs to be shared because of `poll` macro and our consuming `update` method. +type SharedTransactionTracker

= Shared< + Pin< + Box< + dyn Future< + Output = TrackedTransactionStatus< + HeaderIdOf<

::TargetChain>, + >, + > + Send, + >, + >, +>; + +/// Submitted parachain heads transaction. +struct SubmittedHeadsTracker { + /// Ids of parachains which heads were updated in the tracked transaction. + awaiting_update: BTreeSet, + /// Number of relay chain block that has been used to craft parachain heads proof. + relay_block_number: BlockNumberOf, + /// Future that waits for submitted transaction finality or loss. + /// + /// It needs to be shared because of `poll` macro and our consuming `update` method. + transaction_tracker: SharedTransactionTracker

, +} + +impl SubmittedHeadsTracker

+where + P::SourceChain: Chain, +{ + /// Creates new parachain heads transaction tracker. + pub fn new( + awaiting_update: impl IntoIterator, + relay_block_number: BlockNumberOf, + transaction_tracker: impl TransactionTracker> + 'static, + ) -> Self { + SubmittedHeadsTracker { + awaiting_update: awaiting_update.into_iter().collect(), + relay_block_number, + transaction_tracker: transaction_tracker.wait().fuse().boxed().shared(), + } + } + + /// Returns `None` if all submitted parachain heads have been updated. + pub async fn update( + mut self, + at_target_block: &HeaderIdOf, + heads_at_target: &BTreeMap>, + ) -> SubmittedHeadsStatus

{ + // remove all pending heads that were synced + for (para, best_para_head) in heads_at_target { + if best_para_head + .as_ref() + .map(|best_para_head| { + best_para_head.at_relay_block_number >= self.relay_block_number + }) + .unwrap_or(false) + { + self.awaiting_update.remove(para); + + log::trace!( + target: "bridge", + "Head of parachain {:?} has been updated at {}: {:?}. Outdated parachains remaining: {}", + para, + P::TargetChain::NAME, + best_para_head, + self.awaiting_update.len(), + ); + } + } + + // if we have synced all required heads, we are done + if self.awaiting_update.is_empty() { + return SubmittedHeadsStatus::Final(TrackedTransactionStatus::Finalized( + *at_target_block, + )) + } + + // if underlying transaction tracker has reported that the transaction is lost, we may + // then restart our sync + let transaction_tracker = self.transaction_tracker.clone(); + match poll!(transaction_tracker) { + Poll::Ready(TrackedTransactionStatus::Lost) => + return SubmittedHeadsStatus::Final(TrackedTransactionStatus::Lost), + Poll::Ready(TrackedTransactionStatus::Finalized(_)) => { + // so we are here and our transaction is mined+finalized, but some of heads were not + // updated => we're considering our loop as stalled + return SubmittedHeadsStatus::Final(TrackedTransactionStatus::Lost) + }, + _ => (), + } + + SubmittedHeadsStatus::Waiting(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use async_std::sync::{Arc, Mutex}; + use codec::Encode; + use futures::{SinkExt, StreamExt}; + use relay_substrate_client::test_chain::TestChain; + use relay_utils::{HeaderId, MaybeConnectionError}; + use sp_core::H256; + + const PARA_ID: u32 = 0; + const PARA_0_HASH: ParaHash = H256([1u8; 32]); + const PARA_1_HASH: ParaHash = H256([2u8; 32]); + + #[derive(Clone, Debug)] + enum TestError { + Error, + MissingParachainHeadProof, + } + + impl MaybeConnectionError for TestError { + fn is_connection_error(&self) -> bool { + false + } + } + + #[derive(Clone, Debug, PartialEq, Eq)] + struct TestParachainsPipeline; + + impl ParachainsPipeline for TestParachainsPipeline { + type SourceChain = TestChain; + type TargetChain = TestChain; + } + + #[derive(Clone, Debug)] + struct TestClient { + data: Arc>, + } + + #[derive(Clone, Debug)] + struct TestTransactionTracker(Option>>); + + #[async_trait] + impl TransactionTracker for TestTransactionTracker { + type HeaderId = HeaderIdOf; + + async fn wait(self) -> TrackedTransactionStatus> { + match self.0 { + Some(status) => status, + None => futures::future::pending().await, + } + } + } + + #[derive(Clone, Debug)] + struct TestClientData { + source_sync_status: Result, + source_heads: BTreeMap, TestError>>, + source_proofs: BTreeMap, TestError>>, + + target_best_block: Result, TestError>, + target_best_finalized_source_block: Result, TestError>, + target_heads: BTreeMap>, + target_submit_result: Result<(), TestError>, + + exit_signal_sender: Option>>, + } + + impl TestClientData { + pub fn minimal() -> Self { + TestClientData { + source_sync_status: Ok(true), + source_heads: vec![(PARA_ID, Ok(AvailableHeader::Available(PARA_0_HASH)))] + .into_iter() + .collect(), + source_proofs: vec![(PARA_ID, Ok(PARA_0_HASH.encode()))].into_iter().collect(), + + target_best_block: Ok(HeaderId(0, Default::default())), + target_best_finalized_source_block: Ok(HeaderId(0, Default::default())), + target_heads: BTreeMap::new(), + target_submit_result: Ok(()), + + exit_signal_sender: None, + } + } + + pub fn with_exit_signal_sender( + sender: futures::channel::mpsc::UnboundedSender<()>, + ) -> Self { + let mut client = Self::minimal(); + client.exit_signal_sender = Some(Box::new(sender)); + client + } + } + + impl From for TestClient { + fn from(data: TestClientData) -> TestClient { + TestClient { data: Arc::new(Mutex::new(data)) } + } + } + + #[async_trait] + impl RelayClient for TestClient { + type Error = TestError; + + async fn reconnect(&mut self) -> Result<(), TestError> { + unimplemented!() + } + } + + #[async_trait] + impl SourceClient for TestClient { + async fn ensure_synced(&self) -> Result { + self.data.lock().await.source_sync_status.clone() + } + + async fn parachain_head( + &self, + _at_block: HeaderIdOf, + _metrics: Option<&ParachainsLoopMetrics>, + para_id: ParaId, + ) -> Result, TestError> { + match self.data.lock().await.source_heads.get(¶_id.0).cloned() { + Some(result) => result, + None => Ok(AvailableHeader::Missing), + } + } + + async fn prove_parachain_heads( + &self, + _at_block: HeaderIdOf, + parachains: &[ParaId], + ) -> Result<(ParaHeadsProof, Vec), TestError> { + let mut proofs = Vec::new(); + for para_id in parachains { + proofs.push( + self.data + .lock() + .await + .source_proofs + .get(¶_id.0) + .cloned() + .transpose()? + .ok_or(TestError::MissingParachainHeadProof)?, + ); + } + Ok((ParaHeadsProof(proofs), vec![Default::default(); parachains.len()])) + } + } + + #[async_trait] + impl TargetClient for TestClient { + type TransactionTracker = TestTransactionTracker; + + async fn best_block(&self) -> Result, TestError> { + self.data.lock().await.target_best_block.clone() + } + + async fn best_finalized_source_block( + &self, + _at_block: &HeaderIdOf, + ) -> Result, TestError> { + self.data.lock().await.target_best_finalized_source_block.clone() + } + + async fn parachain_head( + &self, + _at_block: HeaderIdOf, + _metrics: Option<&ParachainsLoopMetrics>, + para_id: ParaId, + ) -> Result, TestError> { + self.data.lock().await.target_heads.get(¶_id.0).cloned().transpose() + } + + async fn submit_parachain_heads_proof( + &self, + _at_source_block: HeaderIdOf, + _updated_parachains: Vec<(ParaId, ParaHash)>, + _proof: ParaHeadsProof, + ) -> Result { + let mut data = self.data.lock().await; + data.target_submit_result.clone()?; + + if let Some(mut exit_signal_sender) = data.exit_signal_sender.take() { + exit_signal_sender.send(()).await.unwrap(); + } + Ok(TestTransactionTracker(Some( + TrackedTransactionStatus::Finalized(Default::default()), + ))) + } + } + + fn default_sync_params() -> ParachainSyncParams { + ParachainSyncParams { + parachains: vec![ParaId(PARA_ID)], + strategy: ParachainSyncStrategy::Any, + stall_timeout: Duration::from_secs(60), + } + } + + #[test] + fn when_source_client_fails_to_return_sync_state() { + let mut test_source_client = TestClientData::minimal(); + test_source_client.source_sync_status = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(test_source_client), + TestClient::from(TestClientData::minimal()), + default_sync_params(), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn when_target_client_fails_to_return_best_block() { + let mut test_target_client = TestClientData::minimal(); + test_target_client.target_best_block = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(test_target_client), + default_sync_params(), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn when_target_client_fails_to_read_heads() { + let mut test_target_client = TestClientData::minimal(); + test_target_client.target_heads.insert(PARA_ID, Err(TestError::Error)); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(test_target_client), + default_sync_params(), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn when_target_client_fails_to_read_best_finalized_source_block() { + let mut test_target_client = TestClientData::minimal(); + test_target_client.target_best_finalized_source_block = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(test_target_client), + default_sync_params(), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn when_source_client_fails_to_read_heads() { + let mut test_source_client = TestClientData::minimal(); + test_source_client.source_heads.insert(PARA_ID, Err(TestError::Error)); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(test_source_client), + TestClient::from(TestClientData::minimal()), + default_sync_params(), + None, + futures::future::pending(), + )), + Err(FailedClient::Source), + ); + } + + #[test] + fn when_source_client_fails_to_prove_heads() { + let mut test_source_client = TestClientData::minimal(); + test_source_client.source_proofs.insert(PARA_ID, Err(TestError::Error)); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(test_source_client), + TestClient::from(TestClientData::minimal()), + default_sync_params(), + None, + futures::future::pending(), + )), + Err(FailedClient::Source), + ); + } + + #[test] + fn when_target_client_rejects_update_transaction() { + let mut test_target_client = TestClientData::minimal(); + test_target_client.target_submit_result = Err(TestError::Error); + + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(test_target_client), + default_sync_params(), + None, + futures::future::pending(), + )), + Err(FailedClient::Target), + ); + } + + #[test] + fn minimal_working_case() { + let (exit_signal_sender, exit_signal) = futures::channel::mpsc::unbounded(); + assert_eq!( + async_std::task::block_on(run_until_connection_lost( + TestClient::from(TestClientData::minimal()), + TestClient::from(TestClientData::with_exit_signal_sender(exit_signal_sender)), + default_sync_params(), + None, + exit_signal.into_future().map(|(_, _)| ()), + )), + Ok(()), + ); + } + + const PARA_1_ID: u32 = PARA_ID + 1; + const SOURCE_BLOCK_NUMBER: u32 = 100; + + fn test_tx_tracker() -> SubmittedHeadsTracker { + SubmittedHeadsTracker::new( + vec![ParaId(PARA_ID), ParaId(PARA_1_ID)], + SOURCE_BLOCK_NUMBER, + TestTransactionTracker(None), + ) + } + + fn all_expected_tracker_heads() -> BTreeMap> { + vec![ + ( + ParaId(PARA_ID), + Some(BestParaHeadHash { + at_relay_block_number: SOURCE_BLOCK_NUMBER, + head_hash: PARA_0_HASH, + }), + ), + ( + ParaId(PARA_1_ID), + Some(BestParaHeadHash { + at_relay_block_number: SOURCE_BLOCK_NUMBER, + head_hash: PARA_0_HASH, + }), + ), + ] + .into_iter() + .collect() + } + + impl From> for Option> { + fn from(status: SubmittedHeadsStatus) -> Option> { + match status { + SubmittedHeadsStatus::Waiting(tracker) => Some(tracker.awaiting_update), + _ => None, + } + } + } + + #[async_std::test] + async fn tx_tracker_update_when_nothing_is_updated() { + assert_eq!( + Some(test_tx_tracker().awaiting_update), + test_tx_tracker() + .update(&HeaderId(0, Default::default()), &vec![].into_iter().collect()) + .await + .into(), + ); + } + + #[async_std::test] + async fn tx_tracker_update_when_one_of_heads_is_updated_to_previous_value() { + assert_eq!( + Some(test_tx_tracker().awaiting_update), + test_tx_tracker() + .update( + &HeaderId(0, Default::default()), + &vec![( + ParaId(PARA_ID), + Some(BestParaHeadHash { + at_relay_block_number: SOURCE_BLOCK_NUMBER - 1, + head_hash: PARA_0_HASH, + }) + )] + .into_iter() + .collect() + ) + .await + .into(), + ); + } + + #[async_std::test] + async fn tx_tracker_update_when_one_of_heads_is_updated() { + assert_eq!( + Some(vec![ParaId(PARA_1_ID)].into_iter().collect::>()), + test_tx_tracker() + .update( + &HeaderId(0, Default::default()), + &vec![( + ParaId(PARA_ID), + Some(BestParaHeadHash { + at_relay_block_number: SOURCE_BLOCK_NUMBER, + head_hash: PARA_0_HASH, + }) + )] + .into_iter() + .collect() + ) + .await + .into(), + ); + } + + #[async_std::test] + async fn tx_tracker_update_when_all_heads_are_updated() { + assert_eq!( + Option::>::None, + test_tx_tracker() + .update(&HeaderId(0, Default::default()), &all_expected_tracker_heads()) + .await + .into(), + ); + } + + #[async_std::test] + async fn tx_tracker_update_when_tx_is_lost() { + let mut tx_tracker = test_tx_tracker(); + tx_tracker.transaction_tracker = + futures::future::ready(TrackedTransactionStatus::Lost).boxed().shared(); + assert!(matches!( + tx_tracker + .update(&HeaderId(0, Default::default()), &vec![].into_iter().collect()) + .await, + SubmittedHeadsStatus::Final(TrackedTransactionStatus::Lost), + )); + } + + #[async_std::test] + async fn tx_tracker_update_when_tx_is_finalized_but_heads_are_not_updated() { + let mut tx_tracker = test_tx_tracker(); + tx_tracker.transaction_tracker = + futures::future::ready(TrackedTransactionStatus::Finalized(Default::default())) + .boxed() + .shared(); + assert!(matches!( + tx_tracker + .update(&HeaderId(0, Default::default()), &vec![].into_iter().collect()) + .await, + SubmittedHeadsStatus::Final(TrackedTransactionStatus::Lost), + )); + } + + #[async_std::test] + async fn tx_tracker_update_when_tx_is_finalized_and_heads_are_updated() { + let mut tx_tracker = test_tx_tracker(); + tx_tracker.transaction_tracker = + futures::future::ready(TrackedTransactionStatus::Finalized(Default::default())) + .boxed() + .shared(); + assert!(matches!( + tx_tracker + .update(&HeaderId(0, Default::default()), &all_expected_tracker_heads()) + .await, + SubmittedHeadsStatus::Final(TrackedTransactionStatus::Finalized(_)), + )); + } + + #[test] + fn parachain_is_not_updated_if_it_is_unknown_to_both_clients() { + assert_eq!( + select_parachains_to_update::( + vec![(ParaId(PARA_ID), AvailableHeader::Missing)].into_iter().collect(), + vec![(ParaId(PARA_ID), None)].into_iter().collect(), + HeaderId(10, Default::default()), + ), + Vec::::new(), + ); + } + + #[test] + fn parachain_is_not_updated_if_it_has_been_updated_at_better_relay_block() { + assert_eq!( + select_parachains_to_update::( + vec![(ParaId(PARA_ID), AvailableHeader::Available(PARA_0_HASH))] + .into_iter() + .collect(), + vec![( + ParaId(PARA_ID), + Some(BestParaHeadHash { at_relay_block_number: 20, head_hash: PARA_1_HASH }) + )] + .into_iter() + .collect(), + HeaderId(10, Default::default()), + ), + Vec::::new(), + ); + } + + #[test] + fn parachain_is_not_updated_if_hash_is_the_same_at_next_relay_block() { + assert_eq!( + select_parachains_to_update::( + vec![(ParaId(PARA_ID), AvailableHeader::Available(PARA_0_HASH))] + .into_iter() + .collect(), + vec![( + ParaId(PARA_ID), + Some(BestParaHeadHash { at_relay_block_number: 0, head_hash: PARA_0_HASH }) + )] + .into_iter() + .collect(), + HeaderId(10, Default::default()), + ), + Vec::::new(), + ); + } + + #[test] + fn parachain_is_updated_after_offboarding() { + assert_eq!( + select_parachains_to_update::( + vec![(ParaId(PARA_ID), AvailableHeader::Missing)].into_iter().collect(), + vec![( + ParaId(PARA_ID), + Some(BestParaHeadHash { + at_relay_block_number: 0, + head_hash: Default::default(), + }) + )] + .into_iter() + .collect(), + HeaderId(10, Default::default()), + ), + vec![ParaId(PARA_ID)], + ); + } + + #[test] + fn parachain_is_updated_after_onboarding() { + assert_eq!( + select_parachains_to_update::( + vec![(ParaId(PARA_ID), AvailableHeader::Available(PARA_0_HASH))] + .into_iter() + .collect(), + vec![(ParaId(PARA_ID), None)].into_iter().collect(), + HeaderId(10, Default::default()), + ), + vec![ParaId(PARA_ID)], + ); + } + + #[test] + fn parachain_is_updated_if_newer_head_is_known() { + assert_eq!( + select_parachains_to_update::( + vec![(ParaId(PARA_ID), AvailableHeader::Available(PARA_1_HASH))] + .into_iter() + .collect(), + vec![( + ParaId(PARA_ID), + Some(BestParaHeadHash { at_relay_block_number: 0, head_hash: PARA_0_HASH }) + )] + .into_iter() + .collect(), + HeaderId(10, Default::default()), + ), + vec![ParaId(PARA_ID)], + ); + } + + #[test] + fn parachain_is_not_updated_if_source_head_is_unavailable() { + assert_eq!( + select_parachains_to_update::( + vec![(ParaId(PARA_ID), AvailableHeader::Unavailable)].into_iter().collect(), + vec![( + ParaId(PARA_ID), + Some(BestParaHeadHash { at_relay_block_number: 0, head_hash: PARA_0_HASH }) + )] + .into_iter() + .collect(), + HeaderId(10, Default::default()), + ), + vec![], + ); + } + + #[test] + fn is_update_required_works() { + let mut sync_params = ParachainSyncParams { + parachains: vec![ParaId(PARA_ID), ParaId(PARA_1_ID)], + strategy: ParachainSyncStrategy::Any, + stall_timeout: Duration::from_secs(60), + }; + + assert!(!is_update_required(&sync_params, &[])); + assert!(is_update_required(&sync_params, &[ParaId(PARA_ID)])); + assert!(is_update_required(&sync_params, &[ParaId(PARA_ID), ParaId(PARA_1_ID)])); + + sync_params.strategy = ParachainSyncStrategy::All; + assert!(!is_update_required(&sync_params, &[])); + assert!(!is_update_required(&sync_params, &[ParaId(PARA_ID)])); + assert!(is_update_required(&sync_params, &[ParaId(PARA_ID), ParaId(PARA_1_ID)])); + } +} diff --git a/relays/parachains/src/parachains_loop_metrics.rs b/relays/parachains/src/parachains_loop_metrics.rs new file mode 100644 index 00000000000..ff8bace2744 --- /dev/null +++ b/relays/parachains/src/parachains_loop_metrics.rs @@ -0,0 +1,98 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use bp_polkadot_core::parachains::ParaId; +use relay_utils::metrics::{ + metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64, +}; + +/// Parachains sync metrics. +#[derive(Clone)] +pub struct ParachainsLoopMetrics { + /// Best parachains header numbers at the source. + best_source_block_numbers: GaugeVec, + /// Best parachains header numbers at the target. + best_target_block_numbers: GaugeVec, +} + +impl ParachainsLoopMetrics { + /// Create and register parachains loop metrics. + pub fn new(prefix: Option<&str>) -> Result { + Ok(ParachainsLoopMetrics { + best_source_block_numbers: GaugeVec::new( + Opts::new( + metric_name(prefix, "best_parachain_block_number_at_source"), + "Best parachain block numbers at the source relay chain".to_string(), + ), + &["parachain"], + )?, + best_target_block_numbers: GaugeVec::new( + Opts::new( + metric_name(prefix, "best_parachain_block_number_at_target"), + "Best parachain block numbers at the target chain".to_string(), + ), + &["parachain"], + )?, + }) + } + + /// Update best block number at source. + pub fn update_best_parachain_block_at_source>( + &self, + parachain: ParaId, + block_number: Number, + ) { + let block_number = block_number.into(); + let label = parachain_label(¶chain); + log::trace!( + target: "bridge-metrics", + "Updated value of metric 'best_parachain_block_number_at_source[{}]': {:?}", + label, + block_number, + ); + self.best_source_block_numbers.with_label_values(&[&label]).set(block_number); + } + + /// Update best block number at target. + pub fn update_best_parachain_block_at_target>( + &self, + parachain: ParaId, + block_number: Number, + ) { + let block_number = block_number.into(); + let label = parachain_label(¶chain); + log::trace!( + target: "bridge-metrics", + "Updated value of metric 'best_parachain_block_number_at_target[{}]': {:?}", + label, + block_number, + ); + self.best_target_block_numbers.with_label_values(&[&label]).set(block_number); + } +} + +impl Metric for ParachainsLoopMetrics { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.best_source_block_numbers.clone(), registry)?; + register(self.best_target_block_numbers.clone(), registry)?; + Ok(()) + } +} + +/// Return metric label for the parachain. +fn parachain_label(parachain: &ParaId) -> String { + format!("para_{}", parachain.0) +} diff --git a/relays/utils/Cargo.toml b/relays/utils/Cargo.toml new file mode 100644 index 00000000000..ebbfa74c6ff --- /dev/null +++ b/relays/utils/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "relay-utils" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +ansi_term = "0.12" +anyhow = "1.0" +async-std = "1.6.5" +async-trait = "0.1" +backoff = "0.2" +isahc = "1.2" +env_logger = "0.8.2" +futures = "0.3.5" +jsonpath_lib = "0.2" +log = "0.4.17" +num-traits = "0.2" +serde_json = "1.0" +sysinfo = "0.15" +time = { version = "0.3", features = ["formatting", "local-offset", "std"] } +tokio = { version = "1.8", features = ["rt"] } +thiserror = "1.0.26" + +# Bridge dependencies + +bp-runtime = { path = "../../primitives/runtime" } + +# Substrate dependencies + +substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/relays/utils/src/error.rs b/relays/utils/src/error.rs new file mode 100644 index 00000000000..26f1d0cacef --- /dev/null +++ b/relays/utils/src/error.rs @@ -0,0 +1,46 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. +// Parity Bridges Common 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. +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use std::net::AddrParseError; +use thiserror::Error; + +/// Result type used by relay utilities. +pub type Result = std::result::Result; + +/// Relay utilities errors. +#[derive(Error, Debug)] +pub enum Error { + /// Failed to request a float value from HTTP service. + #[error("Failed to fetch token price from remote server: {0}")] + FetchTokenPrice(#[source] anyhow::Error), + /// Failed to parse the response from HTTP service. + #[error("Failed to parse HTTP service response: {0:?}. Response: {1:?}")] + ParseHttp(serde_json::Error, String), + /// Failed to select response value from the Json response. + #[error("Failed to select value from response: {0:?}. Response: {1:?}")] + SelectResponseValue(jsonpath_lib::JsonPathError, String), + /// Failed to parse float value from the selected value. + #[error( + "Failed to parse float value {0:?} from response. It is assumed to be positive and normal" + )] + ParseFloat(f64), + /// Couldn't found value in the JSON response. + #[error("Missing required value from response: {0:?}")] + MissingResponseValue(String), + /// Invalid host address was used for exposing Prometheus metrics. + #[error("Invalid host {0} is used to expose Prometheus metrics: {1}")] + ExposingMetricsInvalidHost(String, AddrParseError), + /// Prometheus error. + #[error("{0}")] + Prometheus(#[from] substrate_prometheus_endpoint::prometheus::Error), +} diff --git a/relays/utils/src/initialize.rs b/relays/utils/src/initialize.rs new file mode 100644 index 00000000000..8224c1803ad --- /dev/null +++ b/relays/utils/src/initialize.rs @@ -0,0 +1,136 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Relayer initialization functions. + +use std::{cell::RefCell, fmt::Display, io::Write}; + +async_std::task_local! { + pub(crate) static LOOP_NAME: RefCell = RefCell::new(String::default()); +} + +/// Initialize relay environment. +pub fn initialize_relay() { + initialize_logger(true); +} + +/// Initialize Relay logger instance. +pub fn initialize_logger(with_timestamp: bool) { + let format = time::format_description::parse( + "[year]-[month]-[day] \ + [hour repr:24]:[minute]:[second] [offset_hour sign:mandatory]", + ) + .expect("static format string is valid"); + + let mut builder = env_logger::Builder::new(); + builder.filter_level(log::LevelFilter::Warn); + builder.filter_module("bridge", log::LevelFilter::Info); + builder.parse_default_env(); + if with_timestamp { + builder.format(move |buf, record| { + let timestamp = time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()); + let timestamp = timestamp.format(&format).unwrap_or_else(|_| timestamp.to_string()); + + let log_level = color_level(record.level()); + let log_target = color_target(record.target()); + let timestamp = if cfg!(windows) { + Either::Left(timestamp) + } else { + Either::Right(ansi_term::Colour::Fixed(8).bold().paint(timestamp)) + }; + + writeln!( + buf, + "{}{} {} {} {}", + loop_name_prefix(), + timestamp, + log_level, + log_target, + record.args(), + ) + }); + } else { + builder.format(move |buf, record| { + let log_level = color_level(record.level()); + let log_target = color_target(record.target()); + + writeln!(buf, "{}{log_level} {log_target} {}", loop_name_prefix(), record.args(),) + }); + } + + builder.init(); +} + +/// Initialize relay loop. Must only be called once per every loop task. +pub(crate) fn initialize_loop(loop_name: String) { + LOOP_NAME.with(|g_loop_name| *g_loop_name.borrow_mut() = loop_name); +} + +/// Returns loop name prefix to use in logs. The prefix is initialized with the `initialize_loop` +/// call. +fn loop_name_prefix() -> String { + // try_with to avoid panic outside of async-std task context + LOOP_NAME + .try_with(|loop_name| { + // using borrow is ok here, because loop is only initialized once (=> borrow_mut will + // only be called once) + let loop_name = loop_name.borrow(); + if loop_name.is_empty() { + String::new() + } else { + format!("[{loop_name}] ") + } + }) + .unwrap_or_else(|_| String::new()) +} + +enum Either { + Left(A), + Right(B), +} +impl Display for Either { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Left(a) => write!(fmt, "{a}"), + Self::Right(b) => write!(fmt, "{b}"), + } + } +} + +fn color_target(target: &str) -> impl Display + '_ { + if cfg!(windows) { + Either::Left(target) + } else { + Either::Right(ansi_term::Colour::Fixed(8).paint(target)) + } +} + +fn color_level(level: log::Level) -> impl Display { + if cfg!(windows) { + Either::Left(level) + } else { + let s = level.to_string(); + use ansi_term::Colour as Color; + Either::Right(match level { + log::Level::Error => Color::Fixed(9).bold().paint(s), + log::Level::Warn => Color::Fixed(11).bold().paint(s), + log::Level::Info => Color::Fixed(10).paint(s), + log::Level::Debug => Color::Fixed(14).paint(s), + log::Level::Trace => Color::Fixed(12).paint(s), + }) + } +} diff --git a/relays/utils/src/lib.rs b/relays/utils/src/lib.rs new file mode 100644 index 00000000000..42bf86ebf5b --- /dev/null +++ b/relays/utils/src/lib.rs @@ -0,0 +1,312 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Utilities used by different relays. + +pub use bp_runtime::HeaderId; +pub use error::Error; +pub use relay_loop::{relay_loop, relay_metrics}; + +use async_trait::async_trait; +use backoff::{backoff::Backoff, ExponentialBackoff}; +use futures::future::FutureExt; +use std::time::Duration; +use thiserror::Error; + +/// Default relay loop stall timeout. If transactions generated by relay are immortal, then +/// this timeout is used. +/// +/// There are no any strict requirements on block time in Substrate. But we assume here that all +/// Substrate-based chains will be designed to produce relatively fast (compared to the slowest +/// blockchains) blocks. So 1 hour seems to be a good guess for (even congested) chains to mine +/// transaction, or remove it from the pool. +pub const STALL_TIMEOUT: Duration = Duration::from_secs(60 * 60); + +/// Max delay after connection-unrelated error happened before we'll try the +/// same request again. +pub const MAX_BACKOFF_INTERVAL: Duration = Duration::from_secs(60); +/// Delay after connection-related error happened before we'll try +/// reconnection again. +pub const CONNECTION_ERROR_DELAY: Duration = Duration::from_secs(10); + +pub mod error; +pub mod initialize; +pub mod metrics; +pub mod relay_loop; + +/// Block number traits shared by all chains that relay is able to serve. +pub trait BlockNumberBase: + 'static + + From + + Into + + Ord + + Clone + + Copy + + Default + + Send + + Sync + + std::fmt::Debug + + std::fmt::Display + + std::hash::Hash + + std::ops::Add + + std::ops::Sub + + num_traits::CheckedSub + + num_traits::Saturating + + num_traits::Zero + + num_traits::One +{ +} + +impl BlockNumberBase for T where + T: 'static + + From + + Into + + Ord + + Clone + + Copy + + Default + + Send + + Sync + + std::fmt::Debug + + std::fmt::Display + + std::hash::Hash + + std::ops::Add + + std::ops::Sub + + num_traits::CheckedSub + + num_traits::Saturating + + num_traits::Zero + + num_traits::One +{ +} + +/// Macro that returns (client, Err(error)) tuple from function if result is Err(error). +#[macro_export] +macro_rules! bail_on_error { + ($result: expr) => { + match $result { + (client, Ok(result)) => (client, result), + (client, Err(error)) => return (client, Err(error)), + } + }; +} + +/// Macro that returns (client, Err(error)) tuple from function if result is Err(error). +#[macro_export] +macro_rules! bail_on_arg_error { + ($result: expr, $client: ident) => { + match $result { + Ok(result) => result, + Err(error) => return ($client, Err(error)), + } + }; +} + +/// Error type that can signal connection errors. +pub trait MaybeConnectionError { + /// Returns true if error (maybe) represents connection error. + fn is_connection_error(&self) -> bool; +} + +/// Final status of the tracked transaction. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum TrackedTransactionStatus { + /// Transaction has been lost. + Lost, + /// Transaction has been mined and finalized at given block. + Finalized(BlockId), +} + +/// Transaction tracker. +#[async_trait] +pub trait TransactionTracker: Send { + /// Header id, used by the chain. + type HeaderId: Clone + Send; + + /// Wait until transaction is either finalized or invalidated/lost. + async fn wait(self) -> TrackedTransactionStatus; +} + +/// Stringified error that may be either connection-related or not. +#[derive(Error, Debug)] +pub enum StringifiedMaybeConnectionError { + /// The error is connection-related error. + #[error("{0}")] + Connection(String), + /// The error is connection-unrelated error. + #[error("{0}")] + NonConnection(String), +} + +impl StringifiedMaybeConnectionError { + /// Create new stringified connection error. + pub fn new(is_connection_error: bool, error: String) -> Self { + if is_connection_error { + StringifiedMaybeConnectionError::Connection(error) + } else { + StringifiedMaybeConnectionError::NonConnection(error) + } + } +} + +impl MaybeConnectionError for StringifiedMaybeConnectionError { + fn is_connection_error(&self) -> bool { + match *self { + StringifiedMaybeConnectionError::Connection(_) => true, + StringifiedMaybeConnectionError::NonConnection(_) => false, + } + } +} + +/// Exponential backoff for connection-unrelated errors retries. +pub fn retry_backoff() -> ExponentialBackoff { + ExponentialBackoff { + // we do not want relayer to stop + max_elapsed_time: None, + max_interval: MAX_BACKOFF_INTERVAL, + ..Default::default() + } +} + +/// Compact format of IDs vector. +pub fn format_ids(mut ids: impl ExactSizeIterator) -> String { + const NTH_PROOF: &str = "we have checked len; qed"; + match ids.len() { + 0 => "".into(), + 1 => format!("{:?}", ids.next().expect(NTH_PROOF)), + 2 => { + let id0 = ids.next().expect(NTH_PROOF); + let id1 = ids.next().expect(NTH_PROOF); + format!("[{id0:?}, {id1:?}]") + }, + len => { + let id0 = ids.next().expect(NTH_PROOF); + let id_last = ids.last().expect(NTH_PROOF); + format!("{len}:[{id0:?} ... {id_last:?}]") + }, + } +} + +/// Stream that emits item every `timeout_ms` milliseconds. +pub fn interval(timeout: Duration) -> impl futures::Stream { + futures::stream::unfold((), move |_| async move { + async_std::task::sleep(timeout).await; + Some(((), ())) + }) +} + +/// Which client has caused error. +#[derive(Debug, Eq, Clone, Copy, PartialEq)] +pub enum FailedClient { + /// It is the source client who has caused error. + Source, + /// It is the target client who has caused error. + Target, + /// Both clients are failing, or we just encountered some other error that + /// should be treated like that. + Both, +} + +/// Future process result. +#[derive(Debug, Clone, Copy)] +pub enum ProcessFutureResult { + /// Future has been processed successfully. + Success, + /// Future has failed with non-connection error. + Failed, + /// Future has failed with connection error. + ConnectionFailed, +} + +impl ProcessFutureResult { + /// Returns true if result is Success. + pub fn is_ok(self) -> bool { + match self { + ProcessFutureResult::Success => true, + ProcessFutureResult::Failed | ProcessFutureResult::ConnectionFailed => false, + } + } + + /// Returns `Ok(())` if future has succeeded. + /// Returns `Err(failed_client)` otherwise. + pub fn fail_if_error(self, failed_client: FailedClient) -> Result<(), FailedClient> { + if self.is_ok() { + Ok(()) + } else { + Err(failed_client) + } + } + + /// Returns Ok(true) if future has succeeded. + /// Returns Ok(false) if future has failed with non-connection error. + /// Returns Err if future is `ConnectionFailed`. + pub fn fail_if_connection_error( + self, + failed_client: FailedClient, + ) -> Result { + match self { + ProcessFutureResult::Success => Ok(true), + ProcessFutureResult::Failed => Ok(false), + ProcessFutureResult::ConnectionFailed => Err(failed_client), + } + } +} + +/// Process result of the future from a client. +pub fn process_future_result( + result: Result, + retry_backoff: &mut ExponentialBackoff, + on_success: impl FnOnce(TResult), + go_offline_future: &mut std::pin::Pin<&mut futures::future::Fuse>, + go_offline: impl FnOnce(Duration) -> TGoOfflineFuture, + error_pattern: impl FnOnce() -> String, +) -> ProcessFutureResult +where + TError: std::fmt::Debug + MaybeConnectionError, + TGoOfflineFuture: FutureExt, +{ + match result { + Ok(result) => { + on_success(result); + retry_backoff.reset(); + ProcessFutureResult::Success + }, + Err(error) if error.is_connection_error() => { + log::error!( + target: "bridge", + "{}: {:?}. Going to restart", + error_pattern(), + error, + ); + + retry_backoff.reset(); + go_offline_future.set(go_offline(CONNECTION_ERROR_DELAY).fuse()); + ProcessFutureResult::ConnectionFailed + }, + Err(error) => { + let retry_delay = retry_backoff.next_backoff().unwrap_or(CONNECTION_ERROR_DELAY); + log::error!( + target: "bridge", + "{}: {:?}. Retrying in {}", + error_pattern(), + error, + retry_delay.as_secs_f64(), + ); + + go_offline_future.set(go_offline(retry_delay).fuse()); + ProcessFutureResult::Failed + }, + } +} diff --git a/relays/utils/src/metrics.rs b/relays/utils/src/metrics.rs new file mode 100644 index 00000000000..fa7a79a71c1 --- /dev/null +++ b/relays/utils/src/metrics.rs @@ -0,0 +1,165 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +pub use float_json_value::FloatJsonValueMetric; +pub use global::GlobalMetrics; +pub use substrate_prometheus_endpoint::{ + prometheus::core::{Atomic, Collector}, + register, Counter, CounterVec, Gauge, GaugeVec, Opts, PrometheusError, Registry, F64, I64, U64, +}; + +use async_std::sync::{Arc, RwLock}; +use async_trait::async_trait; +use std::{fmt::Debug, time::Duration}; + +mod float_json_value; +mod global; + +/// Shared reference to `f64` value that is updated by the metric. +pub type F64SharedRef = Arc>>; +/// Int gauge metric type. +pub type IntGauge = Gauge; + +/// Unparsed address that needs to be used to expose Prometheus metrics. +#[derive(Debug, Clone)] +pub struct MetricsAddress { + /// Serve HTTP requests at given host. + pub host: String, + /// Serve HTTP requests at given port. + pub port: u16, +} + +/// Prometheus endpoint MetricsParams. +#[derive(Debug, Clone)] +pub struct MetricsParams { + /// Interface and TCP port to be used when exposing Prometheus metrics. + pub address: Option, + /// Metrics registry. May be `Some(_)` if several components share the same endpoint. + pub registry: Registry, +} + +/// Metric API. +pub trait Metric: Clone + Send + Sync + 'static { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError>; +} + +/// Standalone metric API. +/// +/// Metrics of this kind know how to update themselves, so we may just spawn and forget the +/// asynchronous self-update task. +#[async_trait] +pub trait StandaloneMetric: Metric { + /// Update metric values. + async fn update(&self); + + /// Metrics update interval. + fn update_interval(&self) -> Duration; + + /// Register and spawn metric. Metric is only spawned if it is registered for the first time. + fn register_and_spawn(self, registry: &Registry) -> Result<(), PrometheusError> { + match self.register(registry) { + Ok(()) => { + self.spawn(); + Ok(()) + }, + Err(PrometheusError::AlreadyReg) => Ok(()), + Err(e) => Err(e), + } + } + + /// Spawn the self update task that will keep update metric value at given intervals. + fn spawn(self) { + async_std::task::spawn(async move { + let update_interval = self.update_interval(); + loop { + self.update().await; + async_std::task::sleep(update_interval).await; + } + }); + } +} + +impl Default for MetricsAddress { + fn default() -> Self { + MetricsAddress { host: "127.0.0.1".into(), port: 9616 } + } +} + +impl MetricsParams { + /// Creates metrics params so that metrics are not exposed. + pub fn disabled() -> Self { + MetricsParams { address: None, registry: Registry::new() } + } + + /// Do not expose metrics. + #[must_use] + pub fn disable(mut self) -> Self { + self.address = None; + self + } +} + +impl From> for MetricsParams { + fn from(address: Option) -> Self { + MetricsParams { address, registry: Registry::new() } + } +} + +/// Returns metric name optionally prefixed with given prefix. +pub fn metric_name(prefix: Option<&str>, name: &str) -> String { + if let Some(prefix) = prefix { + format!("{prefix}_{name}") + } else { + name.into() + } +} + +/// Set value of gauge metric. +/// +/// If value is `Ok(None)` or `Err(_)`, metric would have default value. +pub fn set_gauge_value, E: Debug>( + gauge: &Gauge, + value: Result, E>, +) { + gauge.set(match value { + Ok(Some(value)) => { + log::trace!( + target: "bridge-metrics", + "Updated value of metric '{:?}': {:?}", + gauge.desc().first().map(|d| &d.fq_name), + value, + ); + value + }, + Ok(None) => { + log::warn!( + target: "bridge-metrics", + "Failed to update metric '{:?}': value is empty", + gauge.desc().first().map(|d| &d.fq_name), + ); + Default::default() + }, + Err(error) => { + log::warn!( + target: "bridge-metrics", + "Failed to update metric '{:?}': {:?}", + gauge.desc().first().map(|d| &d.fq_name), + error, + ); + Default::default() + }, + }) +} diff --git a/relays/utils/src/metrics/float_json_value.rs b/relays/utils/src/metrics/float_json_value.rs new file mode 100644 index 00000000000..17b09e05097 --- /dev/null +++ b/relays/utils/src/metrics/float_json_value.rs @@ -0,0 +1,147 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{ + error::{self, Error}, + metrics::{ + metric_name, register, F64SharedRef, Gauge, Metric, PrometheusError, Registry, + StandaloneMetric, F64, + }, +}; + +use async_std::sync::{Arc, RwLock}; +use async_trait::async_trait; +use std::time::Duration; + +/// Value update interval. +const UPDATE_INTERVAL: Duration = Duration::from_secs(300); + +/// Metric that represents float value received from HTTP service as float gauge. +/// +/// The float value returned by the service is assumed to be normal (`f64::is_normal` +/// should return `true`) and strictly positive. +#[derive(Debug, Clone)] +pub struct FloatJsonValueMetric { + url: String, + json_path: String, + metric: Gauge, + shared_value_ref: F64SharedRef, +} + +impl FloatJsonValueMetric { + /// Create new metric instance with given name and help. + pub fn new( + url: String, + json_path: String, + name: String, + help: String, + ) -> Result { + let shared_value_ref = Arc::new(RwLock::new(None)); + Ok(FloatJsonValueMetric { + url, + json_path, + metric: Gauge::new(metric_name(None, &name), help)?, + shared_value_ref, + }) + } + + /// Get shared reference to metric value. + pub fn shared_value_ref(&self) -> F64SharedRef { + self.shared_value_ref.clone() + } + + /// Request value from HTTP service. + async fn request_value(&self) -> anyhow::Result { + use isahc::{AsyncReadResponseExt, HttpClient, Request}; + + let request = Request::get(&self.url).header("Accept", "application/json").body(())?; + let raw_response = HttpClient::new()?.send_async(request).await?.text().await?; + Ok(raw_response) + } + + /// Read value from HTTP service. + async fn read_value(&self) -> error::Result { + let raw_response = self.request_value().await.map_err(Error::FetchTokenPrice)?; + parse_service_response(&self.json_path, &raw_response) + } +} + +impl Metric for FloatJsonValueMetric { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.metric.clone(), registry).map(drop) + } +} + +#[async_trait] +impl StandaloneMetric for FloatJsonValueMetric { + fn update_interval(&self) -> Duration { + UPDATE_INTERVAL + } + + async fn update(&self) { + let value = self.read_value().await; + let maybe_ok = value.as_ref().ok().copied(); + crate::metrics::set_gauge_value(&self.metric, value.map(Some)); + *self.shared_value_ref.write().await = maybe_ok; + } +} + +/// Parse HTTP service response. +fn parse_service_response(json_path: &str, response: &str) -> error::Result { + let json = + serde_json::from_str(response).map_err(|err| Error::ParseHttp(err, response.to_owned()))?; + + let mut selector = jsonpath_lib::selector(&json); + let maybe_selected_value = + selector(json_path).map_err(|err| Error::SelectResponseValue(err, response.to_owned()))?; + let selected_value = maybe_selected_value + .first() + .and_then(|v| v.as_f64()) + .ok_or_else(|| Error::MissingResponseValue(response.to_owned()))?; + if !selected_value.is_normal() || selected_value < 0.0 { + return Err(Error::ParseFloat(selected_value)) + } + + Ok(selected_value) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_service_response_works() { + assert_eq!( + parse_service_response("$.kusama.usd", r#"{"kusama":{"usd":433.05}}"#).map_err(drop), + Ok(433.05), + ); + } + + #[test] + fn parse_service_response_rejects_negative_numbers() { + assert!(parse_service_response("$.kusama.usd", r#"{"kusama":{"usd":-433.05}}"#).is_err()); + } + + #[test] + fn parse_service_response_rejects_zero_numbers() { + assert!(parse_service_response("$.kusama.usd", r#"{"kusama":{"usd":0.0}}"#).is_err()); + } + + #[test] + fn parse_service_response_rejects_nan() { + assert!(parse_service_response("$.kusama.usd", r#"{"kusama":{"usd":NaN}}"#).is_err()); + } +} diff --git a/relays/utils/src/metrics/global.rs b/relays/utils/src/metrics/global.rs new file mode 100644 index 00000000000..df90a2c4823 --- /dev/null +++ b/relays/utils/src/metrics/global.rs @@ -0,0 +1,118 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Global system-wide Prometheus metrics exposed by relays. + +use crate::metrics::{ + metric_name, register, Gauge, GaugeVec, Metric, Opts, PrometheusError, Registry, + StandaloneMetric, F64, U64, +}; + +use async_std::sync::{Arc, Mutex}; +use async_trait::async_trait; +use std::time::Duration; +use sysinfo::{ProcessExt, RefreshKind, System, SystemExt}; + +/// Global metrics update interval. +const UPDATE_INTERVAL: Duration = Duration::from_secs(10); + +/// Global Prometheus metrics. +#[derive(Debug, Clone)] +pub struct GlobalMetrics { + system: Arc>, + system_average_load: GaugeVec, + process_cpu_usage_percentage: Gauge, + process_memory_usage_bytes: Gauge, +} + +impl GlobalMetrics { + /// Create and register global metrics. + pub fn new() -> Result { + Ok(GlobalMetrics { + system: Arc::new(Mutex::new(System::new_with_specifics(RefreshKind::everything()))), + system_average_load: GaugeVec::new( + Opts::new(metric_name(None, "system_average_load"), "System load average"), + &["over"], + )?, + process_cpu_usage_percentage: Gauge::new( + metric_name(None, "process_cpu_usage_percentage"), + "Process CPU usage", + )?, + process_memory_usage_bytes: Gauge::new( + metric_name(None, "process_memory_usage_bytes"), + "Process memory (resident set size) usage", + )?, + }) + } +} + +impl Metric for GlobalMetrics { + fn register(&self, registry: &Registry) -> Result<(), PrometheusError> { + register(self.system_average_load.clone(), registry)?; + register(self.process_cpu_usage_percentage.clone(), registry)?; + register(self.process_memory_usage_bytes.clone(), registry)?; + Ok(()) + } +} + +#[async_trait] +impl StandaloneMetric for GlobalMetrics { + async fn update(&self) { + // update system-wide metrics + let mut system = self.system.lock().await; + let load = system.get_load_average(); + self.system_average_load.with_label_values(&["1min"]).set(load.one); + self.system_average_load.with_label_values(&["5min"]).set(load.five); + self.system_average_load.with_label_values(&["15min"]).set(load.fifteen); + + // update process-related metrics + let pid = sysinfo::get_current_pid().expect( + "only fails where pid is unavailable (os=unknown || arch=wasm32);\ + relay is not supposed to run in such MetricsParamss;\ + qed", + ); + let is_process_refreshed = system.refresh_process(pid); + match (is_process_refreshed, system.get_process(pid)) { + (true, Some(process_info)) => { + let cpu_usage = process_info.cpu_usage() as f64; + let memory_usage = process_info.memory() * 1024; + log::trace!( + target: "bridge-metrics", + "Refreshed process metrics: CPU={}, memory={}", + cpu_usage, + memory_usage, + ); + + self.process_cpu_usage_percentage.set(if cpu_usage.is_finite() { + cpu_usage + } else { + 0f64 + }); + self.process_memory_usage_bytes.set(memory_usage); + }, + _ => { + log::warn!( + target: "bridge-metrics", + "Failed to refresh process information. Metrics may show obsolete values", + ); + }, + } + } + + fn update_interval(&self) -> Duration { + UPDATE_INTERVAL + } +} diff --git a/relays/utils/src/relay_loop.rs b/relays/utils/src/relay_loop.rs new file mode 100644 index 00000000000..11e14744a07 --- /dev/null +++ b/relays/utils/src/relay_loop.rs @@ -0,0 +1,269 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +use crate::{ + error::Error, + metrics::{Metric, MetricsAddress, MetricsParams}, + FailedClient, MaybeConnectionError, +}; + +use async_trait::async_trait; +use std::{fmt::Debug, future::Future, net::SocketAddr, time::Duration}; +use substrate_prometheus_endpoint::{init_prometheus, Registry}; + +/// Default pause between reconnect attempts. +pub const RECONNECT_DELAY: Duration = Duration::from_secs(10); + +/// Basic blockchain client from relay perspective. +#[async_trait] +pub trait Client: 'static + Clone + Send + Sync { + /// Type of error these clients returns. + type Error: 'static + Debug + MaybeConnectionError + Send + Sync; + + /// Try to reconnect to source node. + async fn reconnect(&mut self) -> Result<(), Self::Error>; +} + +#[async_trait] +impl Client for () { + type Error = crate::StringifiedMaybeConnectionError; + + async fn reconnect(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +/// Returns generic loop that may be customized and started. +pub fn relay_loop(source_client: SC, target_client: TC) -> Loop { + Loop { reconnect_delay: RECONNECT_DELAY, source_client, target_client, loop_metric: None } +} + +/// Returns generic relay loop metrics that may be customized and used in one or several relay +/// loops. +pub fn relay_metrics(params: MetricsParams) -> LoopMetrics<(), (), ()> { + LoopMetrics { + relay_loop: Loop { + reconnect_delay: RECONNECT_DELAY, + source_client: (), + target_client: (), + loop_metric: None, + }, + address: params.address, + registry: params.registry, + loop_metric: None, + } +} + +/// Generic relay loop. +pub struct Loop { + reconnect_delay: Duration, + source_client: SC, + target_client: TC, + loop_metric: Option, +} + +/// Relay loop metrics builder. +pub struct LoopMetrics { + relay_loop: Loop, + address: Option, + registry: Registry, + loop_metric: Option, +} + +impl Loop { + /// Customize delay between reconnect attempts. + #[must_use] + pub fn reconnect_delay(mut self, reconnect_delay: Duration) -> Self { + self.reconnect_delay = reconnect_delay; + self + } + + /// Start building loop metrics using given prefix. + pub fn with_metrics(self, params: MetricsParams) -> LoopMetrics { + LoopMetrics { + relay_loop: Loop { + reconnect_delay: self.reconnect_delay, + source_client: self.source_client, + target_client: self.target_client, + loop_metric: None, + }, + address: params.address, + registry: params.registry, + loop_metric: None, + } + } + + /// Run relay loop. + /// + /// This function represents an outer loop, which in turn calls provided `run_loop` function to + /// do actual job. When `run_loop` returns, this outer loop reconnects to failed client (source, + /// target or both) and calls `run_loop` again. + pub async fn run(mut self, loop_name: String, run_loop: R) -> Result<(), Error> + where + R: 'static + Send + Fn(SC, TC, Option) -> F, + F: 'static + Send + Future>, + SC: 'static + Client, + TC: 'static + Client, + LM: 'static + Send + Clone, + { + let run_loop_task = async move { + crate::initialize::initialize_loop(loop_name); + + loop { + let loop_metric = self.loop_metric.clone(); + let future_result = + run_loop(self.source_client.clone(), self.target_client.clone(), loop_metric); + let result = future_result.await; + + match result { + Ok(()) => break, + Err(failed_client) => + reconnect_failed_client( + failed_client, + self.reconnect_delay, + &mut self.source_client, + &mut self.target_client, + ) + .await, + } + + log::debug!(target: "bridge", "Restarting relay loop"); + } + + Ok(()) + }; + + async_std::task::spawn(run_loop_task).await + } +} + +impl LoopMetrics { + /// Add relay loop metrics. + /// + /// Loop metrics will be passed to the loop callback. + pub fn loop_metric( + self, + metric: NewLM, + ) -> Result, Error> { + metric.register(&self.registry)?; + + Ok(LoopMetrics { + relay_loop: self.relay_loop, + address: self.address, + registry: self.registry, + loop_metric: Some(metric), + }) + } + + /// Convert into `MetricsParams` structure so that metrics registry may be extended later. + pub fn into_params(self) -> MetricsParams { + MetricsParams { address: self.address, registry: self.registry } + } + + /// Expose metrics using address passed at creation. + /// + /// If passed `address` is `None`, metrics are not exposed. + pub async fn expose(self) -> Result, Error> { + if let Some(address) = self.address { + let socket_addr = SocketAddr::new( + address + .host + .parse() + .map_err(|err| Error::ExposingMetricsInvalidHost(address.host.clone(), err))?, + address.port, + ); + + let registry = self.registry; + async_std::task::spawn(async move { + let runtime = + match tokio::runtime::Builder::new_current_thread().enable_all().build() { + Ok(runtime) => runtime, + Err(err) => { + log::trace!( + target: "bridge-metrics", + "Failed to create tokio runtime. Prometheus meterics are not available: {:?}", + err, + ); + return + }, + }; + + runtime.block_on(async move { + log::trace!( + target: "bridge-metrics", + "Starting prometheus endpoint at: {:?}", + socket_addr, + ); + let result = init_prometheus(socket_addr, registry).await; + log::trace!( + target: "bridge-metrics", + "Prometheus endpoint has exited with result: {:?}", + result, + ); + }); + }); + } + + Ok(Loop { + reconnect_delay: self.relay_loop.reconnect_delay, + source_client: self.relay_loop.source_client, + target_client: self.relay_loop.target_client, + loop_metric: self.loop_metric, + }) + } +} + +/// Deal with the client who has returned connection error. +pub async fn reconnect_failed_client( + failed_client: FailedClient, + reconnect_delay: Duration, + source_client: &mut impl Client, + target_client: &mut impl Client, +) { + loop { + async_std::task::sleep(reconnect_delay).await; + if failed_client == FailedClient::Both || failed_client == FailedClient::Source { + match source_client.reconnect().await { + Ok(()) => (), + Err(error) => { + log::warn!( + target: "bridge", + "Failed to reconnect to source client. Going to retry in {}s: {:?}", + reconnect_delay.as_secs(), + error, + ); + continue + }, + } + } + if failed_client == FailedClient::Both || failed_client == FailedClient::Target { + match target_client.reconnect().await { + Ok(()) => (), + Err(error) => { + log::warn!( + target: "bridge", + "Failed to reconnect to target client. Going to retry in {}s: {:?}", + reconnect_delay.as_secs(), + error, + ); + continue + }, + } + } + + break + } +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000000..082150daf04 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,24 @@ +# Basic +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Crate" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Format comments +comment_width = 100 +wrap_comments = true +# Misc +chain_width = 80 +spaces_around_ranges = false +binop_separator = "Back" +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true + diff --git a/scripts/add_license.sh b/scripts/add_license.sh new file mode 100755 index 00000000000..49864b47c05 --- /dev/null +++ b/scripts/add_license.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +PAT_GPL="^// Copyright.*If not, see \.$" +PAT_OTHER="^// Copyright" + +SCRIPTS_DIR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) + +for f in $(find . -type f | egrep '\.(c|cpp|rs)$'); do + HEADER=$(head -16 $f) + if [[ $HEADER =~ $PAT_GPL ]]; then + BODY=$(tail -n +17 $f) + cat $SCRIPTS_DIR/license_header > temp + echo "$BODY" >> temp + mv temp $f + elif [[ $HEADER =~ $PAT_OTHER ]]; then + echo "Other license was found do nothing" + else + echo "$f was missing header" + cat $SCRIPTS_DIR/license_header $f > temp + mv temp $f + fi +done diff --git a/scripts/build-containers.sh b/scripts/build-containers.sh new file mode 100755 index 00000000000..d0af254d93f --- /dev/null +++ b/scripts/build-containers.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eux + +time docker build . -t local/substrate-relay --build-arg=PROJECT=substrate-relay +time docker build . -t local/rialto-bridge-node --build-arg=PROJECT=rialto-bridge-node +time docker build . -t local/millau-bridge-node --build-arg=PROJECT=millau-bridge-node +time docker build . -t local/rialto-parachain-collator --build-arg=PROJECT=rialto-parachain-collator diff --git a/scripts/ci-cache.sh b/scripts/ci-cache.sh new file mode 100755 index 00000000000..040d44fa74a --- /dev/null +++ b/scripts/ci-cache.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -xeu + +echo $CARGO_TARGET_DIR; +mkdir -p $CARGO_TARGET_DIR; +echo "Current Rust nightly version:"; +rustc +nightly --version; +echo "Cached Rust nightly version:"; +if [ ! -f $CARGO_TARGET_DIR/check_nightly_rust ]; then + echo "" > $CARGO_TARGET_DIR/check_nightly_rust; +fi +cat $CARGO_TARGET_DIR/check_nightly_rust; +if [[ $(cat $CARGO_TARGET_DIR/check_nightly_rust) == $(rustc +nightly --version) ]]; then + echo "The Rust nightly version has not changed"; +else + echo "The Rust nightly version has changed. Clearing the cache"; + rm -rf $CARGO_TARGET_DIR/*; +fi diff --git a/scripts/dump-logs.sh b/scripts/dump-logs.sh new file mode 100755 index 00000000000..51e8518872a --- /dev/null +++ b/scripts/dump-logs.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# A script to dump logs from selected important docker containers +# to make it easier to analyze locally. + +set -xeu + +DATE=$(date +"%Y-%m-%d-%T") +LOGS_DIR="${DATE//:/-}-logs" +mkdir $LOGS_DIR +cd $LOGS_DIR + +# From $ docker ps --format '{{.Names}}' + +SERVICES=(\ + deployments_relay-messages-millau-to-rialto-generator_1 \ + deployments_relay-messages-rialto-to-millau-generator_1 \ + deployments_relay-messages-millau-to-rialto-lane-00000001_1 \ + deployments_relay-messages-rialto-to-millau-lane-00000001_1 \ + deployments_relay-millau-rialto_1 \ + deployments_relay-headers-westend-to-millau-1_1 \ + deployments_relay-headers-westend-to-millau-2_1 \ + deployments_relay-parachains-westend-to-millau-1_1 \ + deployments_relay-parachains-westend-to-millau-1_2 \ + deployments_relay-messages-millau-to-rialto-parachain-generator_1 \ + deployments_relay-messages-rialto-parachain-to-millau-generator_1 \ + deployments_relay-millau-rialto-parachain-1_1 \ + deployments_relay-millau-rialto-parachain-2_1 \ + deployments_rialto-node-alice_1 \ + deployments_rialto-node-bob_1 \ + deployments_millau-node-alice_1 \ + deployments_millau-node-bob_1 \ + deployments_rialto-parachain-collator-alice_1 \ + deployments_rialto-parachain-collator-bob_1 \ + deployments_relay-messages-millau-to-rialto-resubmitter_1 \ + deployments_relay-messages-millau-to-rialto-parachain-resubmitter_1 \ +) + +for SVC in ${SERVICES[*]} +do + SHORT_NAME="${SVC//deployments_/}" + docker logs $SVC &> $SHORT_NAME.log | true +done + +cd - +tar cvjf $LOGS_DIR.tar.bz2 $LOGS_DIR diff --git a/scripts/license_header b/scripts/license_header new file mode 100644 index 00000000000..f9b301209bb --- /dev/null +++ b/scripts/license_header @@ -0,0 +1,16 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + diff --git a/scripts/send-message-from-millau-rialto.sh b/scripts/send-message-from-millau-rialto.sh new file mode 100755 index 00000000000..539ca5fc06c --- /dev/null +++ b/scripts/send-message-from-millau-rialto.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Used for manually sending a message to a running network. +# +# You could for example spin up a full network using the Docker Compose files +# we have (to make sure the message relays are running), but remove the message +# generator service. From there you may submit messages manually using this script. + +# TODO: Fix demeo scripts https://github.com/paritytech/parity-bridges-common/issues/1406 + +MILLAU_PORT="${RIALTO_PORT:-9945}" + +case "$1" in + remark) + RUST_LOG=runtime=trace,substrate-relay=trace,bridge=trace \ + ./target/debug/substrate-relay send-message millau-to-rialto \ + --source-host localhost \ + --source-port $MILLAU_PORT \ + --source-signer //Alice \ + raw 020419ac + ;; + transfer) + RUST_LOG=runtime=trace,substrate-relay=trace,bridge=trace \ + ./target/debug/substrate-relay send-message millau-to-rialto \ + --source-host localhost \ + --source-port $MILLAU_PORT \ + --source-signer //Alice \ + transfer \ + --amount 100000000000000 \ + --recipient 5DZvVvd1udr61vL7Xks17TFQ4fi9NiagYLaBobnbPCP14ewA \ + ;; + *) echo "A message type is require. Supported messages: remark, transfer."; exit 1;; +esac diff --git a/scripts/send-message-from-rialto-millau.sh b/scripts/send-message-from-rialto-millau.sh new file mode 100755 index 00000000000..923f588ea47 --- /dev/null +++ b/scripts/send-message-from-rialto-millau.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Used for manually sending a message to a running network. +# +# You could for example spin up a full network using the Docker Compose files +# we have (to make sure the message relays are running), but remove the message +# generator service. From there you may submit messages manually using this script. + +# TODO: Fix demeo scripts https://github.com/paritytech/parity-bridges-common/issues/1406 + +RIALTO_PORT="${RIALTO_PORT:-9944}" + +case "$1" in + remark) + RUST_LOG=runtime=trace,substrate-relay=trace,bridge=trace \ + ./target/debug/substrate-relay send-message rialto-to-millau \ + --source-host localhost \ + --source-port $RIALTO_PORT \ + --source-signer //Bob \ + raw 020419ac + ;; + transfer) + RUST_LOG=runtime=trace,substrate-relay=trace,bridge=trace \ + ./target/debug/substrate-relay send-message rialto-to-millau \ + --source-host localhost \ + --source-port $RIALTO_PORT \ + --source-signer //Bob \ + transfer \ + --amount 100000000000000 \ + --recipient 5DZvVvd1udr61vL7Xks17TFQ4fi9NiagYLaBobnbPCP14ewA \ + ;; + *) echo "A message type is require. Supported messages: remark, transfer."; exit 1;; +esac diff --git a/scripts/update-weights-setup.sh b/scripts/update-weights-setup.sh new file mode 100644 index 00000000000..72534423d63 --- /dev/null +++ b/scripts/update-weights-setup.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -exu + +# Set up the standardized machine and run `update-weights.sh` script. +# The system is assumed to be pristine Ubuntu 20.04 and we install +# all required dependencies. + +# To avoid interruptions you might want to run this script in `screen` cause it will take a while +# to finish. + +# We start off with upgrading the system +apt update && apt dist-upgrade + +# and installing `git` and other required deps. +apt install -y git clang curl libssl-dev llvm libudev-dev screen + +# Now we clone the repository +git clone https://github.com/paritytech/parity-bridges-common.git +cd parity-bridges-common + +# Install rustup & toolchain +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y + +# Source config +source ~/.cargo/env + +# Add nightly and WASM +rustup install nightly +rustup target add wasm32-unknown-unknown --toolchain nightly + +# Update the weights +./scripts/update-weights.sh diff --git a/scripts/update-weights.sh b/scripts/update-weights.sh new file mode 100755 index 00000000000..ac24da62b3c --- /dev/null +++ b/scripts/update-weights.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Runtime benchmarks for the `pallet-bridge-messages` and `pallet-bridge-grandpa` pallets. +# +# Run this script from root of the repo. + +set -eux + +time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet \ + --chain=dev \ + --steps=50 \ + --repeat=20 \ + --pallet=pallet_bridge_messages \ + --extrinsic=* \ + --execution=wasm \ + --wasm-execution=Compiled \ + --heap-pages=4096 \ + --output=./modules/messages/src/weights.rs \ + --template=./.maintain/millau-weight-template.hbs + +time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet \ + --chain=dev \ + --steps=50 \ + --repeat=20 \ + --pallet=pallet_bridge_grandpa \ + --extrinsic=* \ + --execution=wasm \ + --wasm-execution=Compiled \ + --heap-pages=4096 \ + --output=./modules/grandpa/src/weights.rs \ + --template=./.maintain/millau-weight-template.hbs + +time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet \ + --chain=dev \ + --steps=50 \ + --repeat=20 \ + --pallet=pallet_bridge_parachains \ + --extrinsic=* \ + --execution=wasm \ + --wasm-execution=Compiled \ + --heap-pages=4096 \ + --output=./modules/parachains/src/weights.rs \ + --template=./.maintain/millau-weight-template.hbs + +time cargo run --release -p millau-bridge-node --features=runtime-benchmarks -- benchmark pallet \ + --chain=dev \ + --steps=50 \ + --repeat=20 \ + --pallet=pallet_bridge_relayers \ + --extrinsic=* \ + --execution=wasm \ + --wasm-execution=Compiled \ + --heap-pages=4096 \ + --output=./modules/relayers/src/weights.rs \ + --template=./.maintain/millau-weight-template.hbs diff --git a/scripts/update_substrate.sh b/scripts/update_substrate.sh new file mode 100755 index 00000000000..f7715bda5d1 --- /dev/null +++ b/scripts/update_substrate.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# One-liner to update between Substrate releases +# Usage: ./update_substrate.sh 2.0.0-rc6 2.0.0 +set -xeu + +OLD_VERSION=$1 +NEW_VERSION=$2 + +find . -type f -name 'Cargo.toml' -exec sed -i '' -e "s/$OLD_VERSION/$NEW_VERSION/g" {} \;